noema 0.1.3

Noema IOC and DI framework for Rust
Documentation
use std::collections::HashMap;
use std::sync::Arc;

use crate::resolver::Container;

/// Keyed Services of type S identified by key of type K
/// The services are provided via factory functions to allow for lazy instantiation
pub trait KeyedServices<K, S>
where
    K: Eq + std::hash::Hash,
    S: ?Sized + Send + Sync,
{
    /// Get one service by key
    fn one(&self, key: K) -> Option<Arc<S>> {
        Some(self.all().get(&key)?())
    }

    /// Find services by predicate
    fn find(&self, predicate: &dyn Fn(&Arc<K>) -> bool) -> HashMap<Arc<K>, Arc<S>> {
        let mut result = HashMap::new();
        for (k, v) in self.all().iter() {
            if predicate(&k) {
                result.insert(k.clone(), v());
            }
        }
        result
    }

    /// Get all services as a map of key to service
    fn all(&self) -> Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>;
}

/// Get keyed services for key type K and service type S
/// # Examples
/// ```ignore
/// let locator = services::<MyKeyType, dyn MyServiceTrait + Send + Sync>();
/// let service = locator.one(MyKey1{value: 1}).unwrap();
/// ```
pub fn services<K, S>() -> impl KeyedServices<K, S>
where
    K: Eq + std::hash::Hash + 'static,
    S: ?Sized + Send + Sync + 'static,
    Container: KeyedServices<K, S>,
{
    return Container;
}

/// Macro to register keyed services for key type K and service type S
/// ALL IMPLEMENTATIONS MUST IMPLEMENT INJECTABLE
/// ALL KEYS MUST IMPLEMENT EQ AND HASH
/// # Examples
/// ```ignore
/// keyed_services!(
///     MyKeyType => MyServiceTrait: [MyKey1{value: 1}: MyServiceImpl1, MyKey2{value: 2}: MyServiceImpl2],
///     MyKeyTypeB => MyServiceTraitB: [MyKeyB1{value: 1}: MyServiceImplB1]
/// );
/// ```
#[macro_export]
macro_rules! keyed_services {
    ($($item:tt)*) => { $crate::__keyed_services_internal!($($item)*);
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! __keyed_services_internal {
    // Base case - empty
    () => {};

    // KeyType => ServiceTrait: [mappings], more items
    ($tkey:ty => $tservice:path: [$($key:expr => $impl:ty),* $(,)?], $($rest:tt)*) => {
        const _: () = {
            use std::collections::HashMap;
            use std::sync::{Arc, LazyLock};
            use $crate::inject::Injectable;
            use $crate::keyed_service::KeyedServices;

            static MAP: LazyLock<Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>>> = LazyLock::new(|| {
                let mut map = HashMap::new();
                $(
                    let key = Arc::new($key);
                    let factory: Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync> =
                        Arc::new(|| {
                            let instance = <$impl as Injectable>::inject();
                            Arc::new(instance) as Arc<dyn $tservice + Send + Sync>
                        });
                    map.insert(key, factory);
                )*
                Arc::new(map)
            });

            use $crate::resolver::{Container};
            impl KeyedServices<$tkey, dyn $tservice + Send + Sync> for Container {
                fn all(&self) -> Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>> {
                   MAP.clone()
                }
            }
        };
        $crate::__keyed_services_internal!($($rest)*);
    };

    // KeyType => ServiceTrait: [mappings], last item
    ($tkey:ty => $tservice:path: [$($key:expr => $impl:ty),* $(,)?]) => {
        const _: () = {
            use std::collections::HashMap;
            use std::sync::{Arc, LazyLock};
            use $crate::inject::Injectable;
            use $crate::keyed_service::KeyedServices;

            static MAP: LazyLock<Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>>> = LazyLock::new(|| {
                let mut map = HashMap::new();
                $(
                    let key = Arc::new($key);
                    let factory: Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync> =
                        Arc::new(|| {
                            let instance = <$impl as Injectable>::inject();
                            Arc::new(instance) as Arc<dyn $tservice + Send + Sync>
                        });
                    map.insert(key, factory);
                )*
                Arc::new(map)
            });

            use $crate::resolver::{Container};
            impl KeyedServices<$tkey, dyn $tservice + Send + Sync> for Container {
                fn all(&self) -> Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>> {
                   MAP.clone()
                }
            }
        };
    };
}

#[cfg(test)]
mod test {
    use crate::keyed_service::services;

    #[test]
    fn keyed_service_test() {
        use crate::inject::Injectable;
        use crate::keyed_service::KeyedServices;
        use crate::resolver::resolve;

        trait MyService: Send + Sync {
            fn get_value(&self) -> i32;
        }

        struct MyServiceImpl1;
        impl MyService for MyServiceImpl1 {
            fn get_value(&self) -> i32 {
                10
            }
        }
        impl Injectable for MyServiceImpl1 {
            fn inject() -> Self {
                MyServiceImpl1 {}
            }
        }

        struct MyServiceImpl2;
        impl MyService for MyServiceImpl2 {
            fn get_value(&self) -> i32 {
                20
            }
        }
        impl Injectable for MyServiceImpl2 {
            fn inject() -> Self {
                MyServiceImpl2 {}
            }
        }

        keyed_services!(
            i32 => MyService: [1 => MyServiceImpl1, 2 => MyServiceImpl2],
            String => MyService: ["A".to_string() => MyServiceImpl1]
        );

        let locator_int = services::<i32, dyn MyService + Send + Sync>();
        let service1 = locator_int.one(1).unwrap();
        let service2 = locator_int.one(2).unwrap();
        let locator_str = services::<String, dyn MyService + Send + Sync>();
        let service_a = locator_str.one("A".to_string()).unwrap();

        assert_eq!(service1.get_value(), 10);
        assert_eq!(service2.get_value(), 20);
        assert!(service_a.get_value() == 10);
    }
}