noema 0.1.0

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

/// Trait for generate a Collecion Types
///
/// # Examples
/// ```ignore
/// #[derive(Default)]
/// struct Steps;
/// impl Collection for Steps {
///     type Value = i32;
///
///     fn name(&self) -> String {
///         "Steps".to_string()
///     }
/// }
/// ```
pub trait Collection {
    /// Define the valur for all items
    type Value: Display + Eq + Default;

    /// Define a name for collection
    fn name(&self) -> String;
    /// Define what is the collection
    fn describe(&self) -> String {
        format!("Collection with name: {}", self.name())
    }
    /// default value of collection (by defect is default value of Value)
    fn default_value(&self) -> Self::Value {
        <Self::Value as Default>::default()
    }
}

/// Item trait for items collections
///
/// # Examples
/// ```ignore
/// #[derive(Default)]
/// struct Steps;
/// impl Collection for Steps {
///     type Value = i32;
///
///     fn name(&self) -> String {
///         "Steps".to_string()
///     }
/// }
/// #[derive(Default)]
/// struct StepOne;
/// impl Item for StepOne {
///     type CollectionType = Steps;
///     fn name(&self) -> String {
///         "Step One".to_string()
///     }
///     fn value(&self) -> <Self::CollectionType as Collection>::Value {
///         12
///     }
/// }
/// collections!(Steps: [StepOne]);
/// let steps = crate::Resolver::resolve::<
///     dyn ItemsCollection<dyn Item<CollectionType = Steps> + Send + Sync> + Send + Sync,
/// >();
/// assert!(steps.collection().name() == "Steps".to_string());
/// ```
pub trait Item: Send + Sync {
    type CollectionType: Collection + Default;
    /// Get value of the flag
    fn value(&self) -> <Self::CollectionType as Collection>::Value;

    /// Get name of the flag
    fn name(&self) -> String;

    /// Get description of the flag
    fn description(&self) -> String {
        format!("Item {}: {}", self.name(), self.value())
    }

    /// Convert to tuple when (name, value, description)
    fn to_tuple(&self) -> (String, <Self::CollectionType as Collection>::Value, String) {
        (self.name(), self.value(), self.description())
    }
}

/// Collection for Items of type I
pub trait ItemsCollection<I: Item + ?Sized + Send + Sync> {
    /// Get all flag elements
    fn all(&self) -> Arc<Vec<Arc<I>>>;

    fn collection(&self) -> I::CollectionType {
        <I::CollectionType as Default>::default()
    }

    // Get elements matching a predicate
    // # Examples
    // ```
    // let name = "Hola".to_string();
    // let items_collection.find(&|e| e.name() == &name)
    // ```
    fn find(&self, predicate: &dyn Fn(&Arc<I>) -> bool) -> Vec<Arc<I>> {
        let mut result = Vec::new();
        for element in self.all().iter() {
            if predicate(element) {
                result.push(element.clone());
            }
        }
        result
    }

    /// Get one enumeration element by value
    fn by_value(&self, value: <I::CollectionType as Collection>::Value) -> Option<Arc<I>> {
        self.find(&|e| e.value() == value).into_iter().next()
    }

    /// Get one enumeration element by name
    fn by_name(&self, name: &str) -> Option<Arc<I>> {
        self.find(&|e| e.name() == name).into_iter().next()
    }

    /// Convert to tuple when (name, value, description)
    fn to_tuple(&self) -> Vec<(String, <I::CollectionType as Collection>::Value, String)> {
        let mut result = Vec::new();
        for e in self.all().iter() {
            result.push(e.to_tuple());
        }
        result
    }
}

/// Macro to define items collections for item types
/// WHERE
///     Each CollectionType must Implement Collection
///     Each Item must Implement Item set CollectionType how same Collection
///
/// Supports multiple flags collections:
/// - CollectionType: [Impl1, Impl2, Impl3]
///
/// # Examples
/// ```ignore
/// collections!(
///     MyCollectionTypeA: [MyItemImplA1, MyItemImplA2, MyItemImplA3],
///     MyCollectionTypeB: [MyItemImplB1, MyItemsImplB2]
/// );
/// ```
#[macro_export]
macro_rules! collections {
    // Entry point - process all items
    ($($item:tt)*) => {
        $crate::__items_collections_internal!($($item)*);
    };
}

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

    // Collection Type: [Items implementations], more items
    ($tcollection:ty: [$($impl:ty),* $(,)?], $($rest:tt)*) => {
        const _: () = {
            use std::sync::{Arc, LazyLock};
            use $crate::items::{Item, ItemsCollection};
            use $crate::resolver::Resolver;

            static _NOEMA_COLLECTION: LazyLock<Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>>> =
                LazyLock::new(|| {
                    let mut elements: Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>> = Vec::new();
                    $(
                        elements.push(Arc::new(<$impl>::default()) as Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>);
                    )*
                    Arc::new(elements)
                });

            impl ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> for () {
                fn all(&self) -> Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>> {
                    _NOEMA_COLLECTION.clone()
                }
            }

            impl Resolver<dyn ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> + Send + Sync> for () {
                fn resolve(&self) -> Arc<dyn ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> + Send + Sync> {
                    Arc::new(())
                }
            }
        };
        $crate::__items_collections_internal!($($rest)*);
    };

    // Collection Type: [Items implementations], last item
    ($tcollection:ty: [$($impl:ty),* $(,)?]) => {
        const _: () = {
            use std::sync::{Arc, LazyLock};
            use $crate::items::{Item, ItemsCollection};
            use $crate::resolver::Resolver;

            static _NOEMA_COLLECTION: LazyLock<Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>>> =
                LazyLock::new(|| {
                    let mut elements: Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>> = Vec::new();
                    $(
                        elements.push(Arc::new(<$impl>::default()) as Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>);
                    )*
                    Arc::new(elements)
                });

            impl ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> for () {
                fn all(&self) -> Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>> {
                    _NOEMA_COLLECTION.clone()
                }
            }

            impl Resolver<dyn ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> + Send + Sync> for () {
                fn resolve(&self) -> Arc<dyn ItemsCollection<dyn Item<CollectionType = $tcollection> + Send + Sync> + Send + Sync> {
                    Arc::new(())
                }
            }
        };
    };
}

#[cfg(test)]
mod test {
    #[test]
    fn test_items_collections() {
        use crate::items::{Collection, Item, ItemsCollection};
        use crate::resolver::resolve;
        #[derive(Default)]
        struct Steps;
        impl Collection for Steps {
            type Value = i32;

            fn name(&self) -> String {
                "Steps".to_string()
            }
        }
        #[derive(Default)]
        struct StepOne;
        impl Item for StepOne {
            type CollectionType = Steps;
            fn name(&self) -> String {
                "Step One".to_string()
            }
            fn value(&self) -> <Self::CollectionType as Collection>::Value {
                12
            }
        }
        collections!(Steps: [StepOne]);
        let steps = resolve::<
            dyn ItemsCollection<dyn Item<CollectionType = Steps> + Send + Sync> + Send + Sync,
        >();
        assert!(steps.collection().name() == "Steps".to_string());
    }
}