Skip to content

Commit 7bb3fcc

Browse files
authored
feat: ConditionOption middleware (#227)
1 parent 07e75fb commit 7bb3fcc

File tree

9 files changed

+148
-11
lines changed

9 files changed

+148
-11
lines changed

actix-web-lab/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add `ConditionOption` middleware.
6+
57
## 0.24.2
68

79
- Add `LazyDataShared` extractor.

actix-web-lab/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- `RedirectHttps`: middleware to redirect traffic to HTTPS if connection is insecure with optional HSTS [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/struct.RedirectHttps.html)
3737
- `redirect_to_www`: function middleware to redirect traffic to `www.` if not already there [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/fn.redirect_to_www.html)
3838
- `redirect_to_non_www`: function middleware to redirect traffic to `www.` if not already there [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/fn.redirect_to_non_www.html)
39+
- `ConditionOption`: conditional middleware helper [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/struct.ConditionOption.html)
3940
- `ErrorHandlers`: alternative error handler middleware with simpler interface [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/struct.ErrorHandlers.html)
4041
- `NormalizePath`: alternative path normalizing middleware with redirect option [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/struct.NormalizePath.html)
4142
- `CatchPanic`: catch panics in wrapped handlers and middleware, returning empty 500 responses [(docs)](https://docs.rs/actix-web-lab/0.24.2/actix_web_lab/middleware/struct.CatchPanic.html)

actix-web-lab/examples/body_async_write.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use tokio::{
1818
use tokio_util::compat::TokioAsyncWriteCompatExt as _;
1919

2020
fn zip_to_io_err(err: async_zip::error::ZipError) -> io::Error {
21-
io::Error::new(io::ErrorKind::Other, err)
21+
io::Error::other(err)
2222
}
2323

2424
async fn read_dir<W>(zipper: &mut ZipFileWriter<W>) -> io::Result<()>

actix-web-lab/examples/map_response.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use actix_web::{
66
App, Error, HttpRequest, HttpResponse, HttpServer, body::MessageBody, dev::ServiceResponse,
77
http::header, middleware::Logger, web,
88
};
9-
use actix_web_lab::middleware::{map_response, map_response_body};
9+
use actix_web_lab::middleware::{ConditionOption, map_response, map_response_body};
1010
use tracing::info;
1111

1212
async fn add_res_header(
@@ -33,6 +33,10 @@ async fn main() -> io::Result<()> {
3333
info!("staring server at http://{}:{}", &bind.0, &bind.1);
3434

3535
HttpServer::new(|| {
36+
let mutator_enabled = false;
37+
let mutate_body_type_mw =
38+
ConditionOption::from(mutator_enabled.then(|| map_response_body(mutate_body_type)));
39+
3640
App::new()
3741
.service(
3842
web::resource("/foo")
@@ -43,7 +47,7 @@ async fn main() -> io::Result<()> {
4347
.service(
4448
web::resource("/bar")
4549
.default_service(web::to(HttpResponse::Ok))
46-
.wrap(map_response_body(mutate_body_type))
50+
.wrap(mutate_body_type_mw)
4751
.wrap(Logger::default()),
4852
)
4953
.default_service(web::to(HttpResponse::Ok))
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! For middleware documentation, see [`ConditionOption`].
2+
3+
use std::{
4+
pin::Pin,
5+
task::{self, Poll, ready},
6+
};
7+
8+
use actix_web::{
9+
body::EitherBody,
10+
dev::{Service, ServiceResponse, Transform},
11+
};
12+
use futures_core::future::LocalBoxFuture;
13+
use futures_util::future::FutureExt as _;
14+
use pin_project_lite::pin_project;
15+
16+
/// Middleware for conditionally enabling other middleware in an [`Option`].
17+
///
18+
/// Uses [`Condition`](crate::middleware::condition::Condition) under the hood.
19+
///
20+
/// # Example
21+
/// ```
22+
/// use actix_web::{App, middleware::Logger};
23+
/// use actix_web_lab::middleware::ConditionOption;
24+
///
25+
/// let normalize: ConditionOption<_> = Some(Logger::default()).into();
26+
/// let app = App::new().wrap(normalize);
27+
/// ```
28+
#[derive(Debug)]
29+
pub struct ConditionOption<T> {
30+
inner: Option<T>,
31+
}
32+
33+
impl<T> From<Option<T>> for ConditionOption<T> {
34+
fn from(value: Option<T>) -> Self {
35+
Self { inner: value }
36+
}
37+
}
38+
39+
impl<S, T, Req, BE, BD, Err> Transform<S, Req> for ConditionOption<T>
40+
where
41+
S: Service<Req, Response = ServiceResponse<BD>, Error = Err> + 'static,
42+
T: Transform<S, Req, Response = ServiceResponse<BE>, Error = Err>,
43+
T::Future: 'static,
44+
T::InitError: 'static,
45+
T::Transform: 'static,
46+
{
47+
type Response = ServiceResponse<EitherBody<BE, BD>>;
48+
type Error = Err;
49+
type Transform = ConditionMiddleware<T::Transform, S>;
50+
type InitError = T::InitError;
51+
type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
52+
53+
fn new_transform(&self, service: S) -> Self::Future {
54+
match &self.inner {
55+
Some(transformer) => {
56+
let fut = transformer.new_transform(service);
57+
async move {
58+
let wrapped_svc = fut.await?;
59+
Ok(ConditionMiddleware::Enable(wrapped_svc))
60+
}
61+
.boxed_local()
62+
}
63+
None => async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local(),
64+
}
65+
}
66+
}
67+
68+
/// TODO
69+
#[derive(Debug)]
70+
pub enum ConditionMiddleware<E, D> {
71+
Enable(E),
72+
Disable(D),
73+
}
74+
75+
impl<E, D, Req, BE, BD, Err> Service<Req> for ConditionMiddleware<E, D>
76+
where
77+
E: Service<Req, Response = ServiceResponse<BE>, Error = Err>,
78+
D: Service<Req, Response = ServiceResponse<BD>, Error = Err>,
79+
{
80+
type Response = ServiceResponse<EitherBody<BE, BD>>;
81+
type Error = Err;
82+
type Future = ConditionMiddlewareFuture<E::Future, D::Future>;
83+
84+
fn poll_ready(&self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
85+
match self {
86+
ConditionMiddleware::Enable(service) => service.poll_ready(cx),
87+
ConditionMiddleware::Disable(service) => service.poll_ready(cx),
88+
}
89+
}
90+
91+
fn call(&self, req: Req) -> Self::Future {
92+
match self {
93+
ConditionMiddleware::Enable(service) => ConditionMiddlewareFuture::Enabled {
94+
fut: service.call(req),
95+
},
96+
ConditionMiddleware::Disable(service) => ConditionMiddlewareFuture::Disabled {
97+
fut: service.call(req),
98+
},
99+
}
100+
}
101+
}
102+
103+
pin_project! {
104+
#[doc(hidden)]
105+
#[project = ConditionProj]
106+
pub enum ConditionMiddlewareFuture<E, D> {
107+
Enabled { #[pin] fut: E, },
108+
Disabled { #[pin] fut: D, },
109+
}
110+
}
111+
112+
impl<E, D, BE, BD, Err> Future for ConditionMiddlewareFuture<E, D>
113+
where
114+
E: Future<Output = Result<ServiceResponse<BE>, Err>>,
115+
D: Future<Output = Result<ServiceResponse<BD>, Err>>,
116+
{
117+
type Output = Result<ServiceResponse<EitherBody<BE, BD>>, Err>;
118+
119+
#[inline]
120+
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
121+
let res = match self.project() {
122+
ConditionProj::Enabled { fut } => ready!(fut.poll(cx))?.map_into_left_body(),
123+
ConditionProj::Disabled { fut } => ready!(fut.poll(cx))?.map_into_right_body(),
124+
};
125+
126+
Poll::Ready(Ok(res))
127+
}
128+
}

actix-web-lab/src/lazy_data_shared.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,16 @@ mod tests {
135135
actix_web::rt::time::sleep(Duration::from_millis(40)).await;
136136
10_usize
137137
}))
138-
.service(
139-
web::resource("/").to(|lazy_num: LazyDataShared<usize>| async move {
138+
.service(web::resource("/").to(
139+
#[expect(clippy::async_yields_async)]
140+
|lazy_num: LazyDataShared<usize>| async move {
140141
if *lazy_num.get().await == 10 {
141142
HttpResponse::Ok()
142143
} else {
143144
HttpResponse::InternalServerError()
144145
}
145-
}),
146-
),
146+
},
147+
)),
147148
)
148149
.await;
149150
let req = TestRequest::default().to_request();

actix-web-lab/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod catch_panic;
3131
#[cfg(feature = "cbor")]
3232
mod cbor;
3333
mod clear_site_data;
34+
mod condition_option;
3435
mod content_length;
3536
mod csv;
3637
mod display_stream;

actix-web-lab/src/middleware.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
pub use crate::{
66
catch_panic::CatchPanic,
7+
condition_option::ConditionOption,
78
err_handler::ErrorHandlers,
89
load_shed::LoadShed,
910
middleware_map_response::{MapResMiddleware, map_response},

actix-web-lab/src/util.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,9 @@ pub fn fork_request_payload(orig_payload: &mut dev::Payload) -> dev::Payload {
4141
}
4242

4343
Err(err) => tx
44-
.send(Err(PayloadError::Io(io::Error::new(
45-
io::ErrorKind::Other,
46-
format!("error from original stream: {err}"),
47-
))))
44+
.send(Err(PayloadError::Io(io::Error::other(format!(
45+
"error from original stream: {err}"
46+
)))))
4847
.unwrap(),
4948
}
5049
}));

0 commit comments

Comments
 (0)