ntex-service 5.3.1

ntex service
Documentation
//! Type-erased services and service factories.
//!
//! Use [`service()`] or [`BoxService::new`] to hide a service's concrete type
//! while keeping its state, request, response, and error types explicit.
//! This lets different implementations share a return type or collection.
//! [`factory()`] and [`BoxServiceFactory`] do the same for factories; each
//! created service is returned as a [`BoxService`].
//!
//! These wrappers use reference-counted, local handles. Cloning shares the
//! underlying service or factory rather than cloning its implementation.
//! Binding, readiness, calls, and shutdown are forwarded to the wrapped
//! service. Type erasure preserves existing readiness caches but does not add
//! one to a raw service; use [`crate::Pipeline`] or [`crate::ready()`] when a
//! readiness cache is needed.
//!
//! # Example
//!
//! ```
//! use ntex_service::{boxed, fn_service};
//!
//! fn handler(double: bool) -> boxed::BoxService<(), u32, u32, ()> {
//!     if double {
//!         boxed::service(fn_service(async |req: u32| Ok::<_, ()>(req * 2)))
//!     } else {
//!         boxed::service(fn_service(async |req: u32| Ok::<_, ()>(req)))
//!     }
//! }
//! ```
//!
//! # Framework guide
//!
//! - [Service Model](https://docs.ntex.rs/1-service.html)
//! - [Service Pipelines](https://docs.ntex.rs/2-pipeline.html)

use std::{fmt, rc::Rc};

use crate::{BindCtx, Ctx};
use crate::{Service, ServiceFactory, util::BoxFuture};

// ============================ Service =============================

/// Boxed service.
///
/// Type erasure preserves the wrapped service's readiness identity. Clones
/// share that service and any bound readiness; boxing a raw service does not
/// give it a cache.
pub struct BoxService<St, Req, Res, Err> {
    inner: Rc<dyn ServiceObj<St, Req, Res = Res, Error = Err>>,
}

/// Creates a boxed service.
pub fn service<S, St, Req, Res, Err>(service: S) -> BoxService<St, Req, Res, Err>
where
    S: Service<St, Req, Res = Res, Error = Err> + 'static,
{
    BoxService::new(service)
}

impl<St, Req, Res, Err> BoxService<St, Req, Res, Err> {
    /// Creates a boxed service.
    pub fn new<S>(service: S) -> Self
    where
        S: Service<St, Req, Res = Res, Error = Err> + 'static,
    {
        BoxService {
            inner: Rc::new(service),
        }
    }
}

impl<St, Req, Res, Err> Clone for BoxService<St, Req, Res, Err> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
        }
    }
}

impl<St, Req, Res, Err> fmt::Debug for BoxService<St, Req, Res, Err> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BoxService").finish()
    }
}

impl<St, Req, Res, Err> Service<St, Req> for BoxService<St, Req, Res, Err> {
    type Res = Res;
    type Error = Err;

    fn bind(&self, ctx: BindCtx<'_>) {
        self.inner.bind(ctx);
    }

    #[inline]
    async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
        self.inner.ready(ctx.cast()).await
    }

    #[inline]
    async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<Res, Err> {
        self.inner.call(req, ctx.cast()).await
    }

    #[inline]
    async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
        self.inner.shutdown(ctx.cast()).await;
    }
}

trait ServiceObj<St, Req> {
    type Res;
    type Error;

    fn bind(&self, ctx: BindCtx<'_>);

    fn ready<'a>(&'a self, ctx: Ctx<'a, (), St>) -> BoxFuture<'a, Result<(), Self::Error>>;

    fn call<'a>(
        &'a self,
        r: Req,
        ctx: Ctx<'a, (), St>,
    ) -> BoxFuture<'a, Result<Self::Res, Self::Error>>
    where
        Req: 'a;

    fn shutdown<'a>(&'a self, ctx: Ctx<'a, (), St>) -> BoxFuture<'a, ()>
    where
        St: 'a,
        Req: 'a;
}

impl<S, St, Req> ServiceObj<St, Req> for S
where
    S: Service<St, Req>,
{
    type Res = S::Res;
    type Error = S::Error;

    fn bind(&self, ctx: BindCtx<'_>) {
        Service::bind(self, ctx);
    }

    #[inline]
    fn ready<'a>(&'a self, ctx: Ctx<'a, (), St>) -> BoxFuture<'a, Result<(), Self::Error>> {
        Box::pin(async move { Service::ready(self, ctx.cast()).await })
    }

    #[inline]
    fn call<'a>(&'a self, req: Req, ctx: Ctx<'a, (), St>) -> BoxFuture<'a, Result<S::Res, S::Error>>
    where
        Req: 'a,
    {
        Box::pin(Service::call(self, req, ctx.cast()))
    }

    #[inline]
    fn shutdown<'a>(&'a self, ctx: Ctx<'a, (), St>) -> BoxFuture<'a, ()>
    where
        St: 'a,
        Req: 'a,
    {
        Box::pin(Service::shutdown(self, ctx.cast()))
    }
}

// ============================ ServiceFactory =============================

/// Boxed service factory.
pub struct BoxServiceFactory<St, Req, Res, Err, InitErr> {
    inner: Rc<dyn ServiceFactoryObj<St, Req, Res = Res, Error = Err, InitErr = InitErr>>,
}

/// Creates a boxed service factory.
pub fn factory<Sf, St, Req>(
    factory: Sf,
) -> BoxServiceFactory<St, Req, Sf::Res, Sf::Error, Sf::InitError>
where
    Sf: ServiceFactory<St, Req> + 'static,
    St: 'static,
    Req: 'static,
{
    BoxServiceFactory::new(factory)
}

impl<St, Req, Res, Err, InitErr> BoxServiceFactory<St, Req, Res, Err, InitErr>
where
    St: 'static,
    Req: 'static,
{
    /// Creates a boxed service factory.
    pub fn new<Sf>(factory: Sf) -> Self
    where
        Sf: ServiceFactory<St, Req, Res = Res, Error = Err, InitError = InitErr> + 'static,
        St: 'static,
        Req: 'static,
    {
        Self {
            inner: Rc::new(factory),
        }
    }
}

impl<St, Req, Res, Err, InitErr> ServiceFactory<St, Req>
    for BoxServiceFactory<St, Req, Res, Err, InitErr>
where
    Req: 'static,
{
    type Res = Res;
    type Error = Err;

    type Service = BoxService<St, Req, Res, Err>;
    type InitError = InitErr;

    #[inline]
    async fn create(&self, st: &St) -> Result<Self::Service, Self::InitError> {
        self.inner.create(st).await
    }
}

impl<St, Req, Res, Err, InitErr> Clone for BoxServiceFactory<St, Req, Res, Err, InitErr> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
        }
    }
}

impl<St, Req, Res, Err, InitErr> fmt::Debug for BoxServiceFactory<St, Req, Res, Err, InitErr> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BoxServiceFactory").finish()
    }
}

trait ServiceFactoryObj<St, Req> {
    type Res;
    type Error;
    type InitErr;

    fn create<'a>(
        &'a self,
        st: &'a St,
    ) -> BoxFuture<'a, Result<BoxService<St, Req, Self::Res, Self::Error>, Self::InitErr>>
    where
        Req: 'a;
}

impl<Sf, St, Req> ServiceFactoryObj<St, Req> for Sf
where
    St: 'static,
    Req: 'static,
    Sf: ServiceFactory<St, Req> + 'static,
{
    type Res = Sf::Res;
    type Error = Sf::Error;
    type InitErr = Sf::InitError;

    #[inline]
    fn create<'a>(
        &'a self,
        st: &'a St,
    ) -> BoxFuture<'a, Result<BoxService<St, Req, Self::Res, Self::Error>, Self::InitErr>>
    where
        Req: 'a,
    {
        let fut = ServiceFactory::create(self, st);
        Box::pin(async move { fut.await.map(service) })
    }
}