rok-container 0.3.4

IoC service container and dependency injection for the rok ecosystem
Documentation
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

use crate::error::ContainerError;

type AnyArc = Arc<dyn Any + Send + Sync>;

enum Binding {
    Singleton(AnyArc),
    Factory(Box<dyn Fn() -> AnyArc + Send + Sync>),
}

/// Type-map IoC service container.
///
/// Intended to be shared as `Arc<Container>` and mounted as an Axum `Extension`.
///
/// # Example
///
/// ```rust,ignore
/// use std::sync::Arc;
/// use rok_container::Container;
///
/// let container = Arc::new(Container::new());
///
/// // singleton — same Arc<T> returned every call
/// container.singleton(MyService::new());
///
/// // factory — fresh instance on every make()
/// container.bind::<Config>(|| Config::from_env());
///
/// // resolve
/// let svc: Arc<MyService> = container.make::<MyService>().unwrap();
/// ```
pub struct Container {
    bindings: RwLock<HashMap<TypeId, Binding>>,
}

impl Default for Container {
    fn default() -> Self {
        Self::new()
    }
}

impl Container {
    /// Create an empty container.
    pub fn new() -> Self {
        Self {
            bindings: RwLock::new(HashMap::new()),
        }
    }

    /// Register a factory for `T`.
    ///
    /// Every call to [`make::<T>`](Container::make) invokes `factory` and
    /// returns a new `Arc<T>`.
    pub fn bind<T, F>(&self, factory: F)
    where
        T: Any + Send + Sync + 'static,
        F: Fn() -> T + Send + Sync + 'static,
    {
        let mut map = self.bindings.write().expect("container lock poisoned");
        map.insert(
            TypeId::of::<T>(),
            Binding::Factory(Box::new(move || Arc::new(factory()))),
        );
    }

    /// Register a singleton instance for `T`.
    ///
    /// Every call to [`make::<T>`](Container::make) clones the same `Arc<T>`.
    pub fn singleton<T>(&self, instance: T)
    where
        T: Any + Send + Sync + 'static,
    {
        let mut map = self.bindings.write().expect("container lock poisoned");
        map.insert(TypeId::of::<T>(), Binding::Singleton(Arc::new(instance)));
    }

    /// Resolve `T`, returning `Arc<T>`.
    ///
    /// # Errors
    ///
    /// Returns [`ContainerError::NotRegistered`] if `T` was never bound.
    pub fn make<T>(&self) -> Result<Arc<T>, ContainerError>
    where
        T: Any + Send + Sync + 'static,
    {
        let map = self.bindings.read().expect("container lock poisoned");
        match map.get(&TypeId::of::<T>()) {
            Some(Binding::Singleton(arc)) => arc
                .clone()
                .downcast::<T>()
                .map_err(|_| ContainerError::TypeMismatch(std::any::type_name::<T>())),
            Some(Binding::Factory(f)) => f()
                .downcast::<T>()
                .map_err(|_| ContainerError::TypeMismatch(std::any::type_name::<T>())),
            None => Err(ContainerError::NotRegistered(std::any::type_name::<T>())),
        }
    }

    /// Decorate an existing singleton by transforming it.
    ///
    /// Useful for wrapping a service with a decorator or logging layer.
    ///
    /// # Errors
    ///
    /// Returns an error if `T` is not registered.
    pub fn extend<T, F>(&self, extender: F) -> Result<(), ContainerError>
    where
        T: Any + Send + Sync + 'static,
        F: FnOnce(Arc<T>) -> T,
    {
        let existing = self.make::<T>()?;
        let new_instance = extender(existing);
        self.singleton(new_instance);
        Ok(())
    }

    /// Replace an existing binding with a new singleton.
    ///
    /// Useful in tests to swap a real service for a test double.
    pub fn swap<T>(&self, instance: T)
    where
        T: Any + Send + Sync + 'static,
    {
        self.singleton(instance);
    }
}