#![allow(clippy::type_complexity)]
use std::{
any::{Any, TypeId},
collections::HashMap,
future::Future,
};
use crate::{AsyncInvoke, FromLocator, Invoke, LocatorError};
pub enum Provider {
Single(Box<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>),
Factory(Box<dyn Fn(&Locator) -> Box<dyn Any + Send + Sync> + Send + Sync>),
}
#[derive(Default)]
pub struct Locator(HashMap<TypeId, Provider>);
impl Locator {
#[inline]
pub fn unchecked_insert(&mut self, id: TypeId, provider: Provider) -> Option<Provider> {
self.0.insert(id, provider)
}
#[inline]
pub fn unchecked_get(&self, id: &TypeId) -> Option<&Provider> {
self.0.get(id)
}
}
impl Locator {
pub fn new() -> Self {
Default::default()
}
pub fn insert<T>(&mut self, value: T) -> Option<Provider>
where
T: Send + Sync + Clone + 'static,
{
let provider = Provider::Single(Box::new(move || Box::new(value.clone())));
self.unchecked_insert(TypeId::of::<T>(), provider)
}
pub fn insert_with<F, T>(&mut self, factory: F) -> Option<Provider>
where
F: Fn(&Self) -> T + 'static + Send + Sync,
T: Send + Sync + 'static,
{
let provider = Provider::Factory(Box::new(move |locator| {
let value = factory(locator);
Box::new(value)
}));
self.unchecked_insert(TypeId::of::<T>(), provider)
}
pub fn get<T>(&self) -> Option<T>
where
T: Send + Sync + 'static,
{
let provider = self.unchecked_get(&TypeId::of::<T>())?;
match provider {
Provider::Single(f) => {
let value = f();
value.downcast::<T>().map(|x| *x).ok()
}
Provider::Factory(f) => {
let value = f(self);
value.downcast::<T>().map(|x| *x).ok()
}
}
}
pub fn contains<T>(&self) -> bool
where
T: Send + Sync + 'static,
{
self.0.contains_key(&TypeId::of::<T>())
}
pub fn remove<T>(&mut self) -> Option<Provider>
where
T: Send + Sync + 'static,
{
self.0.remove(&TypeId::of::<T>())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn extend(&mut self, other: Self) {
self.0.extend(other.0);
}
pub fn invoke<F, Args>(&self, f: F) -> Result<F::Output, LocatorError>
where
F: Invoke<Args>,
Args: FromLocator,
{
let args = Args::from_locator(self)?;
Ok(Invoke::call(f, args))
}
pub async fn invoke_async<F, Fut, Args>(&self, f: F) -> Result<Fut::Output, LocatorError>
where
F: AsyncInvoke<Args, Fut = Fut>,
Fut: Future,
Args: FromLocator,
{
let args = Args::from_locator(self)?;
Ok(AsyncInvoke::call(f, args).await)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct MyStruct {
val: i32,
}
#[test]
fn test_insert_single() {
let mut locator = Locator::new();
assert!(locator.insert(MyStruct { val: 42 }).is_none());
assert_eq!(locator.get::<MyStruct>().unwrap().val, 42);
}
#[test]
fn test_insert_with_factory() {
let mut locator = Locator::new();
locator.insert_with::<_, MyStruct>(|_| MyStruct { val: 42 });
assert_eq!(locator.get::<MyStruct>().unwrap().val, 42);
}
#[test]
fn test_contains() {
let mut locator = Locator::new();
assert!(!locator.contains::<MyStruct>());
locator.insert(MyStruct { val: 42 });
assert!(locator.contains::<MyStruct>());
}
#[test]
fn test_remove() {
let mut locator = Locator::new();
locator.insert(MyStruct { val: 42 });
assert!(locator.contains::<MyStruct>());
assert!(locator.remove::<MyStruct>().is_some());
assert!(!locator.contains::<MyStruct>());
}
#[test]
fn test_len_and_is_empty() {
let mut locator = Locator::new();
assert!(locator.is_empty());
locator.insert(MyStruct { val: 42 });
assert_eq!(locator.len(), 1);
assert!(!locator.is_empty());
}
#[test]
fn test_extend() {
let mut locator1 = Locator::new();
let mut locator2 = Locator::new();
locator1.insert(MyStruct { val: 42 });
locator2.insert_with::<_, MyStruct>(|_| MyStruct { val: 10 });
locator1.extend(locator2);
assert_eq!(locator1.get::<MyStruct>().unwrap().val, 10);
}
#[test]
fn test_invoke() {
let mut locator = Locator::new();
locator.insert(MyStruct { val: 42 });
let result = locator.invoke(|my_struct: MyStruct| my_struct.val).unwrap();
assert_eq!(result, 42);
}
#[tokio::test]
async fn test_invoke_async() {
let mut locator = Locator::new();
locator.insert(MyStruct { val: 42 });
let result = locator
.invoke_async(|my_struct: MyStruct| async move { my_struct.val })
.await
.unwrap();
assert_eq!(result, 42);
}
}