use futures_core::ready; use pin_project_lite::pin_project; use std::{ fmt, future::Future, pin::Pin, task::{Context, Poll}, }; use tower_service::Service; pin_project! { /// A [`Future`] consuming a [`Service`] and request, waiting until the [`Service`] /// is ready, and then calling [`Service::call`] with the request, and /// waiting for that [`Future`]. #[derive(Debug)] pub struct Oneshot, Req> { #[pin] state: State, } } pin_project! { #[project = StateProj] enum State, Req> { NotReady { svc: S, req: Option, }, Called { #[pin] fut: S::Future, }, Done, } } impl, Req> State { fn not_ready(svc: S, req: Option) -> Self { Self::NotReady { svc, req } } fn called(fut: S::Future) -> Self { Self::Called { fut } } } impl fmt::Debug for State where S: Service + fmt::Debug, Req: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { State::NotReady { svc, req: Some(req), } => f .debug_tuple("State::NotReady") .field(svc) .field(req) .finish(), State::NotReady { req: None, .. } => unreachable!(), State::Called { .. } => f.debug_tuple("State::Called").field(&"S::Future").finish(), State::Done => f.debug_tuple("State::Done").finish(), } } } impl Oneshot where S: Service, { #[allow(missing_docs)] pub fn new(svc: S, req: Req) -> Self { Oneshot { state: State::not_ready(svc, Some(req)), } } } impl Future for Oneshot where S: Service, { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); loop { match this.state.as_mut().project() { StateProj::NotReady { svc, req } => { let _ = ready!(svc.poll_ready(cx))?; let f = svc.call(req.take().expect("already called")); this.state.set(State::called(f)); } StateProj::Called { fut } => { let res = ready!(fut.poll(cx))?; this.state.set(State::Done); return Poll::Ready(Ok(res)); } StateProj::Done => panic!("polled after complete"), } } } }