Skip to main content

actix_web_lab/
condition_option.rs

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