//! [`Or`] used to combine two services into one. use super::{FromEmptyRouter, RoutingDsl}; use crate::body::BoxBody; use futures_util::ready; use http::{Request, Response}; use pin_project_lite::pin_project; use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; use tower::{util::Oneshot, Service, ServiceExt}; /// [`tower::Service`] that is the combination of two routers. /// /// See [`RoutingDsl::or`] for more details. /// /// [`RoutingDsl::or`]: super::RoutingDsl::or #[derive(Debug, Clone, Copy)] pub struct Or { pub(super) first: A, pub(super) second: B, } impl RoutingDsl for Or {} impl crate::sealed::Sealed for Or {} #[allow(warnings)] impl Service> for Or where A: Service, Response = Response> + Clone, B: Service, Response = Response, Error = A::Error> + Clone, ReqBody: Send + Sync + 'static, A: Send + 'static, B: Send + 'static, A::Future: Send + 'static, B::Future: Send + 'static, { type Response = Response; type Error = A::Error; type Future = ResponseFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { ResponseFuture { state: State::FirstFuture { f: self.first.clone().oneshot(req), }, second: Some(self.second.clone()), } } } pin_project! { /// Response future for [`Or`]. pub struct ResponseFuture where A: Service>, B: Service>, { #[pin] state: State, second: Option, } } pin_project! { #[project = StateProj] enum State where A: Service>, B: Service>, { FirstFuture { #[pin] f: Oneshot> }, SecondFuture { #[pin] f: Oneshot>, } } } impl Future for ResponseFuture where A: Service, Response = Response>, B: Service, Response = Response, Error = A::Error>, ReqBody: Send + Sync + 'static, { type Output = Result, A::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { let mut this = self.as_mut().project(); let new_state = match this.state.as_mut().project() { StateProj::FirstFuture { f } => { let mut response = ready!(f.poll(cx)?); let req = if let Some(ext) = response .extensions_mut() .remove::>() { ext.request } else { return Poll::Ready(Ok(response)); }; let second = this.second.take().expect("future polled after completion"); State::SecondFuture { f: second.oneshot(req), } } StateProj::SecondFuture { f } => return f.poll(cx), }; this.state.set(new_state); } } }