//! Future types for the `Fallback` middleware. use std::{ fmt::Debug, future::Future, pin::Pin, task::{Context, Poll}, }; use futures_core::ready; use pin_project::pin_project; use tower::Service; use crate::BoxedError; /// Future that completes either with the first service's successful response, or /// with the second service's response. #[pin_project] pub struct ResponseFuture where S1: Service, S2: Service>::Response>, S2::Error: Into, { #[pin] state: ResponseState, } #[pin_project(project_replace, project = ResponseStateProj)] enum ResponseState where S1: Service, S2: Service, S2::Error: Into, { PollResponse1 { #[pin] fut: S1::Future, req: Request, svc2: S2, }, PollReady2 { req: Request, svc2: S2, }, PollResponse2 { #[pin] fut: S2::Future, }, // Placeholder value to swap into the pin projection of the enum so we can take ownership of the fields. Tmp, } impl ResponseFuture where S1: Service, S2: Service>::Response>, S2::Error: Into, { pub(crate) fn new(fut: S1::Future, req: Request, svc2: S2) -> Self { ResponseFuture { state: ResponseState::PollResponse1 { fut, req, svc2 }, } } } impl Future for ResponseFuture where S1: Service, S2: Service>::Response>, S2::Error: Into, { type Output = Result<>::Response, BoxedError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); // CORRECTNESS // // The current task must be scheduled for wakeup every time we return // `Poll::Pending`. // // This loop ensures that the task is scheduled as required, because it // only returns Pending when a future or service returns Pending. loop { match this.state.as_mut().project() { ResponseStateProj::PollResponse1 { fut, .. } => match ready!(fut.poll(cx)) { Ok(rsp) => return Poll::Ready(Ok(rsp)), Err(_) => { tracing::debug!("got error from svc1, retrying on svc2"); if let __ResponseStateProjectionOwned::PollResponse1 { req, svc2, .. } = this.state.as_mut().project_replace(ResponseState::Tmp) { this.state.set(ResponseState::PollReady2 { req, svc2 }); } else { unreachable!(); } } }, ResponseStateProj::PollReady2 { svc2, .. } => match ready!(svc2.poll_ready(cx)) { Err(e) => return Poll::Ready(Err(e.into())), Ok(()) => { if let __ResponseStateProjectionOwned::PollReady2 { mut svc2, req } = this.state.as_mut().project_replace(ResponseState::Tmp) { this.state.set(ResponseState::PollResponse2 { fut: svc2.call(req), }); } else { unreachable!(); } } }, ResponseStateProj::PollResponse2 { fut } => { return fut.poll(cx).map_err(Into::into) } ResponseStateProj::Tmp => unreachable!(), } } } } impl Debug for ResponseFuture where S1: Service, S2: Service>::Response>, Request: Debug, S1::Future: Debug, S2: Debug, S2::Future: Debug, S2::Error: Into, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ResponseFuture") .field("state", &self.state) .finish() } } impl Debug for ResponseState where S1: Service, S2: Service>::Response>, Request: Debug, S1::Future: Debug, S2: Debug, S2::Future: Debug, S2::Error: Into, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ResponseState::PollResponse1 { fut, req, svc2 } => f .debug_struct("ResponseState::PollResponse1") .field("fut", fut) .field("req", req) .field("svc2", svc2) .finish(), ResponseState::PollReady2 { req, svc2 } => f .debug_struct("ResponseState::PollReady2") .field("req", req) .field("svc2", svc2) .finish(), ResponseState::PollResponse2 { fut } => f .debug_struct("ResponseState::PollResponse2") .field("fut", fut) .finish(), ResponseState::Tmp => unreachable!(), } } }