actix_middleware_macro/
lib.rs

1//! # actix-middleware-macro
2//!
3//! For usage see the doc for the [`create_middleware`] macro.
4
5/// Macro to to reduce boilerplate codes for simple actix_web middleware.
6///
7/// # Example
8/// ```rust
9/// use actix_middleware_macro::create_middleware;
10///
11/// create_middleware!(
12///     TimingHeaders,
13///     |ctx: &MiddlewareTransform<S>, req: ServiceRequest| {
14///         use actix_web::http::header::{HeaderName, HeaderValue};
15///         use chrono::Utc;
16///     
17///         let start = Utc::now();
18///     
19///         let fut = ctx.service.call(req);
20///         Box::pin(async move {
21///             let mut res = fut.await?;
22///             let duration = Utc::now() - start;
23///             res.headers_mut().insert(
24///                 HeaderName::from_static("x-app-time-ms"),
25///                 HeaderValue::from_str(&format!("{}", duration.num_milliseconds()))?,
26///             );
27///             Ok(res)
28///         })
29///     }
30/// );
31///
32/// // Usage
33/// #[actix_web::test]
34/// async fn works() {
35///     let _server = tokio::spawn(async {
36///         actix_web::HttpServer::new(|| {
37///             actix_web::App::new()
38///                 .default_service(web::to(|| async { actix_web::HttpResponse::Ok() }))
39///                 .wrap(timing_cors_headers_middleware::Middleware)
40///         })
41///         .bind("127.1:8080")
42///         .unwrap()
43///         .run()
44///         .await
45///         .unwrap();
46///     });
47///     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
48///     let response = ureq::get("http://127.1:8080").call().unwrap();
49///     assert!(response.header("x-app-time-ms").is_some());
50/// }
51/// ```
52#[macro_export]
53macro_rules! create_middleware {
54    ($name: ident, $code: expr) => {
55        paste::paste! {
56            mod [<$name:snake _middleware>] {
57                use futures_util::future::LocalBoxFuture;
58                use std::future::{ready, Ready};
59
60                use actix_web::{
61                    dev::{Service, ServiceRequest, ServiceResponse, Transform},
62                    Error
63                };
64
65                pub struct Middleware;
66                pub struct MiddlewareTransform<S> {
67                    service: S,
68                }
69
70                impl<S, B> Transform<S, ServiceRequest> for Middleware
71                where
72                    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
73                    S::Future: 'static,
74                    B: 'static,
75                {
76                    type Response = ServiceResponse<B>;
77                    type Error = Error;
78                    type InitError = ();
79                    type Transform = MiddlewareTransform<S>;
80                    type Future = Ready<Result<Self::Transform, Self::InitError>>;
81
82                    fn new_transform(&self, service: S) -> Self::Future {
83                        ready(Ok(MiddlewareTransform { service }))
84                    }
85                }
86
87                impl<S, B> Service<ServiceRequest> for MiddlewareTransform<S>
88                where
89                    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
90                    S::Future: 'static,
91                    B: 'static,
92                {
93                    type Response = ServiceResponse<B>;
94
95                    type Error = Error;
96
97                    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
98
99                    fn poll_ready(
100                        &self,
101                        ctx: &mut core::task::Context<'_>,
102                    ) -> std::task::Poll<Result<(), Self::Error>> {
103                        self.service.poll_ready(ctx)
104                    }
105
106                    fn call(&self, req: ServiceRequest) -> Self::Future {
107                        $code(self, req)
108                    }
109                }
110            }
111        }
112    };
113}
114
115#[cfg(test)]
116mod test {
117    use actix_web::web;
118
119    use super::*;
120
121    create_middleware!(
122        TimingCorsHeaders,
123        |ctx: &MiddlewareTransform<S>, req: ServiceRequest| {
124            use actix_web::http::header::{HeaderName, HeaderValue};
125            use chrono::Utc;
126
127            let start = Utc::now();
128
129            let fut = ctx.service.call(req);
130            Box::pin(async move {
131                let mut res = fut.await?;
132                let duration = Utc::now() - start;
133                res.headers_mut().insert(
134                    HeaderName::from_static("x-app-time-ms"),
135                    HeaderValue::from_str(&format!("{}", duration.num_milliseconds()))?,
136                );
137                res.headers_mut().insert(
138                    HeaderName::from_static("x-app-time-micros"),
139                    HeaderValue::from_str(&format!(
140                        "{}",
141                        duration.num_microseconds().unwrap_or_default()
142                    ))?,
143                );
144                // CORS header
145                res.headers_mut().insert(
146                    HeaderName::from_static("access-control-allow-origin"),
147                    HeaderValue::from_str("*")?,
148                );
149                res.headers_mut().insert(
150                    HeaderName::from_static("access-control-allow-methods"),
151                    HeaderValue::from_str("GET, POST, OPTIONS")?,
152                );
153                Ok(res)
154            })
155        }
156    );
157
158    #[actix_web::test]
159    async fn works() {
160        let _server = tokio::spawn(async {
161            actix_web::HttpServer::new(|| {
162                actix_web::App::new()
163                    .default_service(web::to(|| async { actix_web::HttpResponse::Ok() }))
164                    .wrap(timing_cors_headers_middleware::Middleware)
165            })
166            .bind("127.1:8080")
167            .unwrap()
168            .run()
169            .await
170            .unwrap();
171        });
172
173        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
174
175        let response = ureq::get("http://127.1:8080").call().unwrap();
176        assert!(response.header("x-app-time-ms").is_some());
177    }
178}