noema 0.3.0

Noema IOC and DI framework for Rust
Documentation
pub use crate::configs;

/// Trait for configuration types
/// # Examples
/// ```ignore
/// #[derive(Default)]
/// struct MyConfig {
///    pub host: String,
///    pub port: u16,
/// }
/// impl Configuration for MyConfig {
///    fn config(&mut self) {
///        self.host = "localhost".to_string();
///        self.port = 8080;
///    }
/// }
/// ```
pub trait Configurable: Default + Send + Sync {
    /// Modify the configuration as needed
    /// Called once during singleton initialization
    /// Avoid resolve other dependencies here to prevent circular dependencies
    fn configure(&mut self);
}

/// Macro to define configuration singletons
/// ALL TYPES MUST IMPLEMENT Configuration TRAIT and Default TRAIT
///
/// Supports multiple configuration registrations:
/// - Type1, Type2, Type3
///
/// # Examples
/// ```ignore
/// configs!(
///     DatabaseConfig,
///     CacheConfig,
///     LoggerConfig
/// );
/// ```
#[macro_export]
macro_rules! configs {
    // Entry point - process all items
    ($($item:tt)*) => {
        $crate::__configs_internal!($($item)*);
    };
}

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

    // Type, more items
    ($t:ty, $($rest:tt)*) => {
        const _: () = {
            use $crate::configurations::Configurable;
            use $crate::core::{Resolver, Container};
            use std::sync::{Arc, LazyLock};

            static INSTANCE: LazyLock<Arc<$t>> = LazyLock::new(|| {
                let mut value = <$t>::default();
                value.configure();
                Arc::new(value)
            });

            impl Resolver<$t> for Container {
                fn resolve() -> Arc<$t> {
                    INSTANCE.clone()
                }
            }
        };
        $crate::__configs_internal!($($rest)*);
    };

    // Type, last item
    ($t:ty) => {
        const _: () = {
            use $crate::configurations::Configurable;
            use $crate::core::{Resolver, Container};
            use std::sync::{Arc, LazyLock};

            static INSTANCE: LazyLock<Arc<$t>> = LazyLock::new(|| {
                let mut value = <$t>::default();
                value.configure();
                Arc::new(value)
            });

            impl Resolver<$t> for Container {
                fn resolve() -> Arc<$t> {
                    INSTANCE.clone()
                }
            }
        };
    };
}

#[cfg(test)]
mod test {
    #[test]
    fn config_test() {
        #[derive(Default)]
        struct TestConfig {
            pub field1: String,
            pub field2: u32,
        }
        impl crate::configurations::Configurable for TestConfig {
            fn configure(&mut self) {
                self.field1 = "TestValue".to_string();
                self.field2 = 100;
            }
        }
        #[derive(Default)]
        struct AnotherConfig {
            pub flag: bool,
        }
        impl crate::configurations::Configurable for AnotherConfig {
            fn configure(&mut self) {
                self.flag = true;
            }
        }
        use crate::core::{Container, Resolver};
        configs![TestConfig, AnotherConfig];
        let config = <Container as Resolver<TestConfig>>::resolve();
        let another_config = <Container as Resolver<AnotherConfig>>::resolve();
        println!(
            "Config field1: {}, field2: {}",
            config.field1, config.field2
        );
        assert_eq!(config.field1, "TestValue");
        assert_eq!(config.field2, 100);
        assert!(another_config.flag);
    }
}