use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
pub use noema_macros::keyed_service;
use crate::core::{Container, Injectable};
pub trait ToService<K: Eq + std::hash::Hash, S: ?Sized + Send + Sync> {
fn to_service(self) -> Arc<S>;
}
pub trait KeyedServicesLock<K, S>
where
K: Eq + std::hash::Hash,
S: ?Sized + Send + Sync,
{
fn lock(&self) -> &OnceLock<Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>>;
fn set(&self, values: Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>) {
if self.lock().get().is_some() {
panic!(
"Values already set for this KeyedServicesLock<Key={},Value={}>",
std::any::type_name::<K>(),
std::any::type_name::<S>()
);
}
self.lock().set(values).ok();
}
fn values(&self) -> Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>> {
self.lock()
.get()
.expect(
format!(
"Values not set for this KeyedServicesLock<Key={},Value={}>",
std::any::type_name::<K>(),
std::any::type_name::<S>()
)
.as_str(),
)
.clone()
}
}
pub trait KeyedServices<K, S>
where
K: Eq + std::hash::Hash,
S: ?Sized + Send + Sync,
{
fn one(&self, key: K) -> Option<Arc<S>> {
Some(self.all().get(&key)?())
}
fn find(&self, predicate: &dyn Fn(&Arc<K>) -> bool) -> HashMap<Arc<K>, Arc<S>> {
let mut result = HashMap::new();
for (k, v) in self.all().iter() {
if predicate(&k) {
result.insert(k.clone(), v());
}
}
result
}
fn all(&self) -> Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>;
}
pub fn services<K, S>() -> impl KeyedServices<K, S>
where
K: Eq + std::hash::Hash + 'static,
S: ?Sized + Send + Sync + 'static,
Container: KeyedServices<K, S>,
{
return Container;
}
pub struct KeyedServicesBuilder<K, S>
where
K: Eq + std::hash::Hash,
S: ?Sized + Send + Sync,
{
map: HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>,
}
impl<K, S> KeyedServicesBuilder<K, S>
where
K: Eq + std::hash::Hash,
S: ?Sized + Send + Sync,
{
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn add<T: ToService<K, S> + Injectable<Container> + Send + Sync>(mut self, key: K) -> Self {
self.map.insert(
Arc::new(key),
Arc::new(|| T::inject(&Container).to_service()),
);
self
}
pub fn build(self)
where
Container: KeyedServicesLock<K, S>,
{
Container.set(Arc::new(self.map));
}
}
#[cfg(test)]
mod test {
#[test]
fn keyed_service_test() {
use crate::core::{Container, Injectable, arc_dyn, shared_dyn};
use crate::keyed_services::{
KeyedServices, KeyedServicesBuilder, KeyedServicesLock, ToService, keyed_service,
services,
};
use std::sync::Arc;
#[keyed_service(i32)]
#[keyed_service(String)]
trait MyService: Send + Sync + 'static {
fn get_value(&self) -> i32;
}
struct MyServiceImpl1;
impl MyService for MyServiceImpl1 {
fn get_value(&self) -> i32 {
10
}
}
impl Injectable<Container> for MyServiceImpl1 {
fn inject(_: &Container) -> Self {
MyServiceImpl1 {}
}
}
struct MyServiceImpl2;
impl MyService for MyServiceImpl2 {
fn get_value(&self) -> i32 {
20
}
}
impl Injectable<Container> for MyServiceImpl2 {
fn inject(_: &Container) -> Self {
MyServiceImpl2 {}
}
}
#[derive(Injectable)]
struct TestGetKeyedServices {
value: arc_dyn!(KeyedServices<i32, shared_dyn!(MyService)>),
}
KeyedServicesBuilder::<i32, dyn MyService + Send + Sync>::new()
.add::<MyServiceImpl1>(1)
.add::<MyServiceImpl2>(2)
.build();
KeyedServicesBuilder::<String, dyn MyService + Send + Sync>::new()
.add::<MyServiceImpl1>("A".to_string())
.build();
let test = TestGetKeyedServices::inject(&Container);
assert_eq!(test.value.one(1).unwrap().get_value(), 10);
let locator_int = services::<i32, shared_dyn!(MyService)>();
let service1 = locator_int.one(1).unwrap();
let service2 = locator_int.one(2).unwrap();
let locator_str = services::<String, dyn MyService + Send + Sync>();
let service_a = locator_str.one("A".to_string()).unwrap();
assert_eq!(service1.get_value(), 10);
assert_eq!(service2.get_value(), 20);
assert!(service_a.get_value() == 10);
}
}