use std::{
any::{Any, TypeId},
collections::{HashMap, HashSet},
sync::{Arc, RwLock},
};
use crate::{Error, ErrorKind};
#[derive(Clone)]
pub struct Container<'a>(Arc<RwLock<ContainerImpl<'a>>>);
impl<'a> Container<'a> {
pub fn new() -> Self {
Self(Arc::new(RwLock::new(ContainerImpl::default())))
}
fn register_service_internal(&self, boxed_service: BoxedService) -> Result<(), ErrorKind> {
let mut impl_obj = self.0.write()?;
if impl_obj.services.contains_key(&boxed_service.type_id) {
return Err(ErrorKind::Duplicated);
}
impl_obj
.services
.insert(boxed_service.type_id.clone(), boxed_service);
Ok(())
}
fn register_constructor_internal(
&self,
boxed_constructor: BoxedConstructor<'a>,
) -> Result<(), ErrorKind> {
let mut write = self.0.write()?;
write
.constructors
.insert(boxed_constructor.type_id, Arc::new(boxed_constructor));
Ok(())
}
fn construct_internal<T: Clone + 'static>(&self) -> Result<T, ErrorKind> {
let type_id = TypeId::of::<T>();
let constructor = {
let impl_ref = self.0.read()?;
if impl_ref.pending_construction.contains(&type_id) {
return Err(ErrorKind::CircularReference);
}
impl_ref
.constructors
.get(&type_id)
.ok_or(ErrorKind::NotFound)?
.clone()
};
self.0.write()?.pending_construction.insert(type_id.clone());
let construction = constructor.construct::<T>(self.clone());
self.0.write()?.pending_construction.remove(&type_id);
match construction {
None => Err(ErrorKind::FailDowncast),
Some(v) => Ok(v),
}
}
pub fn register_service<T: Clone + 'static>(&self, value: T) {
self.register_service_internal(BoxedService::from(value))
.unwrap()
}
pub fn try_register_service<T: Clone + 'static>(&self, value: T) -> Result<(), Error<T>> {
Ok(self.register_service_internal(BoxedService::from(value))?)
}
pub fn register_constructor<T: Clone + 'static>(&self, value: impl Fn(Container) -> T + 'a) {
self.register_constructor_internal(BoxedConstructor::from(value))
.unwrap()
}
pub fn try_register_constructor<T: Clone + 'static>(
&self,
value: impl Fn(Container) -> T + 'a,
) -> Result<(), Error<T>> {
Ok(self.register_constructor_internal(BoxedConstructor::from(value))?)
}
pub fn get<T: Clone + 'static>(&self) -> T {
self.try_get().unwrap()
}
pub fn try_get<T: Clone + 'static>(&self) -> Result<T, Error<T>> {
let type_id = TypeId::of::<T>();
{
let impl_obj = self.0.read()?;
if let Some(s) = impl_obj.services.get(&type_id) {
return match s.get_cloned() {
Some(v) => Ok(v),
None => Err(ErrorKind::FailDowncast.into()),
};
}
if !impl_obj.constructors.contains_key(&type_id) {
return Err(ErrorKind::NotFound.into());
}
}
let new_value = self.try_construct::<T>()?;
self.register_service_internal(BoxedService::from(new_value.clone()))?;
Ok(new_value)
}
pub fn construct<T: Clone + 'static>(&self) -> T {
self.construct_internal().unwrap()
}
pub fn try_construct<T: Clone + 'static>(&self) -> Result<T, Error<T>> {
Ok(self.construct_internal()?)
}
pub fn remove_service<T: Clone + 'static>(&self) -> Result<T, Error<T>> {
Ok(self
.0
.write()?
.services
.remove(&TypeId::of::<T>())
.ok_or(ErrorKind::NotFound)?
.get_cloned::<T>()
.ok_or(ErrorKind::FailDowncast)?)
}
pub fn remove_constructor<T: Clone + 'static>(&self) -> Result<(), Error<T>> {
self.0.write()?.constructors.remove(&TypeId::of::<T>());
Ok(())
}
pub fn into_static(self) -> Container<'static> {
let mut services = HashMap::new();
std::mem::swap(&mut self.0.write().unwrap().services, &mut services);
Container::<'static>(Arc::new(RwLock::new(ContainerImpl {
services,
..Default::default()
})))
}
}
#[derive(Default)]
struct ContainerImpl<'a> {
pub constructors: HashMap<TypeId, Arc<BoxedConstructor<'a>>>,
pub services: HashMap<TypeId, BoxedService>,
pub pending_construction: HashSet<TypeId>,
}
struct BoxedService {
pub type_id: TypeId,
pub value: Box<dyn Any>,
}
impl BoxedService {
fn get_cloned<T: Clone + 'static>(&self) -> Option<T> {
self.value.downcast_ref::<T>().cloned()
}
}
impl<T: Clone + 'static> From<T> for BoxedService {
fn from(value: T) -> Self {
Self {
type_id: TypeId::of::<T>(),
value: Box::new(value),
}
}
}
struct BoxedConstructor<'a> {
pub type_id: TypeId,
pub value: Box<dyn Fn(Container) -> Box<dyn Any> + 'a>,
}
impl<'a> BoxedConstructor<'a> {
fn construct<T: Clone + 'static>(&self, container: Container) -> Option<T> {
let value = (self.value)(container).downcast::<T>();
match value {
Err(_) => None,
Ok(v) => Some(*v),
}
}
}
impl<'a, T: Clone + 'static, F: Fn(Container) -> T + 'a> From<F> for BoxedConstructor<'a> {
fn from(value: F) -> Self {
Self {
type_id: TypeId::of::<T>(),
value: Box::new(move |c| Box::new(value(c))),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::Container;
#[test]
fn basic_register() {
let c = Container::new();
c.register_service("A".to_string());
c.register_service(123 as u64);
assert_eq!(c.get::<String>(), "A");
assert_eq!(c.get::<u64>(), 123);
}
#[test]
fn basic_constructor() {
let c = Container::new();
c.register_constructor(|_| "A".to_string());
c.register_constructor(|_| 123 as u64);
assert_eq!(c.get::<String>(), "A");
assert_eq!(c.get::<u64>(), 123);
}
#[allow(dead_code)]
struct A {
b: Arc<B>,
d: Arc<D>,
}
#[allow(dead_code)]
struct B {
c: Arc<C>,
}
struct C;
#[derive(Clone)]
struct D;
#[test]
fn complex() {
let c = Container::new();
c.register_constructor(|container| {
Arc::new(A {
b: container.get(),
d: container.get(),
})
});
c.register_constructor(|container| Arc::new(B { c: container.get() }));
c.register_constructor(|_| Arc::new(C));
c.register_constructor(|_| Arc::new(D));
_ = c.get::<Arc<A>>();
}
#[test]
fn constructor_with_lifetime() {
let outside_string = "A".to_string();
let outside_d = D;
let c = Container::new();
c.register_constructor(|_| outside_string.clone());
c.register_constructor(|_| outside_d.clone());
assert_eq!(c.get::<String>(), "A");
}
#[derive(Debug)]
struct RefA {
pub _b: Arc<RefB>,
}
#[derive(Debug)]
struct RefB {
pub _a: Arc<RefA>,
}
#[test]
#[should_panic]
fn circular_reference() {
let c = Container::new();
c.register_constructor(|c| Arc::new(RefA { _b: c.get() }));
c.register_constructor(|c| Arc::new(RefB { _a: c.get() }));
_ = c.get::<Arc<RefA>>();
}
}