use std::fmt::Display;
use std::sync::Arc;
use crate::resolver::Container;
pub trait Collection {
type Value: Display + Eq + Default;
fn name(&self) -> String;
fn describe(&self) -> String {
format!("Collection with name: {}", self.name())
}
fn default_value(&self) -> Self::Value {
<Self::Value as Default>::default()
}
}
pub trait Item: Send + Sync {
type CollectionType: Collection + Default;
fn value(&self) -> <Self::CollectionType as Collection>::Value;
fn name(&self) -> String;
fn description(&self) -> String {
format!("Item {}: {}", self.name(), self.value())
}
fn to_tuple(&self) -> (String, <Self::CollectionType as Collection>::Value, String) {
(self.name(), self.value(), self.description())
}
}
pub trait ItemsCollection<C: Collection + Default + ?Sized + Send + Sync> {
fn all(&self) -> Arc<Vec<Arc<dyn Item<CollectionType = C> + Send + Sync>>>;
fn type_properties(&self) -> C {
<C as Default>::default()
}
fn find(
&self,
predicate: &dyn Fn(&Arc<dyn Item<CollectionType = C> + Send + Sync>) -> bool,
) -> Vec<Arc<dyn Item<CollectionType = C> + Send + Sync>> {
let mut result = Vec::new();
for element in self.all().iter() {
if predicate(element) {
result.push(element.clone());
}
}
result
}
fn by_value(&self, value: C::Value) -> Option<Arc<dyn Item<CollectionType = C> + Send + Sync>> {
self.find(&|e| e.value() == value).into_iter().next()
}
fn by_name(&self, name: &str) -> Option<Arc<dyn Item<CollectionType = C> + Send + Sync>> {
self.find(&|e| e.name() == name).into_iter().next()
}
fn to_tuple(&self) -> Vec<(String, C::Value, String)> {
let mut result = Vec::new();
for e in self.all().iter() {
result.push(e.to_tuple());
}
result
}
}
pub fn collection<C: Send + Sync>() -> impl ItemsCollection<C> + Send + Sync
where
C: Collection + Default + 'static,
Container: ItemsCollection<C> + Send + Sync,
{
return Container;
}
#[macro_export]
macro_rules! collections {
($($item:tt)*) => {
$crate::__items_collections_internal!($($item)*);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __items_collections_internal {
() => {};
($tcollection:ty: [$($impl:ty),* $(,)?], $($rest:tt)*) => {
const _: () = {
use std::sync::{Arc, LazyLock};
use $crate::items::{Item, ItemsCollection};
use $crate::resolver::Container;
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<$tcollection> for Container {
fn all(&self) -> Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>> {
_NOEMA_COLLECTION.clone()
}
}
};
$crate::__items_collections_internal!($($rest)*);
};
($tcollection:ty: [$($impl:ty),* $(,)?]) => {
const _: () = {
use std::sync::{Arc, LazyLock};
use $crate::items::{Item, ItemsCollection};
use $crate::resolver::Container;
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<$tcollection> for Container {
fn all(&self) -> Arc<Vec<Arc<dyn Item<CollectionType = $tcollection> + Send + Sync>>> {
_NOEMA_COLLECTION.clone()
}
}
};
};
}
#[cfg(test)]
mod test {
use crate::items::collection;
#[test]
fn test_items_collections() {
use crate::items::{Collection, Item, ItemsCollection};
#[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 = collection::<Steps>();
assert!(steps.type_properties().name() == "Steps".to_string());
}
}