use std::task::Poll;
use futures::{Future, FutureExt, ready, task::Context};
use tower_service::Service;
enum State<S> {
Pending,
Ready(S),
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct LazyService<TFn, F, S> {
future: F,
service_fn: Option<TFn>,
state: State<S>,
}
impl<TFn, F, S> LazyService<TFn, F, S> {
pub fn new(future: F, service_fn: TFn) -> Self {
Self {
future,
service_fn: Some(service_fn),
state: State::Pending,
}
}
}
impl<TFn, F, S, TReq> Service<TReq> for LazyService<TFn, F, S>
where
F: Future + Unpin,
TFn: FnOnce(F::Output) -> S,
S: Service<TReq>,
{
type Error = S::Error;
type Future = S::Future;
type Response = S::Response;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
loop {
match self.state {
State::Pending => {
let item = ready!(self.future.poll_unpin(cx));
let service_fn = self
.service_fn
.take()
.expect("service_fn cannot be None in Pending state");
self.state = State::Ready((service_fn)(item));
},
State::Ready(ref mut service) => {
return service.poll_ready(cx);
},
}
}
}
fn call(&mut self, req: TReq) -> Self::Future {
match self.state {
State::Pending => panic!("`Service::call` called before `Service::poll_ready` was ready"),
State::Ready(ref mut service) => service.call(req),
}
}
}
#[cfg(test)]
mod test {
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use futures::future::{self, poll_fn};
use futures_test::task::panic_context;
use tower::service_fn;
use super::*;
fn mock_fut(flag: Arc<AtomicBool>) -> impl Future<Output = ()> {
poll_fn::<_, _>(move |_: &mut Context<'_>| {
if flag.load(Ordering::SeqCst) {
().into()
} else {
Poll::Pending
}
})
}
#[test]
#[allow(clippy::redundant_closure)]
fn ready_after_handles() {
let flag = Arc::new(AtomicBool::new(false));
let fut = mock_fut(flag.clone());
let mut cx = panic_context();
let mut service = LazyService::new(fut, |_: ()| service_fn(|num: u8| future::ok::<_, ()>(num)));
assert!(service.poll_ready(&mut cx).is_pending());
flag.store(true, Ordering::SeqCst);
match service.poll_ready(&mut cx) {
Poll::Ready(Ok(_)) => {},
_ => panic!("Unexpected poll result"),
}
}
#[test]
#[allow(clippy::redundant_closure)]
fn call_after_ready() {
let flag = Arc::new(AtomicBool::new(true));
let fut = mock_fut(flag);
let mut service = LazyService::new(fut, |_: ()| service_fn(|num: u8| future::ok::<_, ()>(num)));
let mut cx = panic_context();
assert!(service.poll_ready(&mut cx).is_ready());
let mut fut = service.call(123);
assert!(fut.poll_unpin(&mut cx).is_ready());
}
#[tokio::test]
#[allow(clippy::redundant_closure)]
#[should_panic]
async fn call_before_ready_should_panic() {
let flag = Arc::new(AtomicBool::new(false));
let fut = mock_fut(flag);
let mut service = LazyService::new(fut, |_: ()| service_fn(|num: u8| future::ok::<_, ()>(num)));
let mut cx = panic_context();
assert!(service.poll_ready(&mut cx).is_pending());
let _ = service.call(123).await;
}
}