actix_web/middleware/
compat.rs1use std::{
4 future::Future,
5 pin::Pin,
6 task::{Context, Poll},
7};
8
9use futures_core::{future::LocalBoxFuture, ready};
10use pin_project_lite::pin_project;
11
12use crate::{
13 body::{BoxBody, MessageBody},
14 dev::{Service, Transform},
15 error::Error,
16 service::ServiceResponse,
17};
18
19pub struct Compat<T> {
38 transform: T,
39}
40
41impl<T> Compat<T> {
42 pub fn new(middleware: T) -> Self {
44 Self {
45 transform: middleware,
46 }
47 }
48}
49
50impl<S, T, Req> Transform<S, Req> for Compat<T>
51where
52 S: Service<Req>,
53 T: Transform<S, Req>,
54 T::Future: 'static,
55 T::Response: MapServiceResponseBody,
56 T::Error: Into<Error>,
57{
58 type Response = ServiceResponse<BoxBody>;
59 type Error = Error;
60 type Transform = CompatMiddleware<T::Transform>;
61 type InitError = T::InitError;
62 type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
63
64 fn new_transform(&self, service: S) -> Self::Future {
65 let fut = self.transform.new_transform(service);
66 Box::pin(async move {
67 let service = fut.await?;
68 Ok(CompatMiddleware { service })
69 })
70 }
71}
72
73pub struct CompatMiddleware<S> {
74 service: S,
75}
76
77impl<S, Req> Service<Req> for CompatMiddleware<S>
78where
79 S: Service<Req>,
80 S::Response: MapServiceResponseBody,
81 S::Error: Into<Error>,
82{
83 type Response = ServiceResponse<BoxBody>;
84 type Error = Error;
85 type Future = CompatMiddlewareFuture<S::Future>;
86
87 actix_service::forward_ready!(service);
88
89 fn call(&self, req: Req) -> Self::Future {
90 let fut = self.service.call(req);
91 CompatMiddlewareFuture { fut }
92 }
93}
94
95pin_project! {
96 pub struct CompatMiddlewareFuture<Fut> {
97 #[pin]
98 fut: Fut,
99 }
100}
101
102impl<Fut, T, E> Future for CompatMiddlewareFuture<Fut>
103where
104 Fut: Future<Output = Result<T, E>>,
105 T: MapServiceResponseBody,
106 E: Into<Error>,
107{
108 type Output = Result<ServiceResponse<BoxBody>, Error>;
109
110 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
111 let res = match ready!(self.project().fut.poll(cx)) {
112 Ok(res) => res,
113 Err(err) => return Poll::Ready(Err(err.into())),
114 };
115
116 Poll::Ready(Ok(res.map_body()))
117 }
118}
119
120pub trait MapServiceResponseBody {
122 fn map_body(self) -> ServiceResponse<BoxBody>;
123}
124
125impl<B> MapServiceResponseBody for ServiceResponse<B>
126where
127 B: MessageBody + 'static,
128{
129 #[inline]
130 fn map_body(self) -> ServiceResponse<BoxBody> {
131 self.map_into_boxed_body()
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 #![allow(unused_imports)]
139
140 use actix_service::IntoService;
141
142 use super::*;
143 use crate::{
144 dev::ServiceRequest,
145 http::StatusCode,
146 middleware::{self, Condition, Identity, Logger},
147 test::{self, call_service, init_service, TestRequest},
148 web, App, HttpResponse,
149 };
150
151 #[actix_rt::test]
152 #[cfg(all(feature = "cookies", feature = "__compress"))]
153 async fn test_scope_middleware() {
154 use crate::middleware::Compress;
155
156 let logger = Logger::default();
157 let compress = Compress::default();
158
159 let srv = init_service(
160 App::new().service(
161 web::scope("app")
162 .wrap(logger)
163 .wrap(Compat::new(compress))
164 .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
165 ),
166 )
167 .await;
168
169 let req = TestRequest::with_uri("/app/test").to_request();
170 let resp = call_service(&srv, req).await;
171 assert_eq!(resp.status(), StatusCode::OK);
172 }
173
174 #[actix_rt::test]
175 #[cfg(all(feature = "cookies", feature = "__compress"))]
176 async fn test_resource_scope_middleware() {
177 use crate::middleware::Compress;
178
179 let logger = Logger::default();
180 let compress = Compress::default();
181
182 let srv = init_service(
183 App::new().service(
184 web::resource("app/test")
185 .wrap(Compat::new(logger))
186 .wrap(Compat::new(compress))
187 .route(web::get().to(HttpResponse::Ok)),
188 ),
189 )
190 .await;
191
192 let req = TestRequest::with_uri("/app/test").to_request();
193 let resp = call_service(&srv, req).await;
194 assert_eq!(resp.status(), StatusCode::OK);
195 }
196
197 #[actix_rt::test]
198 async fn test_condition_scope_middleware() {
199 let srv = |req: ServiceRequest| {
200 Box::pin(
201 async move { Ok(req.into_response(HttpResponse::InternalServerError().finish())) },
202 )
203 };
204
205 let logger = Logger::default();
206
207 let mw = Condition::new(true, Compat::new(logger))
208 .new_transform(srv.into_service())
209 .await
210 .unwrap();
211 let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
212 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
213 }
214
215 #[actix_rt::test]
216 async fn compat_noop_is_noop() {
217 let srv = test::ok_service();
218
219 let mw = Compat::new(Identity)
220 .new_transform(srv.into_service())
221 .await
222 .unwrap();
223
224 let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
225 assert_eq!(resp.status(), StatusCode::OK);
226 }
227}