diff --git a/actix-service/src/fn_service.rs b/actix-service/src/fn_service.rs index 230f437b..19151eed 100644 --- a/actix-service/src/fn_service.rs +++ b/actix-service/src/fn_service.rs @@ -1,4 +1,4 @@ -use core::{future::Future, marker::PhantomData, task::Poll}; +use core::{future::Future, marker::PhantomData}; use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory}; diff --git a/actix-service/src/lib.rs b/actix-service/src/lib.rs index a4f6c5b4..b690553f 100644 --- a/actix-service/src/lib.rs +++ b/actix-service/src/lib.rs @@ -21,6 +21,7 @@ mod apply_cfg; pub mod boxed; mod ext; mod fn_service; +mod macros; mod map; mod map_config; mod map_err; @@ -325,31 +326,3 @@ pub mod dev { pub use crate::transform::ApplyTransform; pub use crate::transform_err::TransformMapInitErr; } - -#[macro_export] -macro_rules! always_ready { - () => { - #[inline] - fn poll_ready( - &self, - _: &mut ::core::task::Context<'_>, - ) -> ::core::task::Poll> { - Poll::Ready(Ok(())) - } - }; -} - -#[macro_export] -macro_rules! forward_ready { - ($field:ident) => { - #[inline] - fn poll_ready( - &self, - cx: &mut ::core::task::Context<'_>, - ) -> ::core::task::Poll> { - self.$field - .poll_ready(cx) - .map_err(::core::convert::Into::into) - } - }; -} diff --git a/actix-service/src/macros.rs b/actix-service/src/macros.rs new file mode 100644 index 00000000..4a083895 --- /dev/null +++ b/actix-service/src/macros.rs @@ -0,0 +1,181 @@ +/// A boilerplate implementation of [`Service::poll_ready`] that always signals readiness. +/// +/// [`Service::poll_ready`]: crate::Service::poll_ready +/// +/// # Examples +/// ```no_run +/// use actix_service::Service; +/// use futures_util::future::{ready, Ready}; +/// +/// struct IdentityService; +/// +/// impl Service for IdentityService { +/// type Response = u32; +/// type Error = (); +/// type Future = Ready>; +/// +/// actix_service::always_ready!(); +/// +/// fn call(&self, req: u32) -> Self::Future { +/// ready(Ok(req)) +/// } +/// } +/// ``` +#[macro_export] +macro_rules! always_ready { + () => { + #[inline] + fn poll_ready( + &self, + _: &mut ::core::task::Context<'_>, + ) -> ::core::task::Poll> { + ::core::task::Poll::Ready(Ok(())) + } + }; +} + +/// A boilerplate implementation of [`Service::poll_ready`] that forwards readiness checks to a +/// named struct field. +/// +/// Tuple structs are not supported. +/// +/// [`Service::poll_ready`]: crate::Service::poll_ready +/// +/// # Examples +/// ```no_run +/// use actix_service::Service; +/// use futures_util::future::{ready, Ready}; +/// +/// struct WrapperService { +/// inner: S, +/// } +/// +/// impl Service<()> for WrapperService +/// where +/// S: Service<()>, +/// { +/// type Response = S::Response; +/// type Error = S::Error; +/// type Future = S::Future; +/// +/// actix_service::forward_ready!(inner); +/// +/// fn call(&self, req: ()) -> Self::Future { +/// self.inner.call(req) +/// } +/// } +/// ``` +#[macro_export] +macro_rules! forward_ready { + ($field:ident) => { + #[inline] + fn poll_ready( + &self, + cx: &mut ::core::task::Context<'_>, + ) -> ::core::task::Poll> { + self.$field + .poll_ready(cx) + .map_err(::core::convert::Into::into) + } + }; +} + +#[cfg(test)] +mod tests { + use core::{ + cell::Cell, + convert::Infallible, + task::{self, Context, Poll}, + }; + + use futures_util::{ + future::{ready, Ready}, + task::noop_waker, + }; + + use crate::Service; + + struct IdentityService; + + impl Service for IdentityService { + type Response = u32; + type Error = Infallible; + type Future = Ready>; + + always_ready!(); + + fn call(&self, req: u32) -> Self::Future { + ready(Ok(req)) + } + } + + struct CountdownService(Cell); + + impl Service<()> for CountdownService { + type Response = (); + type Error = Infallible; + type Future = Ready>; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let count = self.0.get(); + + if count == 0 { + Poll::Ready(Ok(())) + } else { + self.0.set(count - 1); + cx.waker().wake_by_ref(); + Poll::Pending + } + } + + fn call(&self, _: ()) -> Self::Future { + ready(Ok(())) + } + } + + struct WrapperService { + inner: S, + } + + impl Service<()> for WrapperService + where + S: Service<()>, + { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + forward_ready!(inner); + + fn call(&self, req: ()) -> Self::Future { + self.inner.call(req) + } + } + + #[test] + fn test_always_ready_macro() { + let waker = noop_waker(); + let mut cx = task::Context::from_waker(&waker); + + let svc = IdentityService; + + assert!(svc.poll_ready(&mut cx).is_ready()); + assert!(svc.poll_ready(&mut cx).is_ready()); + assert!(svc.poll_ready(&mut cx).is_ready()); + } + + #[test] + fn test_forward_ready_macro() { + let waker = noop_waker(); + let mut cx = task::Context::from_waker(&waker); + + let svc = WrapperService { + inner: CountdownService(Cell::new(3)), + }; + + assert!(svc.poll_ready(&mut cx).is_pending()); + assert!(svc.poll_ready(&mut cx).is_pending()); + assert!(svc.poll_ready(&mut cx).is_pending()); + assert!(svc.poll_ready(&mut cx).is_ready()); + } +} diff --git a/actix-utils/src/timeout.rs b/actix-utils/src/timeout.rs index 9304e5f6..f13c7ffa 100644 --- a/actix-utils/src/timeout.rs +++ b/actix-utils/src/timeout.rs @@ -197,7 +197,6 @@ where #[cfg(test)] mod tests { - use core::task::Poll; use core::time::Duration; use super::*;