noema 0.1.4

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

pub struct Container;

/// Resolver trait for resolving instances of type T
pub trait Resolver<T: ?Sized + Sync + Send> {
    /// Resolve an instance of type T
    fn resolve() -> Arc<T>;
}

/// Resolve an instance of type T
pub fn resolve<T: ?Sized + Sync + Send>() -> 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::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::Injectable;
            use std::sync::{Arc, LazyLock};
            static INSTANCE: LazyLock<Arc<dyn $trait + Send + Sync>> = LazyLock::new(|| {
                Arc::new(<$impl as Injectable>::inject()) 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::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::Injectable;
            use std::sync::{Arc, LazyLock};
            static INSTANCE: LazyLock<Arc<dyn $trait + Send + Sync>> = LazyLock::new(|| {
                Arc::new(<$impl as Injectable>::inject()) 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::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::Injectable;
            use std::sync::{Arc, LazyLock};
            static VALUE: LazyLock<Arc<$type>> = LazyLock::new(|| Arc::new(<$type as Injectable>::inject()));
            impl Resolver<$type> for Container {
                fn resolve() -> Arc<$type> {
                    VALUE.clone()
                }
            }
        };
        $crate::__components_internal!($($rest)*);
    };

    // Concrete type, last item
    ($type:ty) => {
        const _: () = {
            use $crate::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::Injectable;
            use std::sync::{Arc, LazyLock};
            static VALUE: LazyLock<Arc<$type>> = LazyLock::new(|| Arc::new(<$type as Injectable>::inject()));
            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
/// providers!(
///     RequestHandler,
///     Service: ServiceImpl,
///     Repository
/// );
/// ```
#[macro_export]
macro_rules! providers {
    // Entry point - process all items
    ($($item:tt)*) => {
        $crate::__providers_internal!($($item)*);
    };
}

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

    // Trait: Implementation, more items
    ($trait:path: $impl:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::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>::inject()) as Arc<dyn $trait + Send + Sync>
                }
            }
        };
        $crate::__providers_internal!($($rest)*);
    };

    // Trait: Implementation, last item
    ($trait:path: $impl:ty) => {
        const _: () = {
            use $crate::resolver::Resolver;
            use $crate::resolver::Container;
            use $crate::inject::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>::inject()) as Arc<dyn $trait + Send + Sync>
                }
            }
        };
    };

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

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

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

    #[test]
    fn components_test() {
        use crate::inject::Injectable;
        use crate::resolve;
        use crate::{components, providers};
        struct ServiceA {
            pub value: u32,
        }
        impl Injectable for ServiceA {
            fn inject() -> Self {
                ServiceA { value: 1 }
            }
        }
        struct ServiceB {
            pub value: u32,
        }
        impl Injectable for ServiceB {
            fn inject() -> 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 for MyTraitImpl {
            fn inject() -> Self {
                MyTraitImpl {}
            }
        }
        trait AnotherTrait: Send + Sync {
            fn get_another_value(&self) -> u32;
        }
        #[derive(Injectable)]
        struct AnotherTraitImpl {
            pub my_trait: std::sync::Arc<dyn MyTrait + Send + Sync>,
        }
        impl AnotherTrait for AnotherTraitImpl {
            fn get_another_value(&self) -> u32 {
                self.my_trait.get_value() + 1
            }
        }
        components!(
            MyTrait: MyTraitImpl,
            AnotherTrait: AnotherTraitImpl
        );
        providers!(ServiceA, ServiceB);
        let service_a_1 = resolve::<ServiceA>();
        let service_a_2 = resolve::<ServiceA>();
        let another_service = resolve::<dyn AnotherTrait + Send + Sync>();
        assert!(!Arc::ptr_eq(&service_a_1, &service_a_2)); // Same instance

        let my_trait = crate::resolver::resolve::<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);
    }
}