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;
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! {
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>>,
{
pub fn define(future: Pin<Box<dyn DefiningFuture<B>>>, service: S) -> Self {
Self {
state: State::Defining { future },
service,
}
}
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))?));
}
}
}
}
}