use std::collections::HashMap;
use std::sync::Arc;
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>>>;
}
#[macro_export]
macro_rules! keyed_services {
($($item:tt)*) => { $crate::__keyed_services_internal!($($item)*);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __keyed_services_internal {
() => {};
($tkey:ty => $tservice:path: [$($key:expr => $impl:ty),* $(,)?], $($rest:tt)*) => {
const _: () = {
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
use $crate::inject::Injectable;
use $crate::keyed_service::KeyedServices;
static MAP: LazyLock<Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>>> = LazyLock::new(|| {
let mut map = HashMap::new();
$(
let key = Arc::new($key);
let factory: Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync> =
Arc::new(|| {
let instance = <$impl as Injectable>::inject();
Arc::new(instance) as Arc<dyn $tservice + Send + Sync>
});
map.insert(key, factory);
)*
Arc::new(map)
});
impl KeyedServices<$tkey, dyn $tservice + Send + Sync> for () {
fn all(&self) -> Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>> {
MAP.clone()
}
}
use $crate::resolver::Resolver;
impl Resolver<dyn KeyedServices<$tkey, dyn $tservice + Send + Sync> + Send + Sync> for () {
fn resolve(&self) -> Arc<dyn KeyedServices<$tkey, dyn $tservice + Send + Sync> + Send + Sync> {
Arc::new(())
}
}
};
$crate::__keyed_services_internal!($($rest)*);
};
($tkey:ty => $tservice:path: [$($key:expr => $impl:ty),* $(,)?]) => {
const _: () = {
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
use $crate::inject::Injectable;
use $crate::keyed_service::KeyedServices;
static MAP: LazyLock<Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>>> = LazyLock::new(|| {
let mut map = HashMap::new();
$(
let key = Arc::new($key);
let factory: Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync> =
Arc::new(|| {
let instance = <$impl as Injectable>::inject();
Arc::new(instance) as Arc<dyn $tservice + Send + Sync>
});
map.insert(key, factory);
)*
Arc::new(map)
});
impl KeyedServices<$tkey, dyn $tservice + Send + Sync> for () {
fn all(&self) -> Arc<HashMap<Arc<$tkey>, Arc<dyn Fn() -> Arc<dyn $tservice + Send + Sync> + Send + Sync>>> {
MAP.clone()
}
}
use $crate::resolver::Resolver;
impl Resolver<dyn KeyedServices<$tkey, dyn $tservice + Send + Sync> + Send + Sync> for () {
fn resolve(&self) -> Arc<dyn KeyedServices<$tkey, dyn $tservice + Send + Sync> + Send + Sync> {
Arc::new(())
}
}
};
};
}
#[cfg(test)]
mod test {
#[test]
fn keyed_service_test() {
use crate::inject::Injectable;
use crate::keyed_service::KeyedServices;
use crate::resolver::resolve;
trait MyService: Send + Sync {
fn get_value(&self) -> i32;
}
struct MyServiceImpl1;
impl MyService for MyServiceImpl1 {
fn get_value(&self) -> i32 {
10
}
}
impl Injectable for MyServiceImpl1 {
fn inject() -> Self {
MyServiceImpl1 {}
}
}
struct MyServiceImpl2;
impl MyService for MyServiceImpl2 {
fn get_value(&self) -> i32 {
20
}
}
impl Injectable for MyServiceImpl2 {
fn inject() -> Self {
MyServiceImpl2 {}
}
}
keyed_services!(
i32 => MyService: [1 => MyServiceImpl1, 2 => MyServiceImpl2],
String => MyService: ["A".to_string() => MyServiceImpl1]
);
let locator_int =
resolve::<dyn KeyedServices<i32, dyn MyService + Send + Sync> + Send + Sync>();
let service1 = locator_int.one(1).unwrap();
let service2 = locator_int.one(2).unwrap();
let locator_str =
resolve::<dyn KeyedServices<String, dyn MyService + Send + Sync> + 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);
}
}