layered 0.3.3

Build composable async services with layered middleware.
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::fmt::Debug;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};

use infinity_pool::{BlindPool, BlindPooledMut, define_pooled_dyn_cast};

use crate::Service;

/// Extension trait for converting services to [`DynamicService`].
///
/// Provides type erasure for services, useful when working with complex service
/// compositions or storing services of different types.
pub trait DynamicServiceExt<In, Out>: Sized {
    /// Converts this service into a type-erased [`DynamicService`].
    fn into_dynamic(self) -> DynamicService<In, Out>;
}

impl<In: Send + 'static, Out: Send + 'static, T> DynamicServiceExt<In, Out> for T
where
    T: Service<In, Out = Out> + 'static,
{
    fn into_dynamic(self) -> DynamicService<In, Out> {
        DynamicService::new(self)
    }
}

/// Type-erased wrapper for [`Service`] that hides the concrete type.
///
/// Use `DynamicService` when working with complex service compositions where the
/// concrete type becomes unwieldy, or when storing services of different types in
/// collections.
///
/// Type erasure adds some overhead, but it's typically negligible compared to the
/// actual service work (network calls, database queries, etc.).
///
/// # Examples
///
/// ```
/// use layered::{DynamicService, DynamicServiceExt, Execute, Service};
///
/// async fn example() {
///     // Create a concrete service that doubles an integer and uses a closure.
///     let service = Execute::new(|v: i32| async move { v * 2 });
///
///     // Create a type-erased service
///     let service: DynamicService<i32, i32> = service.into_dynamic();
///
///     // Use it like any other service
///     let result = service.execute(42).await;
///     println!("Result: {}", result);
/// }
/// ```
pub struct DynamicService<In, Out> {
    exec: Arc<dyn Fn(In) -> BlindPooledMut<dyn SendFuture<Out>> + Send + Sync>,
}

pub(crate) trait SendFuture<Out>: Future<Output = Out> + Send {}
impl<Out: Send + 'static, F: Future<Output = Out> + Send> SendFuture<Out> for F {}
define_pooled_dyn_cast!(SendFuture<Out>);

impl<In: Send + 'static, Out: Send + 'static> DynamicService<In, Out> {
    pub(crate) fn new<T>(strategy: T) -> Self
    where
        T: Service<In, Out = Out> + Send + Sync + 'static,
    {
        let pool = BlindPool::new();
        let service = Arc::new(strategy);
        // define a delegate that wraps the service execution in a
        // future and inserts it into the pool
        let exec = move |input: In| {
            let cloned = Arc::clone(&service);
            let fut = async move { cloned.execute(input).await };
            pool.insert(fut).cast_send_future()
        };

        Self { exec: Arc::new(exec) }
    }
}

impl<In: Send, Out: Send> Service<In> for DynamicService<In, Out> {
    type Out = Out;

    fn execute(&self, input: In) -> impl Future<Output = Self::Out> + Send {
        ServiceFuture {
            handle: self.exec.as_ref()(input),
        }
    }
}

struct ServiceFuture<Out> {
    handle: BlindPooledMut<dyn SendFuture<Out>>,
}

impl<Out> Future for ServiceFuture<Out> {
    type Output = Out;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.get_mut().handle.as_pin_mut().poll(cx)
    }
}

impl<In, Out> Debug for DynamicService<In, Out> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DynamicService").finish()
    }
}

impl<In, Out> Clone for DynamicService<In, Out> {
    fn clone(&self) -> Self {
        Self {
            exec: Arc::clone(&self.exec),
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use std::sync::Mutex;

    use futures::executor::block_on;
    use static_assertions::assert_impl_all;

    use super::*;
    use crate::Execute;

    #[test]
    fn assert_types() {
        assert_impl_all!(DynamicService<(), ()>: Send, Sync, Clone, Debug);

        // If non-clonable types are used, ensure the DynamicService is still cloneable
        assert_impl_all!(DynamicService<Mutex<()>, Mutex<()>>: Send, Sync, Clone, Debug);
    }

    #[test]
    fn into_dynamic() {
        let dynamic_service: DynamicService<i32, i32> = Execute::new(|v| async move { v }).into_dynamic();

        assert_eq!(block_on(dynamic_service.execute(42)), 42);
    }

    #[test]
    fn clone_and_debug() {
        let svc: DynamicService<i32, i32> = Execute::new(|v| async move { v }).into_dynamic();
        let cloned = svc.clone();
        assert_eq!(block_on(cloned.execute(1)), 1);
        assert_eq!(format!("{svc:?}"), "DynamicService");
    }
}