noema 0.2.2

Noema IOC and DI framework for Rust
Documentation
use crate::core::{Container, Resolver};
pub use crate::{components, factories};

pub fn service<T: ?Sized + Send + Sync + 'static>() -> std::sync::Arc<T>
where
    Container: Resolver<T>,
{
    <Container as Resolver<T>>::resolve()
}

/// Macro to define multiple singleton dependencies
///
/// Supports both concrete types and trait implementations:
/// - Concrete type: `Type`
/// - Trait implementation: `Trait: Implementation`
/// ALL IMPLEMENTATION TYPES MUST IMPLEMENT THE `Injectable` TRAIT
///
/// # Examples
/// ```ignore
/// components!(
///     DatabaseService,
///     Logger: FileLogger,
///     CacheService
/// );
/// ```
#[macro_export]
macro_rules! components {
    // Entry point - process all items
    ($($item:tt)*) => {
        $crate::__components_internal!($($item)*);
    };
}

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

    // Trait: Implementation, more items
    ($trait:path: $impl:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::{Arc, LazyLock};
            static INSTANCE: LazyLock<Arc<dyn $trait + Send + Sync>> = LazyLock::new(|| {
                Arc::new(<$impl as Injectable<Container>>::inject(&Container)) as Arc<dyn $trait + Send + Sync>
            });
            impl Resolver<dyn $trait + Send + Sync> for Container {
                fn resolve() -> Arc<dyn $trait + Send + Sync> {
                    INSTANCE.clone()
                }
            }
        };
        $crate::__components_internal!($($rest)*);
    };

    // Trait: Implementation, last item
    ($trait:path: $impl:ty) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::{Arc, LazyLock};
            static INSTANCE: LazyLock<Arc<dyn $trait + Send + Sync>> = LazyLock::new(|| {
                Arc::new(<$impl as Injectable<Container>>::inject(&Container)) as Arc<dyn $trait + Send + Sync>
            });
            impl Resolver<dyn $trait + Send + Sync> for Container {
                fn resolve() -> Arc<dyn $trait + Send + Sync> {
                    INSTANCE.clone()
                }
            }
        };
    };

    // Concrete type, more items
    ($type:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::{Arc, LazyLock};
            static VALUE: LazyLock<Arc<$type>> = LazyLock::new(|| Arc::new(<$type as Injectable<Container>>::inject(&Container)));
            impl Resolver<$type> for Container {
                fn resolve() -> Arc<$type> {
                    VALUE.clone()
                }
            }
        };
        $crate::__components_internal!($($rest)*);
    };

    // Concrete type, last item
    ($type:ty) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::{Arc, LazyLock};
            static VALUE: LazyLock<Arc<$type>> = LazyLock::new(|| Arc::new(<$type as Injectable<Container>>::inject(&Container)));
            impl Resolver<$type> for Container {
                fn resolve() -> Arc<$type> {
                    VALUE.clone()
                }
            }
        };
    };
}

/// Macro to define multiple factory dependencies
///
/// Supports both concrete types and trait implementations:
/// - Concrete type: `Type`
/// - Trait implementation: `Trait: Implementation`
/// ALL IMPLEMENTATION TYPES MUST IMPLEMENT THE `Injectable` TRAIT
///
/// # Examples
/// ```ignore
/// factories!(
///     RequestHandler,
///     Service: ServiceImpl,
///     Repository
/// );
/// ```
#[macro_export]
macro_rules! factories {
    // Entry point - process all items
    ($($item:tt)*) => {
        $crate::__factories_internal!($($item)*);
    };
}

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

    // Trait: Implementation, more items
    ($trait:path: $impl:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::Arc;
            impl Resolver<dyn $trait + Send + Sync> for Container {
                fn resolve() -> Arc<dyn $trait + Send + Sync> {
                    Arc::new(<$impl as Injectable<Container>>::inject(&Container)) as Arc<dyn $trait + Send + Sync>
                }
            }
        };
        $crate::__factories_internal!($($rest)*);
    };

    // Trait: Implementation, last item
    ($trait:path: $impl:ty) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::Arc;
            impl Resolver<dyn $trait + Send + Sync> for Container {
                fn resolve(&self) -> Arc<dyn $trait + Send + Sync> {
                    Arc::new(<$impl as Injectable<Container>::inject(&Container)) as Arc<dyn $trait + Send + Sync>
                }
            }
        };
    };

    // Concrete type, more items
    ($type:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::Arc;
            impl Resolver<$type> for Container {
                fn resolve() -> Arc<$type> {
                    Arc::new(<$type as Injectable<Container>>::inject(&Container))
                }
            }
        };
        $crate::__factories_internal!($($rest)*);
    };

    // Concrete type, last item
    ($type:ty) => {
        const _: () = {
            use $crate::core::{Resolver, Container, Injectable};
            use std::sync::Arc;
            impl Resolver<$type> for Container {
                fn resolve() -> Arc<$type> {
                    Arc::new(<$type as Injectable<Container>>::inject(&Container))
                }
            }
        };
    };
}

#[cfg(test)]
mod test {
    use noema_macros::Injectable;
    use std::sync::Arc;

    use crate::services::service;

    #[test]
    fn components_test() {
        use crate::core::{Container, Injectable, Resolver, arc_dyn};
        use crate::services::{components, factories};
        struct ServiceA {
            pub value: u32,
        }
        impl Injectable<Container> for ServiceA {
            fn inject(_: &Container) -> Self {
                ServiceA { value: 1 }
            }
        }
        struct ServiceB {
            pub value: u32,
        }
        impl Injectable<Container> for ServiceB {
            fn inject(_: &Container) -> Self {
                ServiceB { value: 2 }
            }
        }
        trait MyTrait: Send + Sync {
            fn get_value(&self) -> u32;
        }
        struct MyTraitImpl;
        impl MyTrait for MyTraitImpl {
            fn get_value(&self) -> u32 {
                42
            }
        }
        impl Injectable<Container> for MyTraitImpl {
            fn inject(_: &Container) -> Self {
                MyTraitImpl {}
            }
        }
        trait AnotherTrait: Send + Sync {
            fn get_another_value(&self) -> u32;
        }
        #[derive(Injectable)]
        struct AnotherTraitImpl {
            pub my_trait: arc_dyn!(MyTrait),
        }
        impl AnotherTrait for AnotherTraitImpl {
            fn get_another_value(&self) -> u32 {
                self.my_trait.get_value() + 1
            }
        }
        components!(
            MyTrait: MyTraitImpl,
            AnotherTrait: AnotherTraitImpl
        );
        factories!(ServiceA, ServiceB);
        let service_a_1 = service::<ServiceA>();
        let service_a_2 = service::<ServiceA>();
        let another_service = service::<dyn AnotherTrait + Send + Sync>();
        assert!(!Arc::ptr_eq(&service_a_1, &service_a_2)); // Same instance

        let my_trait = service::<dyn MyTrait + Send + Sync>();
        println!("My another value: {}", another_service.get_another_value());
        assert_eq!(my_trait.get_value(), 42);
        assert_eq!(another_service.get_another_value(), 43);
    }
}