ts-webapi 0.4.11

Library for my web API projects
Documentation
//! A future that is defined by the result of a future.

use core::{
    pin::Pin,
    task::{Context, Poll, ready},
};

use http::{Request, Response, StatusCode};
use http_body::Body;
use pin_project_lite::pin_project;
use tower_service::Service;

/// Trait alias for the future that defined the final response.
pub trait DefiningFuture<B>:
    Future<Output = Result<Request<B>, StatusCode>> + Send + 'static
{
}
impl<B, F> DefiningFuture<B> for F where
    F: Future<Output = Result<Request<B>, StatusCode>> + Send + 'static
{
}

pin_project! {
    /// A future that is defined by the result of a future.
    pub struct UndefinedFuture<S, B> where S: Service<Request<B>>, {
        #[pin]
        state: State<S::Future, Pin<Box<dyn DefiningFuture<B>>>>,
        service: S,
    }
}

pin_project! {
    #[project = StateProj]
    enum State<SFut, DFut> {
        Defining {
            #[pin]
            future: DFut,
        },
        Proceeding {
            #[pin]
            future: SFut,
        },
    }
}

impl<S, B> UndefinedFuture<S, B>
where
    S: Service<Request<B>>,
{
    /// Define the response using the given future.
    pub fn define(future: Pin<Box<dyn DefiningFuture<B>>>, service: S) -> Self {
        Self {
            state: State::Defining { future },
            service,
        }
    }

    /// Proceed with the service future.
    pub fn proceed(future: S::Future, service: S) -> Self {
        Self {
            state: State::Proceeding { future },
            service,
        }
    }
}

impl<ResBody, S, B> Future for UndefinedFuture<S, B>
where
    ResBody: Body + Default,
    S: Service<Request<B>, Response = Response<ResBody>>,
{
    type Output = Result<Response<ResBody>, S::Error>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut this = self.project();

        loop {
            match this.state.as_mut().project() {
                StateProj::Defining { future } => {
                    let auth = ready!(future.poll(cx));
                    match auth {
                        Ok(request) => {
                            let future = this.service.call(request);
                            this.state.set(State::Proceeding { future });
                        }
                        Err(status) => {
                            return Poll::Ready(Ok(Response::builder()
                                .status(status)
                                .body(ResBody::default())
                                .expect("error response should be valid response")));
                        }
                    };
                }
                StateProj::Proceeding { future: fut } => {
                    return Poll::Ready(Ok(ready!(fut.poll(cx))?));
                }
            }
        }
    }
}