1use std::{future::Future, marker::PhantomData, rc::Rc};
2
3use actix_service::boxed::{self, BoxFuture, RcService};
4#[expect(deprecated, reason = "preserve the public ready future type")]
5use actix_utils::future::{ready, Ready};
6use futures_core::future::LocalBoxFuture;
7
8use crate::{
9 body::MessageBody,
10 dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
11 Error, FromRequest,
12};
13
14pub fn from_fn<F, Es>(mw_fn: F) -> MiddlewareFn<F, Es> {
85 MiddlewareFn {
86 mw_fn: Rc::new(mw_fn),
87 _phantom: PhantomData,
88 }
89}
90
91#[allow(missing_debug_implementations)]
93pub struct MiddlewareFn<F, Es> {
94 mw_fn: Rc<F>,
95 _phantom: PhantomData<Es>,
96}
97
98#[expect(deprecated, reason = "preserve the public ready future type")]
99impl<S, F, Fut, B, B2> Transform<S, ServiceRequest> for MiddlewareFn<F, ()>
100where
101 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
102 F: Fn(ServiceRequest, Next<B>) -> Fut + 'static,
103 Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
104 B2: MessageBody,
105{
106 type Response = ServiceResponse<B2>;
107 type Error = Error;
108 type Transform = MiddlewareFnService<F, B, ()>;
109 type InitError = ();
110 type Future = Ready<Result<Self::Transform, Self::InitError>>;
111
112 fn new_transform(&self, service: S) -> Self::Future {
113 ready(Ok(MiddlewareFnService {
114 service: boxed::rc_service(service),
115 mw_fn: Rc::clone(&self.mw_fn),
116 _phantom: PhantomData,
117 }))
118 }
119}
120
121#[allow(missing_debug_implementations)]
123pub struct MiddlewareFnService<F, B, Es> {
124 service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
125 mw_fn: Rc<F>,
126 _phantom: PhantomData<(B, Es)>,
127}
128
129impl<F, Fut, B, B2> Service<ServiceRequest> for MiddlewareFnService<F, B, ()>
130where
131 F: Fn(ServiceRequest, Next<B>) -> Fut,
132 Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
133 B2: MessageBody,
134{
135 type Response = ServiceResponse<B2>;
136 type Error = Error;
137 type Future = Fut;
138
139 forward_ready!(service);
140
141 fn call(&self, req: ServiceRequest) -> Self::Future {
142 (self.mw_fn)(
143 req,
144 Next::<B> {
145 service: Rc::clone(&self.service),
146 },
147 )
148 }
149}
150
151macro_rules! impl_middleware_fn_service {
152 ($($ext_type:ident),*) => {
153 #[expect(deprecated, reason = "preserve the public ready future type")]
154 impl<S, F, Fut, B, B2, $($ext_type),*> Transform<S, ServiceRequest> for MiddlewareFn<F, ($($ext_type),*,)>
155 where
156 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
157 F: Fn($($ext_type),*, ServiceRequest, Next<B>) -> Fut + 'static,
158 $($ext_type: FromRequest + 'static,)*
159 Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
160 B: MessageBody + 'static,
161 B2: MessageBody + 'static,
162 {
163 type Response = ServiceResponse<B2>;
164 type Error = Error;
165 type Transform = MiddlewareFnService<F, B, ($($ext_type,)*)>;
166 type InitError = ();
167 type Future = Ready<Result<Self::Transform, Self::InitError>>;
168
169 fn new_transform(&self, service: S) -> Self::Future {
170 ready(Ok(MiddlewareFnService {
171 service: boxed::rc_service(service),
172 mw_fn: Rc::clone(&self.mw_fn),
173 _phantom: PhantomData,
174 }))
175 }
176 }
177
178 impl<F, $($ext_type),*, Fut, B: 'static, B2> Service<ServiceRequest>
179 for MiddlewareFnService<F, B, ($($ext_type),*,)>
180 where
181 F: Fn(
182 $($ext_type),*,
183 ServiceRequest,
184 Next<B>
185 ) -> Fut + 'static,
186 $($ext_type: FromRequest + 'static,)*
187 Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
188 B2: MessageBody + 'static,
189 {
190 type Response = ServiceResponse<B2>;
191 type Error = Error;
192 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
193
194 forward_ready!(service);
195
196 #[allow(nonstandard_style)]
197 fn call(&self, mut req: ServiceRequest) -> Self::Future {
198 let mw_fn = Rc::clone(&self.mw_fn);
199 let service = Rc::clone(&self.service);
200
201 Box::pin(async move {
202 let ($($ext_type,)*) = req.extract::<($($ext_type,)*)>().await?;
203
204 (mw_fn)($($ext_type),*, req, Next::<B> { service }).await
205 })
206 }
207 }
208 };
209}
210
211impl_middleware_fn_service!(E1);
212impl_middleware_fn_service!(E1, E2);
213impl_middleware_fn_service!(E1, E2, E3);
214impl_middleware_fn_service!(E1, E2, E3, E4);
215impl_middleware_fn_service!(E1, E2, E3, E4, E5);
216impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6);
217impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7);
218impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8);
219impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8, E9);
220
221#[allow(missing_debug_implementations)]
223pub struct Next<B> {
224 service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
225}
226
227impl<B> Next<B> {
228 pub fn call(&self, req: ServiceRequest) -> <Self as Service<ServiceRequest>>::Future {
230 Service::call(self, req)
231 }
232}
233
234impl<B> Service<ServiceRequest> for Next<B> {
235 type Response = ServiceResponse<B>;
236 type Error = Error;
237 type Future = BoxFuture<Result<Self::Response, Self::Error>>;
238
239 forward_ready!(service);
240
241 fn call(&self, req: ServiceRequest) -> Self::Future {
242 self.service.call(req)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::{
250 http::header::{self, HeaderValue},
251 middleware::{Compat, Logger},
252 test, web, App, HttpResponse,
253 };
254
255 async fn noop<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
256 next.call(req).await
257 }
258
259 async fn add_res_header<B>(
260 req: ServiceRequest,
261 next: Next<B>,
262 ) -> Result<ServiceResponse<B>, Error> {
263 let mut res = next.call(req).await?;
264 res.headers_mut()
265 .insert(header::WARNING, HeaderValue::from_static("42"));
266 Ok(res)
267 }
268
269 async fn mutate_body_type(
270 req: ServiceRequest,
271 next: Next<impl MessageBody + 'static>,
272 ) -> Result<ServiceResponse<impl MessageBody>, Error> {
273 let res = next.call(req).await?;
274 Ok(res.map_into_left_body::<()>())
275 }
276
277 struct MyMw(bool);
278
279 impl MyMw {
280 async fn mw_cb(
281 &self,
282 req: ServiceRequest,
283 next: Next<impl MessageBody + 'static>,
284 ) -> Result<ServiceResponse<impl MessageBody>, Error> {
285 let mut res = match self.0 {
286 true => req.into_response("short-circuited").map_into_right_body(),
287 false => next.call(req).await?.map_into_left_body(),
288 };
289 res.headers_mut()
290 .insert(header::WARNING, HeaderValue::from_static("42"));
291 Ok(res)
292 }
293
294 pub fn into_middleware<S, B>(
295 self,
296 ) -> impl Transform<
297 S,
298 ServiceRequest,
299 Response = ServiceResponse<impl MessageBody>,
300 Error = Error,
301 InitError = (),
302 >
303 where
304 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
305 B: MessageBody + 'static,
306 {
307 let this = Rc::new(self);
308 from_fn(move |req, next| {
309 let this = Rc::clone(&this);
310 async move { Self::mw_cb(&this, req, next).await }
311 })
312 }
313 }
314
315 #[actix_rt::test]
316 async fn compat_compat() {
317 let _ = App::new().wrap(Compat::new(from_fn(noop)));
318 let _ = App::new().wrap(Compat::new(from_fn(mutate_body_type)));
319 }
320
321 #[actix_rt::test]
322 async fn permits_different_in_and_out_body_types() {
323 let app = test::init_service(
324 App::new()
325 .wrap(from_fn(mutate_body_type))
326 .wrap(from_fn(add_res_header))
327 .wrap(Logger::default())
328 .wrap(from_fn(noop))
329 .default_service(web::to(HttpResponse::NotFound)),
330 )
331 .await;
332
333 let req = test::TestRequest::default().to_request();
334 let res = test::call_service(&app, req).await;
335 assert!(res.headers().contains_key(header::WARNING));
336 }
337
338 #[actix_rt::test]
339 async fn closure_capture_and_return_from_fn() {
340 let app = test::init_service(
341 App::new()
342 .wrap(Logger::default())
343 .wrap(MyMw(true).into_middleware())
344 .wrap(Logger::default()),
345 )
346 .await;
347
348 let req = test::TestRequest::default().to_request();
349 let res = test::call_service(&app, req).await;
350 assert!(res.headers().contains_key(header::WARNING));
351 }
352}