From 5b0c6a52022e67a2d9cee251b3d08b9cb2b5f6cb Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 9 Oct 2022 12:05:24 +0200 Subject: refactor!: reorganize DI containers BREAKING CHANGE: DIContainer, AsyncDIContainer & the binding structs have been relocated --- examples/async/bootstrap.rs | 3 +- macros/src/factory/build_declare_interfaces.rs | 12 +- macros/src/injectable/implementation.rs | 2 +- src/async_di_container.rs | 1641 -------------------- src/di_container.rs | 1267 --------------- src/di_container/asynchronous/binding/builder.rs | 653 ++++++++ src/di_container/asynchronous/binding/mod.rs | 5 + .../asynchronous/binding/scope_configurator.rs | 76 + .../asynchronous/binding/when_configurator.rs | 54 + src/di_container/asynchronous/mod.rs | 767 +++++++++ src/di_container/binding_map.rs | 91 ++ src/di_container/blocking/binding/builder.rs | 450 ++++++ src/di_container/blocking/binding/mod.rs | 5 + .../blocking/binding/scope_configurator.rs | 72 + .../blocking/binding/when_configurator.rs | 55 + src/di_container/blocking/mod.rs | 599 +++++++ src/di_container/mod.rs | 9 + src/di_container_binding_map.rs | 91 -- src/interfaces/async_injectable.rs | 2 +- src/lib.rs | 12 +- src/provider/async.rs | 2 +- src/test_utils.rs | 253 +++ 22 files changed, 3104 insertions(+), 3017 deletions(-) delete mode 100644 src/async_di_container.rs delete mode 100644 src/di_container.rs create mode 100644 src/di_container/asynchronous/binding/builder.rs create mode 100644 src/di_container/asynchronous/binding/mod.rs create mode 100644 src/di_container/asynchronous/binding/scope_configurator.rs create mode 100644 src/di_container/asynchronous/binding/when_configurator.rs create mode 100644 src/di_container/asynchronous/mod.rs create mode 100644 src/di_container/binding_map.rs create mode 100644 src/di_container/blocking/binding/builder.rs create mode 100644 src/di_container/blocking/binding/mod.rs create mode 100644 src/di_container/blocking/binding/scope_configurator.rs create mode 100644 src/di_container/blocking/binding/when_configurator.rs create mode 100644 src/di_container/blocking/mod.rs create mode 100644 src/di_container/mod.rs delete mode 100644 src/di_container_binding_map.rs create mode 100644 src/test_utils.rs diff --git a/examples/async/bootstrap.rs b/examples/async/bootstrap.rs index dd2febe..9b495c2 100644 --- a/examples/async/bootstrap.rs +++ b/examples/async/bootstrap.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use anyhow::Result; -use syrette::async_di_container::AsyncDIContainer; -use syrette::declare_default_factory; use syrette::ptr::TransientPtr; +use syrette::{declare_default_factory, AsyncDIContainer}; use crate::animals::cat::Cat; use crate::animals::dog::Dog; diff --git a/macros/src/factory/build_declare_interfaces.rs b/macros/src/factory/build_declare_interfaces.rs index 61e162f..6ab135e 100644 --- a/macros/src/factory/build_declare_interfaces.rs +++ b/macros/src/factory/build_declare_interfaces.rs @@ -12,10 +12,10 @@ pub fn build_declare_factory_interfaces( quote! { syrette::declare_interface!( syrette::castable_factory::threadsafe::ThreadsafeCastableFactory< - (std::sync::Arc,), + (std::sync::Arc,), #factory_interface > -> syrette::interfaces::factory::IThreadsafeFactory< - (std::sync::Arc,), + (std::sync::Arc,), #factory_interface >, async = true @@ -23,7 +23,7 @@ pub fn build_declare_factory_interfaces( syrette::declare_interface!( syrette::castable_factory::threadsafe::ThreadsafeCastableFactory< - (std::sync::Arc,), + (std::sync::Arc,), #factory_interface > -> syrette::interfaces::any_factory::AnyThreadsafeFactory, async = true @@ -33,17 +33,17 @@ pub fn build_declare_factory_interfaces( quote! { syrette::declare_interface!( syrette::castable_factory::blocking::CastableFactory< - (std::rc::Rc,), + (std::rc::Rc,), #factory_interface > -> syrette::interfaces::factory::IFactory< - (std::rc::Rc,), + (std::rc::Rc,), #factory_interface > ); syrette::declare_interface!( syrette::castable_factory::blocking::CastableFactory< - (std::rc::Rc,), + (std::rc::Rc,), #factory_interface > -> syrette::interfaces::any_factory::AnyFactory ); diff --git a/macros/src/injectable/implementation.rs b/macros/src/injectable/implementation.rs index 82cbe16..c907f32 100644 --- a/macros/src/injectable/implementation.rs +++ b/macros/src/injectable/implementation.rs @@ -91,7 +91,7 @@ impl InjectableImpl { fn resolve<'di_container, 'fut>( #di_container_var: &'di_container std::sync::Arc< - syrette::async_di_container::AsyncDIContainer + syrette::AsyncDIContainer >, mut #dependency_history_var: Vec<&'static str>, ) -> syrette::future::BoxFuture< diff --git a/src/async_di_container.rs b/src/async_di_container.rs deleted file mode 100644 index 3943dae..0000000 --- a/src/async_di_container.rs +++ /dev/null @@ -1,1641 +0,0 @@ -//! Asynchronous dependency injection container. -//! -//! # Examples -//! ``` -//! use std::collections::HashMap; -//! use std::error::Error; -//! -//! use syrette::{injectable, AsyncDIContainer}; -//! -//! trait IDatabaseService: Send + Sync -//! { -//! fn get_all_records(&self, table_name: String) -> HashMap; -//! } -//! -//! struct DatabaseService {} -//! -//! #[injectable(IDatabaseService, async = true)] -//! impl DatabaseService -//! { -//! fn new() -> Self -//! { -//! Self {} -//! } -//! } -//! -//! impl IDatabaseService for DatabaseService -//! { -//! fn get_all_records(&self, table_name: String) -> HashMap -//! { -//! // Do stuff here -//! HashMap::::new() -//! } -//! } -//! -//! #[tokio::main] -//! async fn main() -> Result<(), Box> -//! { -//! let mut di_container = AsyncDIContainer::new(); -//! -//! di_container -//! .bind::() -//! .to::() -//! .await?; -//! -//! let database_service = di_container -//! .get::() -//! .await? -//! .transient()?; -//! -//! Ok(()) -//! } -//! ``` -use std::any::type_name; -use std::marker::PhantomData; -use std::sync::Arc; - -use tokio::sync::Mutex; - -#[cfg(feature = "factory")] -use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; -use crate::di_container_binding_map::DIContainerBindingMap; -use crate::errors::async_di_container::{ - AsyncBindingBuilderError, - AsyncBindingScopeConfiguratorError, - AsyncBindingWhenConfiguratorError, - AsyncDIContainerError, -}; -use crate::future::BoxFuture; -use crate::interfaces::async_injectable::AsyncInjectable; -use crate::libs::intertrait::cast::error::CastError; -use crate::libs::intertrait::cast::{CastArc, CastBox}; -use crate::provider::r#async::{ - AsyncProvidable, - AsyncSingletonProvider, - AsyncTransientTypeProvider, - IAsyncProvider, -}; -use crate::ptr::{SomeThreadsafePtr, ThreadsafeSingletonPtr, TransientPtr}; - -/// Alias for a threadsafe boxed function. -#[cfg(feature = "factory")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] -pub type BoxFn = Box<(dyn Fn + Send + Sync)>; - -/// When configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`]. -pub struct AsyncBindingWhenConfigurator -where - Interface: 'static + ?Sized + Send + Sync, -{ - di_container: Arc, - interface_phantom: PhantomData, -} - -impl AsyncBindingWhenConfigurator -where - Interface: 'static + ?Sized + Send + Sync, -{ - fn new(di_container: Arc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - } - } - - /// Configures the binding to have a name. - /// - /// # Errors - /// Will return Err if no binding for the interface already exists. - pub async fn when_named( - &self, - name: &'static str, - ) -> Result<(), AsyncBindingWhenConfiguratorError> - { - let mut bindings_lock = self.di_container.bindings.lock().await; - - let binding = bindings_lock.remove::(None).map_or_else( - || { - Err(AsyncBindingWhenConfiguratorError::BindingNotFound( - type_name::(), - )) - }, - Ok, - )?; - - bindings_lock.set::(Some(name), binding); - - Ok(()) - } -} - -/// Scope configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`]. -pub struct AsyncBindingScopeConfigurator -where - Interface: 'static + ?Sized + Send + Sync, - Implementation: AsyncInjectable, -{ - di_container: Arc, - interface_phantom: PhantomData, - implementation_phantom: PhantomData, -} - -impl AsyncBindingScopeConfigurator -where - Interface: 'static + ?Sized + Send + Sync, - Implementation: AsyncInjectable, -{ - fn new(di_container: Arc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - implementation_phantom: PhantomData, - } - } - - /// Configures the binding to be in a transient scope. - /// - /// This is the default. - pub async fn in_transient_scope(&self) -> AsyncBindingWhenConfigurator - { - let mut bindings_lock = self.di_container.bindings.lock().await; - - bindings_lock.set::( - None, - Box::new(AsyncTransientTypeProvider::::new()), - ); - - AsyncBindingWhenConfigurator::new(self.di_container.clone()) - } - - /// Configures the binding to be in a singleton scope. - /// - /// # Errors - /// Will return Err if resolving the implementation fails. - pub async fn in_singleton_scope( - &self, - ) -> Result, AsyncBindingScopeConfiguratorError> - { - let singleton: ThreadsafeSingletonPtr = - ThreadsafeSingletonPtr::from( - Implementation::resolve(&self.di_container, Vec::new()) - .await - .map_err( - AsyncBindingScopeConfiguratorError::SingletonResolveFailed, - )?, - ); - - let mut bindings_lock = self.di_container.bindings.lock().await; - - bindings_lock - .set::(None, Box::new(AsyncSingletonProvider::new(singleton))); - - Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) - } -} - -/// Binding builder for type `Interface` inside a [`AsyncDIContainer`]. -pub struct AsyncBindingBuilder -where - Interface: 'static + ?Sized + Send + Sync, -{ - di_container: Arc, - interface_phantom: PhantomData, -} - -impl AsyncBindingBuilder -where - Interface: 'static + ?Sized + Send + Sync, -{ - fn new(di_container: Arc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - } - } - - /// Creates a binding of type `Interface` to type `Implementation` inside of the - /// associated [`AsyncDIContainer`]. - /// - /// The scope of the binding is transient. But that can be changed by using the - /// returned [`AsyncBindingScopeConfigurator`] - /// - /// # Errors - /// Will return Err if the associated [`AsyncDIContainer`] already have a binding for - /// the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::{AsyncDIContainer, injectable}; - /// # - /// # trait Foo: Send + Sync {} - /// # - /// # struct Bar {} - /// # - /// # #[injectable(Foo, async = true)] - /// # impl Bar { - /// # fn new() -> Self - /// # { - /// # Self {} - /// # } - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = AsyncDIContainer::new(); - /// # - /// di_container.bind::().to::().await?; - /// # - /// # Ok(()) - /// # } - /// ``` - pub async fn to( - &self, - ) -> Result< - AsyncBindingScopeConfigurator, - AsyncBindingBuilderError, - > - where - Implementation: AsyncInjectable, - { - { - let bindings_lock = self.di_container.bindings.lock().await; - - if bindings_lock.has::(None) { - return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - } - - let binding_scope_configurator = - AsyncBindingScopeConfigurator::new(self.di_container.clone()); - - binding_scope_configurator.in_transient_scope().await; - - Ok(binding_scope_configurator) - } - - /// Creates a binding of factory type `Interface` to a factory inside of the - /// associated [`AsyncDIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`AsyncDIContainer`] already have a binding - /// for the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::{AsyncDIContainer, factory}; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait Foo: Send + Sync {} - /// # - /// # struct Bar - /// # { - /// # num: i32, - /// # some_str: String - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # #[factory(threadsafe = true)] - /// # type FooFactory = dyn Fn(i32, String) -> dyn Foo; - /// # - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = AsyncDIContainer::new(); - /// # - /// di_container - /// .bind::() - /// .to_factory(&|_| { - /// Box::new(|num, some_str| { - /// let bar = TransientPtr::new(Bar { num, some_str }); - /// - /// bar as TransientPtr - /// }) - /// }) - /// .await?; - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub async fn to_factory( - &self, - factory_func: &'static FactoryFunc, - ) -> Result, AsyncBindingBuilderError> - where - Args: 'static, - Return: 'static + ?Sized, - Interface: Fn + Send + Sync, - FactoryFunc: - Fn<(Arc,), Output = BoxFn> + Send + Sync, - { - use crate::provider::r#async::AsyncFactoryVariant; - - let mut bindings_lock = self.di_container.bindings.lock().await; - - if bindings_lock.has::(None) { - return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - - let factory_impl = ThreadsafeCastableFactory::new(factory_func); - - bindings_lock.set::( - None, - Box::new(crate::provider::r#async::AsyncFactoryProvider::new( - crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), - AsyncFactoryVariant::Normal, - )), - ); - - Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) - } - - /// Creates a binding of factory type `Interface` to a async factory inside of the - /// associated [`AsyncDIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`AsyncDIContainer`] already have a binding - /// for the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # use std::time::Duration; - /// # - /// # use syrette::{AsyncDIContainer, factory, async_closure}; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait Foo: Send + Sync {} - /// # - /// # struct Bar - /// # { - /// # num: i32, - /// # some_str: String - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # #[factory(async = true)] - /// # type FooFactory = dyn Fn(i32, String) -> dyn Foo; - /// # - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = AsyncDIContainer::new(); - /// # - /// di_container - /// .bind::() - /// .to_async_factory(&|_| { - /// async_closure!(|num, some_str| { - /// let bar = TransientPtr::new(Bar { num, some_str }); - /// - /// tokio::time::sleep(Duration::from_secs(2)).await; - /// - /// bar as TransientPtr - /// }) - /// }) - /// .await?; - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub async fn to_async_factory( - &self, - factory_func: &'static FactoryFunc, - ) -> Result, AsyncBindingBuilderError> - where - Args: 'static, - Return: 'static + ?Sized, - Interface: - Fn> + Send + Sync, - FactoryFunc: Fn< - (Arc,), - Output = BoxFn>, - > + Send - + Sync, - { - use crate::provider::r#async::AsyncFactoryVariant; - - let mut bindings_lock = self.di_container.bindings.lock().await; - - if bindings_lock.has::(None) { - return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - - let factory_impl = ThreadsafeCastableFactory::new(factory_func); - - bindings_lock.set::( - None, - Box::new(crate::provider::r#async::AsyncFactoryProvider::new( - crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), - AsyncFactoryVariant::Normal, - )), - ); - - Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) - } - - /// Creates a binding of type `Interface` to a factory that takes no arguments - /// inside of the associated [`AsyncDIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`AsyncDIContainer`] already have a binding - /// for the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::AsyncDIContainer; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait Foo: Send + Sync {} - /// # - /// # struct Bar - /// # { - /// # num: i32, - /// # some_str: String - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = AsyncDIContainer::new(); - /// # - /// di_container - /// .bind::() - /// .to_default_factory(&|_| { - /// Box::new(|| { - /// let bar = TransientPtr::new(Bar { - /// num: 42, - /// some_str: "hello".to_string(), - /// }); - /// - /// bar as TransientPtr - /// }) - /// }) - /// .await?; - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub async fn to_default_factory( - &self, - factory_func: &'static FactoryFunc, - ) -> Result, AsyncBindingBuilderError> - where - Return: 'static + ?Sized, - FactoryFunc: Fn< - (Arc,), - Output = BoxFn<(), crate::ptr::TransientPtr>, - > + Send - + Sync, - { - use crate::provider::r#async::AsyncFactoryVariant; - - let mut bindings_lock = self.di_container.bindings.lock().await; - - if bindings_lock.has::(None) { - return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - - let factory_impl = ThreadsafeCastableFactory::new(factory_func); - - bindings_lock.set::( - None, - Box::new(crate::provider::r#async::AsyncFactoryProvider::new( - crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), - AsyncFactoryVariant::Default, - )), - ); - - Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) - } - - /// Creates a binding of factory type `Interface` to a async factory inside of the - /// associated [`AsyncDIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`AsyncDIContainer`] already have a binding - /// for the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # use std::time::Duration; - /// # - /// # use syrette::{AsyncDIContainer, async_closure}; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait Foo: Send + Sync {} - /// # - /// # struct Bar - /// # { - /// # num: i32, - /// # some_str: String - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = AsyncDIContainer::new(); - /// # - /// di_container - /// .bind::() - /// .to_async_default_factory(&|_| { - /// async_closure!(|| { - /// let bar = TransientPtr::new(Bar { - /// num: 42, - /// some_str: "hello".to_string(), - /// }); - /// - /// tokio::time::sleep(Duration::from_secs(1)).await; - /// - /// bar as TransientPtr - /// }) - /// }) - /// .await?; - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub async fn to_async_default_factory( - &self, - factory_func: &'static FactoryFunc, - ) -> Result, AsyncBindingBuilderError> - where - Return: 'static + ?Sized, - FactoryFunc: Fn< - (Arc,), - Output = BoxFn<(), crate::future::BoxFuture<'static, Return>>, - > + Send - + Sync, - { - use crate::provider::r#async::AsyncFactoryVariant; - - let mut bindings_lock = self.di_container.bindings.lock().await; - - if bindings_lock.has::(None) { - return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - - let factory_impl = ThreadsafeCastableFactory::new(factory_func); - - bindings_lock.set::( - None, - Box::new(crate::provider::r#async::AsyncFactoryProvider::new( - crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), - AsyncFactoryVariant::AsyncDefault, - )), - ); - - Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) - } -} - -/// Dependency injection container. -pub struct AsyncDIContainer -{ - bindings: Mutex>, -} - -impl AsyncDIContainer -{ - /// Returns a new `AsyncDIContainer`. - #[must_use] - pub fn new() -> Arc - { - Arc::new(Self { - bindings: Mutex::new(DIContainerBindingMap::new()), - }) - } - - /// Returns a new [`AsyncBindingBuilder`] for the given interface. - #[must_use] - pub fn bind(self: &mut Arc) -> AsyncBindingBuilder - where - Interface: 'static + ?Sized + Send + Sync, - { - AsyncBindingBuilder::::new(self.clone()) - } - - /// Returns the type bound with `Interface`. - /// - /// # Errors - /// Will return `Err` if: - /// - No binding for `Interface` exists - /// - Resolving the binding for `Interface` fails - /// - Casting the binding for `Interface` fails - pub async fn get( - self: &Arc, - ) -> Result, AsyncDIContainerError> - where - Interface: 'static + ?Sized + Send + Sync, - { - self.get_bound::(Vec::new(), None).await - } - - /// Returns the type bound with `Interface` and the specified name. - /// - /// # Errors - /// Will return `Err` if: - /// - No binding for `Interface` with name `name` exists - /// - Resolving the binding for `Interface` fails - /// - Casting the binding for `Interface` fails - pub async fn get_named( - self: &Arc, - name: &'static str, - ) -> Result, AsyncDIContainerError> - where - Interface: 'static + ?Sized + Send + Sync, - { - self.get_bound::(Vec::new(), Some(name)).await - } - - #[doc(hidden)] - pub async fn get_bound( - self: &Arc, - dependency_history: Vec<&'static str>, - name: Option<&'static str>, - ) -> Result, AsyncDIContainerError> - where - Interface: 'static + ?Sized + Send + Sync, - { - let binding_providable = self - .get_binding_providable::(name, dependency_history) - .await?; - - self.handle_binding_providable(binding_providable).await - } - - async fn handle_binding_providable( - self: &Arc, - binding_providable: AsyncProvidable, - ) -> Result, AsyncDIContainerError> - where - Interface: 'static + ?Sized + Send + Sync, - { - match binding_providable { - AsyncProvidable::Transient(transient_binding) => { - Ok(SomeThreadsafePtr::Transient( - transient_binding.cast::().map_err(|_| { - AsyncDIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "transient", - } - })?, - )) - } - AsyncProvidable::Singleton(singleton_binding) => { - Ok(SomeThreadsafePtr::ThreadsafeSingleton( - singleton_binding - .cast::() - .map_err(|err| match err { - CastError::NotArcCastable(_) => { - AsyncDIContainerError::InterfaceNotAsync(type_name::< - Interface, - >( - )) - } - CastError::CastFailed { from: _, to: _ } => { - AsyncDIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "singleton", - } - } - })?, - )) - } - #[cfg(feature = "factory")] - AsyncProvidable::Factory(factory_binding) => { - use crate::interfaces::factory::IThreadsafeFactory; - - let factory = factory_binding - .cast::,), Interface>>() - .map_err(|err| match err { - CastError::NotArcCastable(_) => { - AsyncDIContainerError::InterfaceNotAsync( - type_name::(), - ) - } - CastError::CastFailed { from: _, to: _ } => { - AsyncDIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "factory", - } - } - })?; - - Ok(SomeThreadsafePtr::ThreadsafeFactory( - factory(self.clone()).into(), - )) - } - #[cfg(feature = "factory")] - AsyncProvidable::DefaultFactory(binding) => { - use crate::interfaces::factory::IThreadsafeFactory; - - let default_factory = Self::cast_factory_binding::< - dyn IThreadsafeFactory< - (Arc,), - dyn Fn<(), Output = TransientPtr> + Send + Sync, - >, - >(binding, "default factory")?; - - Ok(SomeThreadsafePtr::Transient(default_factory(self.clone())())) - } - #[cfg(feature = "factory")] - AsyncProvidable::AsyncDefaultFactory(binding) => { - use crate::interfaces::factory::IThreadsafeFactory; - - let async_default_factory = Self::cast_factory_binding::< - dyn IThreadsafeFactory< - (Arc,), - dyn Fn<(), Output = BoxFuture<'static, TransientPtr>> - + Send - + Sync, - >, - >( - binding, "async default factory" - )?; - - Ok(SomeThreadsafePtr::Transient( - async_default_factory(self.clone())().await, - )) - } - } - } - - #[cfg(feature = "factory")] - fn cast_factory_binding( - factory_binding: Arc, - binding_kind: &'static str, - ) -> Result, AsyncDIContainerError> - { - factory_binding.cast::().map_err(|err| match err { - CastError::NotArcCastable(_) => { - AsyncDIContainerError::InterfaceNotAsync(type_name::()) - } - CastError::CastFailed { from: _, to: _ } => { - AsyncDIContainerError::CastFailed { - interface: type_name::(), - binding_kind, - } - } - }) - } - - async fn get_binding_providable( - self: &Arc, - name: Option<&'static str>, - dependency_history: Vec<&'static str>, - ) -> Result - where - Interface: 'static + ?Sized + Send + Sync, - { - let provider; - - { - let bindings_lock = self.bindings.lock().await; - - provider = bindings_lock - .get::(name) - .map_or_else( - || { - Err(AsyncDIContainerError::BindingNotFound { - interface: type_name::(), - name, - }) - }, - Ok, - )? - .clone(); - } - - provider - .provide(self, dependency_history) - .await - .map_err(|err| AsyncDIContainerError::BindingResolveFailed { - reason: err, - interface: type_name::(), - }) - } -} - -#[cfg(test)] -mod tests -{ - use std::error::Error; - - use async_trait::async_trait; - use mockall::mock; - - use super::*; - use crate::errors::injectable::InjectableError; - use crate::ptr::TransientPtr; - - mod subjects - { - //! Test subjects. - - use std::fmt::Debug; - use std::sync::Arc; - - use async_trait::async_trait; - use syrette_macros::declare_interface; - - use super::AsyncDIContainer; - use crate::interfaces::async_injectable::AsyncInjectable; - use crate::ptr::TransientPtr; - - pub trait IUserManager: Send + Sync - { - fn add_user(&self, user_id: i128); - - fn remove_user(&self, user_id: i128); - } - - pub struct UserManager {} - - impl UserManager - { - pub fn new() -> Self - { - Self {} - } - } - - impl IUserManager for UserManager - { - fn add_user(&self, _user_id: i128) - { - // ... - } - - fn remove_user(&self, _user_id: i128) - { - // ... - } - } - - use crate as syrette; - - declare_interface!(UserManager -> IUserManager); - - #[async_trait] - impl AsyncInjectable for UserManager - { - async fn resolve( - _: &Arc, - _dependency_history: Vec<&'static str>, - ) -> Result, crate::errors::injectable::InjectableError> - where - Self: Sized, - { - Ok(TransientPtr::new(Self::new())) - } - } - - pub trait INumber: Send + Sync - { - fn get(&self) -> i32; - - fn set(&mut self, number: i32); - } - - impl PartialEq for dyn INumber - { - fn eq(&self, other: &Self) -> bool - { - self.get() == other.get() - } - } - - impl Debug for dyn INumber - { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - f.write_str(format!("{}", self.get()).as_str()) - } - } - - pub struct Number - { - pub num: i32, - } - - impl Number - { - pub fn new() -> Self - { - Self { num: 0 } - } - } - - impl INumber for Number - { - fn get(&self) -> i32 - { - self.num - } - - fn set(&mut self, number: i32) - { - self.num = number; - } - } - - declare_interface!(Number -> INumber, async = true); - - #[async_trait] - impl AsyncInjectable for Number - { - async fn resolve( - _: &Arc, - _dependency_history: Vec<&'static str>, - ) -> Result, crate::errors::injectable::InjectableError> - where - Self: Sized, - { - Ok(TransientPtr::new(Self::new())) - } - } - } - - #[tokio::test] - async fn can_bind_to() -> Result<(), Box> - { - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to::() - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - async fn can_bind_to_transient() -> Result<(), Box> - { - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to::() - .await? - .in_transient_scope() - .await; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - async fn can_bind_to_transient_when_named() -> Result<(), Box> - { - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to::() - .await? - .in_transient_scope() - .await - .when_named("regular") - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - async fn can_bind_to_singleton() -> Result<(), Box> - { - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to::() - .await? - .in_singleton_scope() - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - async fn can_bind_to_singleton_when_named() -> Result<(), Box> - { - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to::() - .await? - .in_singleton_scope() - .await? - .when_named("cool") - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "factory")] - async fn can_bind_to_factory() -> Result<(), Box> - { - use crate as syrette; - use crate::factory; - - #[factory(threadsafe = true)] - type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; - - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to_factory(&|_| { - Box::new(|| { - let user_manager: TransientPtr = - TransientPtr::new(subjects::UserManager::new()); - - user_manager - }) - }) - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "factory")] - async fn can_bind_to_factory_when_named() -> Result<(), Box> - { - use crate as syrette; - use crate::factory; - - #[factory(threadsafe = true)] - type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; - - let mut di_container = AsyncDIContainer::new(); - - { - assert_eq!(di_container.bindings.lock().await.count(), 0); - } - - di_container - .bind::() - .to_factory(&|_| { - Box::new(|| { - let user_manager: TransientPtr = - TransientPtr::new(subjects::UserManager::new()); - - user_manager - }) - }) - .await? - .when_named("awesome") - .await?; - - { - assert_eq!(di_container.bindings.lock().await.count(), 1); - } - - Ok(()) - } - - #[tokio::test] - async fn can_get() -> Result<(), Box> - { - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_do_clone().returning(|| { - let mut inner_mock_provider = MockProvider::new(); - - inner_mock_provider.expect_provide().returning(|_, _| { - Ok(AsyncProvidable::Transient(TransientPtr::new( - subjects::UserManager::new(), - ))) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::(None, Box::new(mock_provider)); - } - - di_container - .get::() - .await? - .transient()?; - - Ok(()) - } - - #[tokio::test] - async fn can_get_named() -> Result<(), Box> - { - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_do_clone().returning(|| { - let mut inner_mock_provider = MockProvider::new(); - - inner_mock_provider.expect_provide().returning(|_, _| { - Ok(AsyncProvidable::Transient(TransientPtr::new( - subjects::UserManager::new(), - ))) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::( - Some("special"), - Box::new(mock_provider), - ); - } - - di_container - .get_named::("special") - .await? - .transient()?; - - Ok(()) - } - - #[tokio::test] - async fn can_get_singleton() -> Result<(), Box> - { - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - let mut singleton = ThreadsafeSingletonPtr::new(subjects::Number::new()); - - ThreadsafeSingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; - - mock_provider.expect_do_clone().returning(move || { - let mut inner_mock_provider = MockProvider::new(); - - let singleton_clone = singleton.clone(); - - inner_mock_provider.expect_provide().returning(move |_, _| { - Ok(AsyncProvidable::Singleton(singleton_clone.clone())) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::(None, Box::new(mock_provider)); - } - - let first_number_rc = di_container - .get::() - .await? - .threadsafe_singleton()?; - - assert_eq!(first_number_rc.get(), 2820); - - let second_number_rc = di_container - .get::() - .await? - .threadsafe_singleton()?; - - assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); - - Ok(()) - } - - #[tokio::test] - async fn can_get_singleton_named() -> Result<(), Box> - { - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - let mut singleton = ThreadsafeSingletonPtr::new(subjects::Number::new()); - - ThreadsafeSingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; - - mock_provider.expect_do_clone().returning(move || { - let mut inner_mock_provider = MockProvider::new(); - - let singleton_clone = singleton.clone(); - - inner_mock_provider.expect_provide().returning(move |_, _| { - Ok(AsyncProvidable::Singleton(singleton_clone.clone())) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::(Some("cool"), Box::new(mock_provider)); - } - - let first_number_rc = di_container - .get_named::("cool") - .await? - .threadsafe_singleton()?; - - assert_eq!(first_number_rc.get(), 2820); - - let second_number_rc = di_container - .get_named::("cool") - .await? - .threadsafe_singleton()?; - - assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "factory")] - async fn can_get_factory() -> Result<(), Box> - { - trait IUserManager: Send + Sync - { - fn add_user(&mut self, user_id: i128); - - fn remove_user(&mut self, user_id: i128); - } - - struct UserManager - { - users: Vec, - } - - impl UserManager - { - fn new(users: Vec) -> Self - { - Self { users } - } - } - - impl IUserManager for UserManager - { - fn add_user(&mut self, user_id: i128) - { - self.users.push(user_id); - } - - fn remove_user(&mut self, user_id: i128) - { - let user_index = - self.users.iter().position(|user| *user == user_id).unwrap(); - - self.users.remove(user_index); - } - } - - use crate as syrette; - - #[crate::factory(threadsafe = true)] - type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; - - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_do_clone().returning(|| { - type FactoryFunc = Box< - (dyn Fn<(Vec,), Output = TransientPtr> + Send + Sync) - >; - - let mut inner_mock_provider = MockProvider::new(); - - let factory_func: &'static (dyn Fn< - (Arc,), - Output = FactoryFunc> + Send + Sync) = &|_| { - Box::new(|users| { - let user_manager: TransientPtr = - TransientPtr::new(UserManager::new(users)); - - user_manager - }) - }; - - inner_mock_provider.expect_provide().returning(|_, _| { - Ok(AsyncProvidable::Factory( - crate::ptr::ThreadsafeFactoryPtr::new( - ThreadsafeCastableFactory::new(factory_func), - ), - )) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::(None, Box::new(mock_provider)); - } - - di_container - .get::() - .await? - .threadsafe_factory()?; - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "factory")] - async fn can_get_factory_named() -> Result<(), Box> - { - trait IUserManager: Send + Sync - { - fn add_user(&mut self, user_id: i128); - - fn remove_user(&mut self, user_id: i128); - } - - struct UserManager - { - users: Vec, - } - - impl UserManager - { - fn new(users: Vec) -> Self - { - Self { users } - } - } - - impl IUserManager for UserManager - { - fn add_user(&mut self, user_id: i128) - { - self.users.push(user_id); - } - - fn remove_user(&mut self, user_id: i128) - { - let user_index = - self.users.iter().position(|user| *user == user_id).unwrap(); - - self.users.remove(user_index); - } - } - - use crate as syrette; - - #[crate::factory(threadsafe = true)] - type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; - - mock! { - Provider {} - - #[async_trait] - impl IAsyncProvider for Provider - { - async fn provide( - &self, - di_container: &Arc, - dependency_history: Vec<&'static str>, - ) -> Result; - - fn do_clone(&self) -> Box; - } - } - - let di_container = AsyncDIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_do_clone().returning(|| { - type FactoryFunc = Box< - (dyn Fn<(Vec,), Output = TransientPtr> + Send + Sync) - >; - - let mut inner_mock_provider = MockProvider::new(); - - let factory_func: &'static (dyn Fn< - (Arc,), - Output = FactoryFunc> + Send + Sync) = &|_| { - Box::new(|users| { - let user_manager: TransientPtr = - TransientPtr::new(UserManager::new(users)); - - user_manager - }) - }; - - inner_mock_provider.expect_provide().returning(|_, _| { - Ok(AsyncProvidable::Factory( - crate::ptr::ThreadsafeFactoryPtr::new( - ThreadsafeCastableFactory::new(factory_func), - ), - )) - }); - - Box::new(inner_mock_provider) - }); - - { - di_container - .bindings - .lock() - .await - .set::(Some("special"), Box::new(mock_provider)); - } - - di_container - .get_named::("special") - .await? - .threadsafe_factory()?; - - Ok(()) - } -} diff --git a/src/di_container.rs b/src/di_container.rs deleted file mode 100644 index 2ed1e25..0000000 --- a/src/di_container.rs +++ /dev/null @@ -1,1267 +0,0 @@ -//! Dependency injection container. -//! -//! # Examples -//! ``` -//! use std::collections::HashMap; -//! use std::error::Error; -//! -//! use syrette::{injectable, DIContainer}; -//! -//! trait IDatabaseService -//! { -//! fn get_all_records(&self, table_name: String) -> HashMap; -//! } -//! -//! struct DatabaseService {} -//! -//! #[injectable(IDatabaseService)] -//! impl DatabaseService -//! { -//! fn new() -> Self -//! { -//! Self {} -//! } -//! } -//! -//! impl IDatabaseService for DatabaseService -//! { -//! fn get_all_records(&self, table_name: String) -> HashMap -//! { -//! // Do stuff here -//! HashMap::::new() -//! } -//! } -//! -//! fn main() -> Result<(), Box> -//! { -//! let mut di_container = DIContainer::new(); -//! -//! di_container -//! .bind::() -//! .to::() -//! .map_err(|err| err.to_string())?; -//! -//! let database_service = di_container -//! .get::() -//! .map_err(|err| err.to_string())? -//! .transient()?; -//! -//! Ok(()) -//! } -//! ``` -use std::any::type_name; -use std::cell::RefCell; -use std::marker::PhantomData; -use std::rc::Rc; - -#[cfg(feature = "factory")] -use crate::castable_factory::blocking::CastableFactory; -use crate::di_container_binding_map::DIContainerBindingMap; -use crate::errors::di_container::{ - BindingBuilderError, - BindingScopeConfiguratorError, - BindingWhenConfiguratorError, - DIContainerError, -}; -use crate::interfaces::injectable::Injectable; -use crate::libs::intertrait::cast::{CastBox, CastRc}; -use crate::provider::blocking::{ - IProvider, - Providable, - SingletonProvider, - TransientTypeProvider, -}; -use crate::ptr::{SingletonPtr, SomePtr}; - -/// When configurator for a binding for type 'Interface' inside a [`DIContainer`]. -pub struct BindingWhenConfigurator -where - Interface: 'static + ?Sized, -{ - di_container: Rc, - interface_phantom: PhantomData, -} - -impl BindingWhenConfigurator -where - Interface: 'static + ?Sized, -{ - fn new(di_container: Rc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - } - } - - /// Configures the binding to have a name. - /// - /// # Errors - /// Will return Err if no binding for the interface already exists. - pub fn when_named( - &self, - name: &'static str, - ) -> Result<(), BindingWhenConfiguratorError> - { - let mut bindings_mut = self.di_container.bindings.borrow_mut(); - - let binding = bindings_mut.remove::(None).map_or_else( - || { - Err(BindingWhenConfiguratorError::BindingNotFound(type_name::< - Interface, - >( - ))) - }, - Ok, - )?; - - bindings_mut.set::(Some(name), binding); - - Ok(()) - } -} - -/// Scope configurator for a binding for type 'Interface' inside a [`DIContainer`]. -pub struct BindingScopeConfigurator -where - Interface: 'static + ?Sized, - Implementation: Injectable, -{ - di_container: Rc, - interface_phantom: PhantomData, - implementation_phantom: PhantomData, -} - -impl BindingScopeConfigurator -where - Interface: 'static + ?Sized, - Implementation: Injectable, -{ - fn new(di_container: Rc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - implementation_phantom: PhantomData, - } - } - - /// Configures the binding to be in a transient scope. - /// - /// This is the default. - #[allow(clippy::must_use_candidate)] - pub fn in_transient_scope(&self) -> BindingWhenConfigurator - { - let mut bindings_mut = self.di_container.bindings.borrow_mut(); - - bindings_mut.set::( - None, - Box::new(TransientTypeProvider::::new()), - ); - - BindingWhenConfigurator::new(self.di_container.clone()) - } - - /// Configures the binding to be in a singleton scope. - /// - /// # Errors - /// Will return Err if resolving the implementation fails. - pub fn in_singleton_scope( - &self, - ) -> Result, BindingScopeConfiguratorError> - { - let singleton: SingletonPtr = SingletonPtr::from( - Implementation::resolve(&self.di_container, Vec::new()) - .map_err(BindingScopeConfiguratorError::SingletonResolveFailed)?, - ); - - let mut bindings_mut = self.di_container.bindings.borrow_mut(); - - bindings_mut.set::(None, Box::new(SingletonProvider::new(singleton))); - - Ok(BindingWhenConfigurator::new(self.di_container.clone())) - } -} - -/// Binding builder for type `Interface` inside a [`DIContainer`]. -pub struct BindingBuilder -where - Interface: 'static + ?Sized, -{ - di_container: Rc, - interface_phantom: PhantomData, -} - -impl BindingBuilder -where - Interface: 'static + ?Sized, -{ - fn new(di_container: Rc) -> Self - { - Self { - di_container, - interface_phantom: PhantomData, - } - } - - /// Creates a binding of type `Interface` to type `Implementation` inside of the - /// associated [`DIContainer`]. - /// - /// The scope of the binding is transient. But that can be changed by using the - /// returned [`BindingScopeConfigurator`] - /// - /// # Errors - /// Will return Err if the associated [`DIContainer`] already have a binding for - /// the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::{DIContainer, injectable}; - /// # - /// # trait Foo {} - /// # - /// # struct Bar {} - /// # - /// # #[injectable(Foo)] - /// # impl Bar { - /// # fn new() -> Self - /// # { - /// # Self {} - /// # } - /// # } - /// # - /// # impl Foo for Bar {} - /// # - /// # fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = DIContainer::new(); - /// # - /// di_container.bind::().to::(); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn to( - &self, - ) -> Result, BindingBuilderError> - where - Implementation: Injectable, - { - { - let bindings = self.di_container.bindings.borrow(); - - if bindings.has::(None) { - return Err(BindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - } - - let binding_scope_configurator = - BindingScopeConfigurator::new(self.di_container.clone()); - - binding_scope_configurator.in_transient_scope(); - - Ok(binding_scope_configurator) - } - - /// Creates a binding of factory type `Interface` to a factory inside of the - /// associated [`DIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`DIContainer`] already have a binding for - /// the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::{DIContainer, factory}; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait ICustomerID {} - /// # trait ICustomer {} - /// # - /// # struct Customer - /// # { - /// # name: String, - /// # id: TransientPtr - /// # } - /// # - /// # impl Customer { - /// # fn new(name: String, id: TransientPtr) -> Self - /// # { - /// # Self { name, id } - /// # } - /// # } - /// # - /// # impl ICustomer for Customer {} - /// # - /// # #[factory] - /// # type ICustomerFactory = dyn Fn(String, u32) -> dyn ICustomer; - /// # - /// # #[factory] - /// # type ICustomerIDFactory = dyn Fn(u32) -> dyn ICustomerID; - /// # - /// # fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = DIContainer::new(); - /// # - /// di_container - /// .bind::() - /// .to_factory(&|context| { - /// Box::new(move |name, id| { - /// let customer_id_factory = context - /// .get::() - /// .unwrap() - /// .factory() - /// .unwrap(); - /// - /// let customer_id = customer_id_factory(id); - /// - /// let customer = TransientPtr::new(Customer::new(name, customer_id)); - /// - /// customer as TransientPtr - /// }) - /// }); - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub fn to_factory( - &self, - factory_func: &'static Func, - ) -> Result, BindingBuilderError> - where - Args: 'static, - Return: 'static + ?Sized, - Interface: Fn>, - Func: Fn<(std::rc::Rc,), Output = Box>, - { - { - let bindings = self.di_container.bindings.borrow(); - - if bindings.has::(None) { - return Err(BindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - } - - let mut bindings_mut = self.di_container.bindings.borrow_mut(); - - let factory_impl = CastableFactory::new(factory_func); - - bindings_mut.set::( - None, - Box::new(crate::provider::blocking::FactoryProvider::new( - crate::ptr::FactoryPtr::new(factory_impl), - false, - )), - ); - - Ok(BindingWhenConfigurator::new(self.di_container.clone())) - } - - /// Creates a binding of type `Interface` to a factory that takes no arguments - /// inside of the associated [`DIContainer`]. - /// - /// # Errors - /// Will return Err if the associated [`DIContainer`] already have a binding for - /// the interface. - /// - /// # Examples - /// ``` - /// # use std::error::Error; - /// # - /// # use syrette::{DIContainer, factory}; - /// # use syrette::ptr::TransientPtr; - /// # - /// # trait IBuffer {} - /// # - /// # struct Buffer - /// # { - /// # buf: [u8; SIZE] - /// # } - /// # - /// # impl Buffer - /// # { - /// # fn new() -> Self - /// # { - /// # Self { - /// # buf: [0; SIZE] - /// # } - /// # } - /// # } - /// # - /// # impl IBuffer for Buffer {} - /// # - /// # const BUFFER_SIZE: usize = 12; - /// # - /// # fn main() -> Result<(), Box> - /// # { - /// # let mut di_container = DIContainer::new(); - /// # - /// di_container.bind::().to_default_factory(&|_| { - /// Box::new(|| { - /// let buffer = TransientPtr::new(Buffer::::new()); - /// - /// buffer as TransientPtr - /// }) - /// }); - /// # - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "factory")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] - pub fn to_default_factory( - &self, - factory_func: &'static FactoryFunc, - ) -> Result, BindingBuilderError> - where - Return: 'static + ?Sized, - FactoryFunc: Fn< - (Rc,), - Output = crate::ptr::TransientPtr< - dyn Fn<(), Output = crate::ptr::TransientPtr>, - >, - >, - { - { - let bindings = self.di_container.bindings.borrow(); - - if bindings.has::(None) { - return Err(BindingBuilderError::BindingAlreadyExists(type_name::< - Interface, - >( - ))); - } - } - - let mut bindings_mut = self.di_container.bindings.borrow_mut(); - - let factory_impl = CastableFactory::new(factory_func); - - bindings_mut.set::( - None, - Box::new(crate::provider::blocking::FactoryProvider::new( - crate::ptr::FactoryPtr::new(factory_impl), - true, - )), - ); - - Ok(BindingWhenConfigurator::new(self.di_container.clone())) - } -} - -/// Dependency injection container. -pub struct DIContainer -{ - bindings: RefCell>, -} - -impl DIContainer -{ - /// Returns a new `DIContainer`. - #[must_use] - pub fn new() -> Rc - { - Rc::new(Self { - bindings: RefCell::new(DIContainerBindingMap::new()), - }) - } - - /// Returns a new [`BindingBuilder`] for the given interface. - #[must_use] - pub fn bind(self: &mut Rc) -> BindingBuilder - where - Interface: 'static + ?Sized, - { - BindingBuilder::::new(self.clone()) - } - - /// Returns the type bound with `Interface`. - /// - /// # Errors - /// Will return `Err` if: - /// - No binding for `Interface` exists - /// - Resolving the binding for `Interface` fails - /// - Casting the binding for `Interface` fails - pub fn get(self: &Rc) -> Result, DIContainerError> - where - Interface: 'static + ?Sized, - { - self.get_bound::(Vec::new(), None) - } - - /// Returns the type bound with `Interface` and the specified name. - /// - /// # Errors - /// Will return `Err` if: - /// - No binding for `Interface` with name `name` exists - /// - Resolving the binding for `Interface` fails - /// - Casting the binding for `Interface` fails - pub fn get_named( - self: &Rc, - name: &'static str, - ) -> Result, DIContainerError> - where - Interface: 'static + ?Sized, - { - self.get_bound::(Vec::new(), Some(name)) - } - - #[doc(hidden)] - pub fn get_bound( - self: &Rc, - dependency_history: Vec<&'static str>, - name: Option<&'static str>, - ) -> Result, DIContainerError> - where - Interface: 'static + ?Sized, - { - let binding_providable = - self.get_binding_providable::(name, dependency_history)?; - - self.handle_binding_providable(binding_providable) - } - - fn handle_binding_providable( - self: &Rc, - binding_providable: Providable, - ) -> Result, DIContainerError> - where - Interface: 'static + ?Sized, - { - match binding_providable { - Providable::Transient(transient_binding) => Ok(SomePtr::Transient( - transient_binding.cast::().map_err(|_| { - DIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "transient", - } - })?, - )), - Providable::Singleton(singleton_binding) => Ok(SomePtr::Singleton( - singleton_binding.cast::().map_err(|_| { - DIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "singleton", - } - })?, - )), - #[cfg(feature = "factory")] - Providable::Factory(factory_binding) => { - use crate::interfaces::factory::IFactory; - - let factory = factory_binding - .cast::,), Interface>>() - .map_err(|_| DIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "factory", - })?; - - Ok(SomePtr::Factory(factory(self.clone()).into())) - } - #[cfg(feature = "factory")] - Providable::DefaultFactory(factory_binding) => { - use crate::interfaces::factory::IFactory; - use crate::ptr::TransientPtr; - - let default_factory = factory_binding - .cast::,), - dyn Fn<(), Output = TransientPtr>, - >>() - .map_err(|_| DIContainerError::CastFailed { - interface: type_name::(), - binding_kind: "default factory", - })?; - - Ok(SomePtr::Transient(default_factory(self.clone())())) - } - } - } - - fn get_binding_providable( - self: &Rc, - name: Option<&'static str>, - dependency_history: Vec<&'static str>, - ) -> Result - where - Interface: 'static + ?Sized, - { - self.bindings - .borrow() - .get::(name) - .map_or_else( - || { - Err(DIContainerError::BindingNotFound { - interface: type_name::(), - name, - }) - }, - Ok, - )? - .provide(self, dependency_history) - .map_err(|err| DIContainerError::BindingResolveFailed { - reason: err, - interface: type_name::(), - }) - } -} - -#[cfg(test)] -mod tests -{ - use std::error::Error; - - use mockall::mock; - - use super::*; - use crate::errors::injectable::InjectableError; - use crate::provider::blocking::IProvider; - use crate::ptr::TransientPtr; - - mod subjects - { - //! Test subjects. - - use std::fmt::Debug; - use std::rc::Rc; - - use syrette_macros::declare_interface; - - use super::DIContainer; - use crate::interfaces::injectable::Injectable; - use crate::ptr::TransientPtr; - - pub trait IUserManager - { - fn add_user(&self, user_id: i128); - - fn remove_user(&self, user_id: i128); - } - - pub struct UserManager {} - - impl UserManager - { - pub fn new() -> Self - { - Self {} - } - } - - impl IUserManager for UserManager - { - fn add_user(&self, _user_id: i128) - { - // ... - } - - fn remove_user(&self, _user_id: i128) - { - // ... - } - } - - use crate as syrette; - - declare_interface!(UserManager -> IUserManager); - - impl Injectable for UserManager - { - fn resolve( - _di_container: &Rc, - _dependency_history: Vec<&'static str>, - ) -> Result, crate::errors::injectable::InjectableError> - where - Self: Sized, - { - Ok(TransientPtr::new(Self::new())) - } - } - - pub trait INumber - { - fn get(&self) -> i32; - - fn set(&mut self, number: i32); - } - - impl PartialEq for dyn INumber - { - fn eq(&self, other: &Self) -> bool - { - self.get() == other.get() - } - } - - impl Debug for dyn INumber - { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - f.write_str(format!("{}", self.get()).as_str()) - } - } - - pub struct Number - { - pub num: i32, - } - - impl Number - { - pub fn new() -> Self - { - Self { num: 0 } - } - } - - impl INumber for Number - { - fn get(&self) -> i32 - { - self.num - } - - fn set(&mut self, number: i32) - { - self.num = number; - } - } - - declare_interface!(Number -> INumber); - - impl Injectable for Number - { - fn resolve( - _di_container: &Rc, - _dependency_history: Vec<&'static str>, - ) -> Result, crate::errors::injectable::InjectableError> - where - Self: Sized, - { - Ok(TransientPtr::new(Self::new())) - } - } - } - - #[test] - fn can_bind_to() -> Result<(), Box> - { - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to::()?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - fn can_bind_to_transient() -> Result<(), Box> - { - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to::()? - .in_transient_scope(); - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - fn can_bind_to_transient_when_named() -> Result<(), Box> - { - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to::()? - .in_transient_scope() - .when_named("regular")?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - fn can_bind_to_singleton() -> Result<(), Box> - { - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to::()? - .in_singleton_scope()?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - fn can_bind_to_singleton_when_named() -> Result<(), Box> - { - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to::()? - .in_singleton_scope()? - .when_named("cool")?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - #[cfg(feature = "factory")] - fn can_bind_to_factory() -> Result<(), Box> - { - use crate as syrette; - use crate::factory; - - #[factory] - type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; - - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to_factory(&|_| { - Box::new(move || { - let user_manager: TransientPtr = - TransientPtr::new(subjects::UserManager::new()); - - user_manager - }) - })?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - #[cfg(feature = "factory")] - fn can_bind_to_factory_when_named() -> Result<(), Box> - { - use crate as syrette; - use crate::factory; - - #[factory] - type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; - - let mut di_container = DIContainer::new(); - - assert_eq!(di_container.bindings.borrow().count(), 0); - - di_container - .bind::() - .to_factory(&|_| { - Box::new(move || { - let user_manager: TransientPtr = - TransientPtr::new(subjects::UserManager::new()); - - user_manager - }) - })? - .when_named("awesome")?; - - assert_eq!(di_container.bindings.borrow().count(), 1); - - Ok(()) - } - - #[test] - fn can_get() -> Result<(), Box> - { - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_provide().returning(|_, _| { - Ok(Providable::Transient(TransientPtr::new( - subjects::UserManager::new(), - ))) - }); - - di_container - .bindings - .borrow_mut() - .set::(None, Box::new(mock_provider)); - - di_container - .get::()? - .transient()?; - - Ok(()) - } - - #[test] - fn can_get_named() -> Result<(), Box> - { - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_provide().returning(|_, _| { - Ok(Providable::Transient(TransientPtr::new( - subjects::UserManager::new(), - ))) - }); - - di_container - .bindings - .borrow_mut() - .set::(Some("special"), Box::new(mock_provider)); - - di_container - .get_named::("special")? - .transient()?; - - Ok(()) - } - - #[test] - fn can_get_singleton() -> Result<(), Box> - { - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - let mut singleton = SingletonPtr::new(subjects::Number::new()); - - SingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; - - mock_provider - .expect_provide() - .returning_st(move |_, _| Ok(Providable::Singleton(singleton.clone()))); - - di_container - .bindings - .borrow_mut() - .set::(None, Box::new(mock_provider)); - - let first_number_rc = di_container.get::()?.singleton()?; - - assert_eq!(first_number_rc.get(), 2820); - - let second_number_rc = - di_container.get::()?.singleton()?; - - assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); - - Ok(()) - } - - #[test] - fn can_get_singleton_named() -> Result<(), Box> - { - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let mut mock_provider = MockProvider::new(); - - let mut singleton = SingletonPtr::new(subjects::Number::new()); - - SingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; - - mock_provider - .expect_provide() - .returning_st(move |_, _| Ok(Providable::Singleton(singleton.clone()))); - - di_container - .bindings - .borrow_mut() - .set::(Some("cool"), Box::new(mock_provider)); - - let first_number_rc = di_container - .get_named::("cool")? - .singleton()?; - - assert_eq!(first_number_rc.get(), 2820); - - let second_number_rc = di_container - .get_named::("cool")? - .singleton()?; - - assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); - - Ok(()) - } - - #[test] - #[cfg(feature = "factory")] - fn can_get_factory() -> Result<(), Box> - { - use crate::ptr::FactoryPtr; - - trait IUserManager - { - fn add_user(&mut self, user_id: i128); - - fn remove_user(&mut self, user_id: i128); - } - - struct UserManager - { - users: Vec, - } - - impl UserManager - { - fn new(users: Vec) -> Self - { - Self { users } - } - } - - impl IUserManager for UserManager - { - fn add_user(&mut self, user_id: i128) - { - self.users.push(user_id); - } - - fn remove_user(&mut self, user_id: i128) - { - let user_index = - self.users.iter().position(|user| *user == user_id).unwrap(); - - self.users.remove(user_index); - } - } - - type FactoryFunc = dyn Fn< - (std::rc::Rc,), - Output = Box< - dyn Fn<(Vec,), Output = crate::ptr::TransientPtr>, - >, - >; - - use crate as syrette; - - #[crate::factory] - type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; - - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let factory_func: &'static FactoryFunc = &|_: Rc| { - Box::new(move |users| { - let user_manager: TransientPtr = - TransientPtr::new(UserManager::new(users)); - - user_manager - }) - }; - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_provide().returning_st(|_, _| { - Ok(Providable::Factory(FactoryPtr::new(CastableFactory::new( - factory_func, - )))) - }); - - di_container - .bindings - .borrow_mut() - .set::(None, Box::new(mock_provider)); - - di_container.get::()?.factory()?; - - Ok(()) - } - - #[test] - #[cfg(feature = "factory")] - fn can_get_factory_named() -> Result<(), Box> - { - use crate::ptr::FactoryPtr; - - trait IUserManager - { - fn add_user(&mut self, user_id: i128); - - fn remove_user(&mut self, user_id: i128); - } - - struct UserManager - { - users: Vec, - } - - impl UserManager - { - fn new(users: Vec) -> Self - { - Self { users } - } - } - - impl IUserManager for UserManager - { - fn add_user(&mut self, user_id: i128) - { - self.users.push(user_id); - } - - fn remove_user(&mut self, user_id: i128) - { - let user_index = - self.users.iter().position(|user| *user == user_id).unwrap(); - - self.users.remove(user_index); - } - } - - type FactoryFunc = dyn Fn< - (std::rc::Rc,), - Output = Box< - dyn Fn<(Vec,), Output = crate::ptr::TransientPtr>, - >, - >; - - use crate as syrette; - - #[crate::factory] - type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; - - mock! { - Provider {} - - impl IProvider for Provider - { - fn provide( - &self, - di_container: &Rc, - dependency_history: Vec<&'static str>, - ) -> Result; - } - } - - let di_container = DIContainer::new(); - - let factory_func: &'static FactoryFunc = &|_: Rc| { - Box::new(move |users| { - let user_manager: TransientPtr = - TransientPtr::new(UserManager::new(users)); - - user_manager - }) - }; - - let mut mock_provider = MockProvider::new(); - - mock_provider.expect_provide().returning_st(|_, _| { - Ok(Providable::Factory(FactoryPtr::new(CastableFactory::new( - factory_func, - )))) - }); - - di_container - .bindings - .borrow_mut() - .set::(Some("special"), Box::new(mock_provider)); - - di_container - .get_named::("special")? - .factory()?; - - Ok(()) - } -} diff --git a/src/di_container/asynchronous/binding/builder.rs b/src/di_container/asynchronous/binding/builder.rs new file mode 100644 index 0000000..6daba30 --- /dev/null +++ b/src/di_container/asynchronous/binding/builder.rs @@ -0,0 +1,653 @@ +//! Binding builder for types inside of a [`AsyncDIContainer`]. +use std::any::type_name; +use std::marker::PhantomData; +use std::sync::Arc; + +use crate::di_container::asynchronous::binding::scope_configurator::AsyncBindingScopeConfigurator; +#[cfg(feature = "factory")] +use crate::di_container::asynchronous::binding::when_configurator::AsyncBindingWhenConfigurator; +use crate::errors::async_di_container::AsyncBindingBuilderError; +use crate::interfaces::async_injectable::AsyncInjectable; +use crate::AsyncDIContainer; + +/// Alias for a threadsafe boxed function. +#[cfg(feature = "factory")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] +pub type BoxFn = Box<(dyn Fn + Send + Sync)>; + +/// Binding builder for type `Interface` inside a [`AsyncDIContainer`]. +pub struct AsyncBindingBuilder +where + Interface: 'static + ?Sized + Send + Sync, +{ + di_container: Arc, + interface_phantom: PhantomData, +} + +impl AsyncBindingBuilder +where + Interface: 'static + ?Sized + Send + Sync, +{ + pub(crate) fn new(di_container: Arc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + } + } + + /// Creates a binding of type `Interface` to type `Implementation` inside of the + /// associated [`AsyncDIContainer`]. + /// + /// The scope of the binding is transient. But that can be changed by using the + /// returned [`AsyncBindingScopeConfigurator`] + /// + /// # Errors + /// Will return Err if the associated [`AsyncDIContainer`] already have a binding for + /// the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::{AsyncDIContainer, injectable}; + /// # + /// # trait Foo: Send + Sync {} + /// # + /// # struct Bar {} + /// # + /// # #[injectable(Foo, async = true)] + /// # impl Bar { + /// # fn new() -> Self + /// # { + /// # Self {} + /// # } + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = AsyncDIContainer::new(); + /// # + /// di_container.bind::().to::().await?; + /// # + /// # Ok(()) + /// # } + /// ``` + pub async fn to( + &self, + ) -> Result< + AsyncBindingScopeConfigurator, + AsyncBindingBuilderError, + > + where + Implementation: AsyncInjectable, + { + { + let bindings_lock = self.di_container.bindings.lock().await; + + if bindings_lock.has::(None) { + return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + } + + let binding_scope_configurator = + AsyncBindingScopeConfigurator::new(self.di_container.clone()); + + binding_scope_configurator.in_transient_scope().await; + + Ok(binding_scope_configurator) + } + + /// Creates a binding of factory type `Interface` to a factory inside of the + /// associated [`AsyncDIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`AsyncDIContainer`] already have a binding + /// for the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::{AsyncDIContainer, factory}; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait Foo: Send + Sync {} + /// # + /// # struct Bar + /// # { + /// # num: i32, + /// # some_str: String + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # #[factory(threadsafe = true)] + /// # type FooFactory = dyn Fn(i32, String) -> dyn Foo; + /// # + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = AsyncDIContainer::new(); + /// # + /// di_container + /// .bind::() + /// .to_factory(&|_| { + /// Box::new(|num, some_str| { + /// let bar = TransientPtr::new(Bar { num, some_str }); + /// + /// bar as TransientPtr + /// }) + /// }) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub async fn to_factory( + &self, + factory_func: &'static FactoryFunc, + ) -> Result, AsyncBindingBuilderError> + where + Args: 'static, + Return: 'static + ?Sized, + Interface: Fn + Send + Sync, + FactoryFunc: + Fn<(Arc,), Output = BoxFn> + Send + Sync, + { + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + use crate::provider::r#async::AsyncFactoryVariant; + + let mut bindings_lock = self.di_container.bindings.lock().await; + + if bindings_lock.has::(None) { + return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + + let factory_impl = ThreadsafeCastableFactory::new(factory_func); + + bindings_lock.set::( + None, + Box::new(crate::provider::r#async::AsyncFactoryProvider::new( + crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), + AsyncFactoryVariant::Normal, + )), + ); + + Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) + } + + /// Creates a binding of factory type `Interface` to a async factory inside of the + /// associated [`AsyncDIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`AsyncDIContainer`] already have a binding + /// for the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # use std::time::Duration; + /// # + /// # use syrette::{AsyncDIContainer, factory, async_closure}; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait Foo: Send + Sync {} + /// # + /// # struct Bar + /// # { + /// # num: i32, + /// # some_str: String + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # #[factory(async = true)] + /// # type FooFactory = dyn Fn(i32, String) -> dyn Foo; + /// # + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = AsyncDIContainer::new(); + /// # + /// di_container + /// .bind::() + /// .to_async_factory(&|_| { + /// async_closure!(|num, some_str| { + /// let bar = TransientPtr::new(Bar { num, some_str }); + /// + /// tokio::time::sleep(Duration::from_secs(2)).await; + /// + /// bar as TransientPtr + /// }) + /// }) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub async fn to_async_factory( + &self, + factory_func: &'static FactoryFunc, + ) -> Result, AsyncBindingBuilderError> + where + Args: 'static, + Return: 'static + ?Sized, + Interface: + Fn> + Send + Sync, + FactoryFunc: Fn< + (Arc,), + Output = BoxFn>, + > + Send + + Sync, + { + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + use crate::provider::r#async::AsyncFactoryVariant; + + let mut bindings_lock = self.di_container.bindings.lock().await; + + if bindings_lock.has::(None) { + return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + + let factory_impl = ThreadsafeCastableFactory::new(factory_func); + + bindings_lock.set::( + None, + Box::new(crate::provider::r#async::AsyncFactoryProvider::new( + crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), + AsyncFactoryVariant::Normal, + )), + ); + + Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) + } + + /// Creates a binding of type `Interface` to a factory that takes no arguments + /// inside of the associated [`AsyncDIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`AsyncDIContainer`] already have a binding + /// for the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::AsyncDIContainer; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait Foo: Send + Sync {} + /// # + /// # struct Bar + /// # { + /// # num: i32, + /// # some_str: String + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = AsyncDIContainer::new(); + /// # + /// di_container + /// .bind::() + /// .to_default_factory(&|_| { + /// Box::new(|| { + /// let bar = TransientPtr::new(Bar { + /// num: 42, + /// some_str: "hello".to_string(), + /// }); + /// + /// bar as TransientPtr + /// }) + /// }) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub async fn to_default_factory( + &self, + factory_func: &'static FactoryFunc, + ) -> Result, AsyncBindingBuilderError> + where + Return: 'static + ?Sized, + FactoryFunc: Fn< + (Arc,), + Output = BoxFn<(), crate::ptr::TransientPtr>, + > + Send + + Sync, + { + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + use crate::provider::r#async::AsyncFactoryVariant; + + let mut bindings_lock = self.di_container.bindings.lock().await; + + if bindings_lock.has::(None) { + return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + + let factory_impl = ThreadsafeCastableFactory::new(factory_func); + + bindings_lock.set::( + None, + Box::new(crate::provider::r#async::AsyncFactoryProvider::new( + crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), + AsyncFactoryVariant::Default, + )), + ); + + Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) + } + + /// Creates a binding of factory type `Interface` to a async factory inside of the + /// associated [`AsyncDIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`AsyncDIContainer`] already have a binding + /// for the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # use std::time::Duration; + /// # + /// # use syrette::{AsyncDIContainer, async_closure}; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait Foo: Send + Sync {} + /// # + /// # struct Bar + /// # { + /// # num: i32, + /// # some_str: String + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = AsyncDIContainer::new(); + /// # + /// di_container + /// .bind::() + /// .to_async_default_factory(&|_| { + /// async_closure!(|| { + /// let bar = TransientPtr::new(Bar { + /// num: 42, + /// some_str: "hello".to_string(), + /// }); + /// + /// tokio::time::sleep(Duration::from_secs(1)).await; + /// + /// bar as TransientPtr + /// }) + /// }) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub async fn to_async_default_factory( + &self, + factory_func: &'static FactoryFunc, + ) -> Result, AsyncBindingBuilderError> + where + Return: 'static + ?Sized, + FactoryFunc: Fn< + (Arc,), + Output = BoxFn<(), crate::future::BoxFuture<'static, Return>>, + > + Send + + Sync, + { + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + use crate::provider::r#async::AsyncFactoryVariant; + + let mut bindings_lock = self.di_container.bindings.lock().await; + + if bindings_lock.has::(None) { + return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + + let factory_impl = ThreadsafeCastableFactory::new(factory_func); + + bindings_lock.set::( + None, + Box::new(crate::provider::r#async::AsyncFactoryProvider::new( + crate::ptr::ThreadsafeFactoryPtr::new(factory_impl), + AsyncFactoryVariant::AsyncDefault, + )), + ); + + Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) + } +} + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use super::*; + use crate::ptr::TransientPtr; + use crate::test_utils::subjects_async; + + #[tokio::test] + async fn can_bind_to() -> Result<(), Box> + { + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to::() + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + async fn can_bind_to_transient() -> Result<(), Box> + { + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to::() + .await? + .in_transient_scope() + .await; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + async fn can_bind_to_transient_when_named() -> Result<(), Box> + { + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to::() + .await? + .in_transient_scope() + .await + .when_named("regular") + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + async fn can_bind_to_singleton() -> Result<(), Box> + { + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to::() + .await? + .in_singleton_scope() + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + async fn can_bind_to_singleton_when_named() -> Result<(), Box> + { + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to::() + .await? + .in_singleton_scope() + .await? + .when_named("cool") + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + #[cfg(feature = "factory")] + async fn can_bind_to_factory() -> Result<(), Box> + { + use crate as syrette; + use crate::factory; + + #[factory(threadsafe = true)] + type IUserManagerFactory = dyn Fn() -> dyn subjects_async::IUserManager; + + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to_factory(&|_| { + Box::new(|| { + let user_manager: TransientPtr = + TransientPtr::new(subjects_async::UserManager::new()); + + user_manager + }) + }) + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } + + #[tokio::test] + #[cfg(feature = "factory")] + async fn can_bind_to_factory_when_named() -> Result<(), Box> + { + use crate as syrette; + use crate::factory; + + #[factory(threadsafe = true)] + type IUserManagerFactory = dyn Fn() -> dyn subjects_async::IUserManager; + + let mut di_container = AsyncDIContainer::new(); + + { + assert_eq!(di_container.bindings.lock().await.count(), 0); + } + + di_container + .bind::() + .to_factory(&|_| { + Box::new(|| { + let user_manager: TransientPtr = + TransientPtr::new(subjects_async::UserManager::new()); + + user_manager + }) + }) + .await? + .when_named("awesome") + .await?; + + { + assert_eq!(di_container.bindings.lock().await.count(), 1); + } + + Ok(()) + } +} diff --git a/src/di_container/asynchronous/binding/mod.rs b/src/di_container/asynchronous/binding/mod.rs new file mode 100644 index 0000000..6a09bff --- /dev/null +++ b/src/di_container/asynchronous/binding/mod.rs @@ -0,0 +1,5 @@ +//! Types for building & configurating DI container bindings. + +pub mod builder; +pub mod scope_configurator; +pub mod when_configurator; diff --git a/src/di_container/asynchronous/binding/scope_configurator.rs b/src/di_container/asynchronous/binding/scope_configurator.rs new file mode 100644 index 0000000..2b0f0b3 --- /dev/null +++ b/src/di_container/asynchronous/binding/scope_configurator.rs @@ -0,0 +1,76 @@ +//! Scope configurator for a binding for types inside of a [`AsyncDIContainer`]. +use std::marker::PhantomData; +use std::sync::Arc; + +use crate::di_container::asynchronous::binding::when_configurator::AsyncBindingWhenConfigurator; +use crate::errors::async_di_container::AsyncBindingScopeConfiguratorError; +use crate::interfaces::async_injectable::AsyncInjectable; +use crate::provider::r#async::{AsyncSingletonProvider, AsyncTransientTypeProvider}; +use crate::ptr::ThreadsafeSingletonPtr; +use crate::AsyncDIContainer; + +/// Scope configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`]. +pub struct AsyncBindingScopeConfigurator +where + Interface: 'static + ?Sized + Send + Sync, + Implementation: AsyncInjectable, +{ + di_container: Arc, + interface_phantom: PhantomData, + implementation_phantom: PhantomData, +} + +impl AsyncBindingScopeConfigurator +where + Interface: 'static + ?Sized + Send + Sync, + Implementation: AsyncInjectable, +{ + pub(crate) fn new(di_container: Arc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + implementation_phantom: PhantomData, + } + } + + /// Configures the binding to be in a transient scope. + /// + /// This is the default. + pub async fn in_transient_scope(&self) -> AsyncBindingWhenConfigurator + { + let mut bindings_lock = self.di_container.bindings.lock().await; + + bindings_lock.set::( + None, + Box::new(AsyncTransientTypeProvider::::new()), + ); + + AsyncBindingWhenConfigurator::new(self.di_container.clone()) + } + + /// Configures the binding to be in a singleton scope. + /// + /// # Errors + /// Will return Err if resolving the implementation fails. + pub async fn in_singleton_scope( + &self, + ) -> Result, AsyncBindingScopeConfiguratorError> + { + let singleton: ThreadsafeSingletonPtr = + ThreadsafeSingletonPtr::from( + Implementation::resolve(&self.di_container, Vec::new()) + .await + .map_err( + AsyncBindingScopeConfiguratorError::SingletonResolveFailed, + )?, + ); + + let mut bindings_lock = self.di_container.bindings.lock().await; + + bindings_lock + .set::(None, Box::new(AsyncSingletonProvider::new(singleton))); + + Ok(AsyncBindingWhenConfigurator::new(self.di_container.clone())) + } +} diff --git a/src/di_container/asynchronous/binding/when_configurator.rs b/src/di_container/asynchronous/binding/when_configurator.rs new file mode 100644 index 0000000..b245ad8 --- /dev/null +++ b/src/di_container/asynchronous/binding/when_configurator.rs @@ -0,0 +1,54 @@ +//! When configurator for a binding for types inside of a [`AsyncDIContainer`]. +use std::any::type_name; +use std::marker::PhantomData; +use std::sync::Arc; + +use crate::errors::async_di_container::AsyncBindingWhenConfiguratorError; +use crate::AsyncDIContainer; + +/// When configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`]. +pub struct AsyncBindingWhenConfigurator +where + Interface: 'static + ?Sized + Send + Sync, +{ + di_container: Arc, + interface_phantom: PhantomData, +} + +impl AsyncBindingWhenConfigurator +where + Interface: 'static + ?Sized + Send + Sync, +{ + pub(crate) fn new(di_container: Arc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + } + } + + /// Configures the binding to have a name. + /// + /// # Errors + /// Will return Err if no binding for the interface already exists. + pub async fn when_named( + &self, + name: &'static str, + ) -> Result<(), AsyncBindingWhenConfiguratorError> + { + let mut bindings_lock = self.di_container.bindings.lock().await; + + let binding = bindings_lock.remove::(None).map_or_else( + || { + Err(AsyncBindingWhenConfiguratorError::BindingNotFound( + type_name::(), + )) + }, + Ok, + )?; + + bindings_lock.set::(Some(name), binding); + + Ok(()) + } +} diff --git a/src/di_container/asynchronous/mod.rs b/src/di_container/asynchronous/mod.rs new file mode 100644 index 0000000..b6653a5 --- /dev/null +++ b/src/di_container/asynchronous/mod.rs @@ -0,0 +1,767 @@ +//! Asynchronous dependency injection container. +//! +//! # Examples +//! ``` +//! use std::collections::HashMap; +//! use std::error::Error; +//! +//! use syrette::{injectable, AsyncDIContainer}; +//! +//! trait IDatabaseService: Send + Sync +//! { +//! fn get_all_records(&self, table_name: String) -> HashMap; +//! } +//! +//! struct DatabaseService {} +//! +//! #[injectable(IDatabaseService, async = true)] +//! impl DatabaseService +//! { +//! fn new() -> Self +//! { +//! Self {} +//! } +//! } +//! +//! impl IDatabaseService for DatabaseService +//! { +//! fn get_all_records(&self, table_name: String) -> HashMap +//! { +//! // Do stuff here +//! HashMap::::new() +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> +//! { +//! let mut di_container = AsyncDIContainer::new(); +//! +//! di_container +//! .bind::() +//! .to::() +//! .await?; +//! +//! let database_service = di_container +//! .get::() +//! .await? +//! .transient()?; +//! +//! Ok(()) +//! } +//! ``` +use std::any::type_name; +use std::sync::Arc; + +use tokio::sync::Mutex; + +use crate::di_container::asynchronous::binding::builder::AsyncBindingBuilder; +use crate::di_container::binding_map::DIContainerBindingMap; +use crate::errors::async_di_container::AsyncDIContainerError; +use crate::future::BoxFuture; +use crate::libs::intertrait::cast::error::CastError; +use crate::libs::intertrait::cast::{CastArc, CastBox}; +use crate::provider::r#async::{AsyncProvidable, IAsyncProvider}; +use crate::ptr::{SomeThreadsafePtr, TransientPtr}; + +pub mod binding; + +/// Dependency injection container. +pub struct AsyncDIContainer +{ + bindings: Mutex>, +} + +impl AsyncDIContainer +{ + /// Returns a new `AsyncDIContainer`. + #[must_use] + pub fn new() -> Arc + { + Arc::new(Self { + bindings: Mutex::new(DIContainerBindingMap::new()), + }) + } + + /// Returns a new [`AsyncBindingBuilder`] for the given interface. + #[must_use] + pub fn bind(self: &mut Arc) -> AsyncBindingBuilder + where + Interface: 'static + ?Sized + Send + Sync, + { + AsyncBindingBuilder::::new(self.clone()) + } + + /// Returns the type bound with `Interface`. + /// + /// # Errors + /// Will return `Err` if: + /// - No binding for `Interface` exists + /// - Resolving the binding for `Interface` fails + /// - Casting the binding for `Interface` fails + pub async fn get( + self: &Arc, + ) -> Result, AsyncDIContainerError> + where + Interface: 'static + ?Sized + Send + Sync, + { + self.get_bound::(Vec::new(), None).await + } + + /// Returns the type bound with `Interface` and the specified name. + /// + /// # Errors + /// Will return `Err` if: + /// - No binding for `Interface` with name `name` exists + /// - Resolving the binding for `Interface` fails + /// - Casting the binding for `Interface` fails + pub async fn get_named( + self: &Arc, + name: &'static str, + ) -> Result, AsyncDIContainerError> + where + Interface: 'static + ?Sized + Send + Sync, + { + self.get_bound::(Vec::new(), Some(name)).await + } + + #[doc(hidden)] + pub async fn get_bound( + self: &Arc, + dependency_history: Vec<&'static str>, + name: Option<&'static str>, + ) -> Result, AsyncDIContainerError> + where + Interface: 'static + ?Sized + Send + Sync, + { + let binding_providable = self + .get_binding_providable::(name, dependency_history) + .await?; + + self.handle_binding_providable(binding_providable).await + } + + async fn handle_binding_providable( + self: &Arc, + binding_providable: AsyncProvidable, + ) -> Result, AsyncDIContainerError> + where + Interface: 'static + ?Sized + Send + Sync, + { + match binding_providable { + AsyncProvidable::Transient(transient_binding) => { + Ok(SomeThreadsafePtr::Transient( + transient_binding.cast::().map_err(|_| { + AsyncDIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "transient", + } + })?, + )) + } + AsyncProvidable::Singleton(singleton_binding) => { + Ok(SomeThreadsafePtr::ThreadsafeSingleton( + singleton_binding + .cast::() + .map_err(|err| match err { + CastError::NotArcCastable(_) => { + AsyncDIContainerError::InterfaceNotAsync(type_name::< + Interface, + >( + )) + } + CastError::CastFailed { from: _, to: _ } => { + AsyncDIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "singleton", + } + } + })?, + )) + } + #[cfg(feature = "factory")] + AsyncProvidable::Factory(factory_binding) => { + use crate::interfaces::factory::IThreadsafeFactory; + + let factory = factory_binding + .cast::,), Interface>>() + .map_err(|err| match err { + CastError::NotArcCastable(_) => { + AsyncDIContainerError::InterfaceNotAsync( + type_name::(), + ) + } + CastError::CastFailed { from: _, to: _ } => { + AsyncDIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "factory", + } + } + })?; + + Ok(SomeThreadsafePtr::ThreadsafeFactory( + factory(self.clone()).into(), + )) + } + #[cfg(feature = "factory")] + AsyncProvidable::DefaultFactory(binding) => { + use crate::interfaces::factory::IThreadsafeFactory; + + let default_factory = Self::cast_factory_binding::< + dyn IThreadsafeFactory< + (Arc,), + dyn Fn<(), Output = TransientPtr> + Send + Sync, + >, + >(binding, "default factory")?; + + Ok(SomeThreadsafePtr::Transient(default_factory(self.clone())())) + } + #[cfg(feature = "factory")] + AsyncProvidable::AsyncDefaultFactory(binding) => { + use crate::interfaces::factory::IThreadsafeFactory; + + let async_default_factory = Self::cast_factory_binding::< + dyn IThreadsafeFactory< + (Arc,), + dyn Fn<(), Output = BoxFuture<'static, TransientPtr>> + + Send + + Sync, + >, + >( + binding, "async default factory" + )?; + + Ok(SomeThreadsafePtr::Transient( + async_default_factory(self.clone())().await, + )) + } + } + } + + #[cfg(feature = "factory")] + fn cast_factory_binding( + factory_binding: Arc, + binding_kind: &'static str, + ) -> Result, AsyncDIContainerError> + { + factory_binding.cast::().map_err(|err| match err { + CastError::NotArcCastable(_) => { + AsyncDIContainerError::InterfaceNotAsync(type_name::()) + } + CastError::CastFailed { from: _, to: _ } => { + AsyncDIContainerError::CastFailed { + interface: type_name::(), + binding_kind, + } + } + }) + } + + async fn get_binding_providable( + self: &Arc, + name: Option<&'static str>, + dependency_history: Vec<&'static str>, + ) -> Result + where + Interface: 'static + ?Sized + Send + Sync, + { + let provider; + + { + let bindings_lock = self.bindings.lock().await; + + provider = bindings_lock + .get::(name) + .map_or_else( + || { + Err(AsyncDIContainerError::BindingNotFound { + interface: type_name::(), + name, + }) + }, + Ok, + )? + .clone(); + } + + provider + .provide(self, dependency_history) + .await + .map_err(|err| AsyncDIContainerError::BindingResolveFailed { + reason: err, + interface: type_name::(), + }) + } +} + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use async_trait::async_trait; + use mockall::mock; + + use super::*; + use crate::errors::injectable::InjectableError; + use crate::ptr::{ThreadsafeSingletonPtr, TransientPtr}; + use crate::test_utils::subjects_async; + + #[tokio::test] + async fn can_get() -> Result<(), Box> + { + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_do_clone().returning(|| { + let mut inner_mock_provider = MockProvider::new(); + + inner_mock_provider.expect_provide().returning(|_, _| { + Ok(AsyncProvidable::Transient(TransientPtr::new( + subjects_async::UserManager::new(), + ))) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::(None, Box::new(mock_provider)); + } + + di_container + .get::() + .await? + .transient()?; + + Ok(()) + } + + #[tokio::test] + async fn can_get_named() -> Result<(), Box> + { + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_do_clone().returning(|| { + let mut inner_mock_provider = MockProvider::new(); + + inner_mock_provider.expect_provide().returning(|_, _| { + Ok(AsyncProvidable::Transient(TransientPtr::new( + subjects_async::UserManager::new(), + ))) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::( + Some("special"), + Box::new(mock_provider), + ); + } + + di_container + .get_named::("special") + .await? + .transient()?; + + Ok(()) + } + + #[tokio::test] + async fn can_get_singleton() -> Result<(), Box> + { + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + let mut singleton = ThreadsafeSingletonPtr::new(subjects_async::Number::new()); + + ThreadsafeSingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; + + mock_provider.expect_do_clone().returning(move || { + let mut inner_mock_provider = MockProvider::new(); + + let singleton_clone = singleton.clone(); + + inner_mock_provider.expect_provide().returning(move |_, _| { + Ok(AsyncProvidable::Singleton(singleton_clone.clone())) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::(None, Box::new(mock_provider)); + } + + let first_number_rc = di_container + .get::() + .await? + .threadsafe_singleton()?; + + assert_eq!(first_number_rc.get(), 2820); + + let second_number_rc = di_container + .get::() + .await? + .threadsafe_singleton()?; + + assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); + + Ok(()) + } + + #[tokio::test] + async fn can_get_singleton_named() -> Result<(), Box> + { + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + let mut singleton = ThreadsafeSingletonPtr::new(subjects_async::Number::new()); + + ThreadsafeSingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; + + mock_provider.expect_do_clone().returning(move || { + let mut inner_mock_provider = MockProvider::new(); + + let singleton_clone = singleton.clone(); + + inner_mock_provider.expect_provide().returning(move |_, _| { + Ok(AsyncProvidable::Singleton(singleton_clone.clone())) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::( + Some("cool"), + Box::new(mock_provider), + ); + } + + let first_number_rc = di_container + .get_named::("cool") + .await? + .threadsafe_singleton()?; + + assert_eq!(first_number_rc.get(), 2820); + + let second_number_rc = di_container + .get_named::("cool") + .await? + .threadsafe_singleton()?; + + assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); + + Ok(()) + } + + #[tokio::test] + #[cfg(feature = "factory")] + async fn can_get_factory() -> Result<(), Box> + { + trait IUserManager: Send + Sync + { + fn add_user(&mut self, user_id: i128); + + fn remove_user(&mut self, user_id: i128); + } + + struct UserManager + { + users: Vec, + } + + impl UserManager + { + fn new(users: Vec) -> Self + { + Self { users } + } + } + + impl IUserManager for UserManager + { + fn add_user(&mut self, user_id: i128) + { + self.users.push(user_id); + } + + fn remove_user(&mut self, user_id: i128) + { + let user_index = + self.users.iter().position(|user| *user == user_id).unwrap(); + + self.users.remove(user_index); + } + } + + use crate as syrette; + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + + #[crate::factory(threadsafe = true)] + type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; + + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_do_clone().returning(|| { + type FactoryFunc = Box< + (dyn Fn<(Vec,), Output = TransientPtr> + Send + Sync) + >; + + let mut inner_mock_provider = MockProvider::new(); + + let factory_func: &'static (dyn Fn< + (Arc,), + Output = FactoryFunc> + Send + Sync) = &|_| { + Box::new(|users| { + let user_manager: TransientPtr = + TransientPtr::new(UserManager::new(users)); + + user_manager + }) + }; + + inner_mock_provider.expect_provide().returning(|_, _| { + Ok(AsyncProvidable::Factory( + crate::ptr::ThreadsafeFactoryPtr::new( + ThreadsafeCastableFactory::new(factory_func), + ), + )) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::(None, Box::new(mock_provider)); + } + + di_container + .get::() + .await? + .threadsafe_factory()?; + + Ok(()) + } + + #[tokio::test] + #[cfg(feature = "factory")] + async fn can_get_factory_named() -> Result<(), Box> + { + trait IUserManager: Send + Sync + { + fn add_user(&mut self, user_id: i128); + + fn remove_user(&mut self, user_id: i128); + } + + struct UserManager + { + users: Vec, + } + + impl UserManager + { + fn new(users: Vec) -> Self + { + Self { users } + } + } + + impl IUserManager for UserManager + { + fn add_user(&mut self, user_id: i128) + { + self.users.push(user_id); + } + + fn remove_user(&mut self, user_id: i128) + { + let user_index = + self.users.iter().position(|user| *user == user_id).unwrap(); + + self.users.remove(user_index); + } + } + + use crate as syrette; + use crate::castable_factory::threadsafe::ThreadsafeCastableFactory; + + #[crate::factory(threadsafe = true)] + type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; + + mock! { + Provider {} + + #[async_trait] + impl IAsyncProvider for Provider + { + async fn provide( + &self, + di_container: &Arc, + dependency_history: Vec<&'static str>, + ) -> Result; + + fn do_clone(&self) -> Box; + } + } + + let di_container = AsyncDIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_do_clone().returning(|| { + type FactoryFunc = Box< + (dyn Fn<(Vec,), Output = TransientPtr> + Send + Sync) + >; + + let mut inner_mock_provider = MockProvider::new(); + + let factory_func: &'static (dyn Fn< + (Arc,), + Output = FactoryFunc> + Send + Sync) = &|_| { + Box::new(|users| { + let user_manager: TransientPtr = + TransientPtr::new(UserManager::new(users)); + + user_manager + }) + }; + + inner_mock_provider.expect_provide().returning(|_, _| { + Ok(AsyncProvidable::Factory( + crate::ptr::ThreadsafeFactoryPtr::new( + ThreadsafeCastableFactory::new(factory_func), + ), + )) + }); + + Box::new(inner_mock_provider) + }); + + { + di_container + .bindings + .lock() + .await + .set::(Some("special"), Box::new(mock_provider)); + } + + di_container + .get_named::("special") + .await? + .threadsafe_factory()?; + + Ok(()) + } +} diff --git a/src/di_container/binding_map.rs b/src/di_container/binding_map.rs new file mode 100644 index 0000000..eb71ff7 --- /dev/null +++ b/src/di_container/binding_map.rs @@ -0,0 +1,91 @@ +use std::any::TypeId; + +use ahash::AHashMap; + +#[derive(Debug, PartialEq, Eq, Hash)] +struct DIContainerBindingKey +{ + type_id: TypeId, + name: Option<&'static str>, +} + +pub struct DIContainerBindingMap +where + Provider: 'static + ?Sized, +{ + bindings: AHashMap>, +} + +impl DIContainerBindingMap +where + Provider: 'static + ?Sized, +{ + pub fn new() -> Self + { + Self { + bindings: AHashMap::new(), + } + } + + #[allow(clippy::borrowed_box)] + pub fn get(&self, name: Option<&'static str>) -> Option<&Box> + where + Interface: 'static + ?Sized, + { + let interface_typeid = TypeId::of::(); + + self.bindings.get(&DIContainerBindingKey { + type_id: interface_typeid, + name, + }) + } + + pub fn set(&mut self, name: Option<&'static str>, provider: Box) + where + Interface: 'static + ?Sized, + { + let interface_typeid = TypeId::of::(); + + self.bindings.insert( + DIContainerBindingKey { + type_id: interface_typeid, + name, + }, + provider, + ); + } + + pub fn remove( + &mut self, + name: Option<&'static str>, + ) -> Option> + where + Interface: 'static + ?Sized, + { + let interface_typeid = TypeId::of::(); + + self.bindings.remove(&DIContainerBindingKey { + type_id: interface_typeid, + name, + }) + } + + pub fn has(&self, name: Option<&'static str>) -> bool + where + Interface: 'static + ?Sized, + { + let interface_typeid = TypeId::of::(); + + self.bindings.contains_key(&DIContainerBindingKey { + type_id: interface_typeid, + name, + }) + } + + /// Only used by tests in the `di_container` module. + #[cfg(test)] + pub fn count(&self) -> usize + { + self.bindings.len() + } +} diff --git a/src/di_container/blocking/binding/builder.rs b/src/di_container/blocking/binding/builder.rs new file mode 100644 index 0000000..8e15f0c --- /dev/null +++ b/src/di_container/blocking/binding/builder.rs @@ -0,0 +1,450 @@ +//! Binding builder for types inside of a [`DIContainer`]. +use std::any::type_name; +use std::marker::PhantomData; +use std::rc::Rc; + +use crate::di_container::blocking::binding::scope_configurator::BindingScopeConfigurator; +#[cfg(feature = "factory")] +use crate::di_container::blocking::binding::when_configurator::BindingWhenConfigurator; +use crate::di_container::blocking::DIContainer; +use crate::errors::di_container::BindingBuilderError; +use crate::interfaces::injectable::Injectable; + +/// Binding builder for type `Interface` inside a [`DIContainer`]. +pub struct BindingBuilder +where + Interface: 'static + ?Sized, +{ + di_container: Rc, + interface_phantom: PhantomData, +} + +impl BindingBuilder +where + Interface: 'static + ?Sized, +{ + pub(crate) fn new(di_container: Rc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + } + } + + /// Creates a binding of type `Interface` to type `Implementation` inside of the + /// associated [`DIContainer`]. + /// + /// The scope of the binding is transient. But that can be changed by using the + /// returned [`BindingScopeConfigurator`] + /// + /// # Errors + /// Will return Err if the associated [`DIContainer`] already have a binding for + /// the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::{DIContainer, injectable}; + /// # + /// # trait Foo {} + /// # + /// # struct Bar {} + /// # + /// # #[injectable(Foo)] + /// # impl Bar { + /// # fn new() -> Self + /// # { + /// # Self {} + /// # } + /// # } + /// # + /// # impl Foo for Bar {} + /// # + /// # fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = DIContainer::new(); + /// # + /// di_container.bind::().to::(); + /// # + /// # Ok(()) + /// # } + /// ``` + pub fn to( + &self, + ) -> Result, BindingBuilderError> + where + Implementation: Injectable, + { + { + let bindings = self.di_container.bindings.borrow(); + + if bindings.has::(None) { + return Err(BindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + } + + let binding_scope_configurator = + BindingScopeConfigurator::new(self.di_container.clone()); + + binding_scope_configurator.in_transient_scope(); + + Ok(binding_scope_configurator) + } + + /// Creates a binding of factory type `Interface` to a factory inside of the + /// associated [`DIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`DIContainer`] already have a binding for + /// the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::{DIContainer, factory}; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait ICustomerID {} + /// # trait ICustomer {} + /// # + /// # struct Customer + /// # { + /// # name: String, + /// # id: TransientPtr + /// # } + /// # + /// # impl Customer { + /// # fn new(name: String, id: TransientPtr) -> Self + /// # { + /// # Self { name, id } + /// # } + /// # } + /// # + /// # impl ICustomer for Customer {} + /// # + /// # #[factory] + /// # type ICustomerFactory = dyn Fn(String, u32) -> dyn ICustomer; + /// # + /// # #[factory] + /// # type ICustomerIDFactory = dyn Fn(u32) -> dyn ICustomerID; + /// # + /// # fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = DIContainer::new(); + /// # + /// di_container + /// .bind::() + /// .to_factory(&|context| { + /// Box::new(move |name, id| { + /// let customer_id_factory = context + /// .get::() + /// .unwrap() + /// .factory() + /// .unwrap(); + /// + /// let customer_id = customer_id_factory(id); + /// + /// let customer = TransientPtr::new(Customer::new(name, customer_id)); + /// + /// customer as TransientPtr + /// }) + /// }); + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub fn to_factory( + &self, + factory_func: &'static Func, + ) -> Result, BindingBuilderError> + where + Args: 'static, + Return: 'static + ?Sized, + Interface: Fn>, + Func: Fn<(std::rc::Rc,), Output = Box>, + { + use crate::castable_factory::blocking::CastableFactory; + + { + let bindings = self.di_container.bindings.borrow(); + + if bindings.has::(None) { + return Err(BindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + } + + let mut bindings_mut = self.di_container.bindings.borrow_mut(); + + let factory_impl = CastableFactory::new(factory_func); + + bindings_mut.set::( + None, + Box::new(crate::provider::blocking::FactoryProvider::new( + crate::ptr::FactoryPtr::new(factory_impl), + false, + )), + ); + + Ok(BindingWhenConfigurator::new(self.di_container.clone())) + } + + /// Creates a binding of type `Interface` to a factory that takes no arguments + /// inside of the associated [`DIContainer`]. + /// + /// # Errors + /// Will return Err if the associated [`DIContainer`] already have a binding for + /// the interface. + /// + /// # Examples + /// ``` + /// # use std::error::Error; + /// # + /// # use syrette::{DIContainer, factory}; + /// # use syrette::ptr::TransientPtr; + /// # + /// # trait IBuffer {} + /// # + /// # struct Buffer + /// # { + /// # buf: [u8; SIZE] + /// # } + /// # + /// # impl Buffer + /// # { + /// # fn new() -> Self + /// # { + /// # Self { + /// # buf: [0; SIZE] + /// # } + /// # } + /// # } + /// # + /// # impl IBuffer for Buffer {} + /// # + /// # const BUFFER_SIZE: usize = 12; + /// # + /// # fn main() -> Result<(), Box> + /// # { + /// # let mut di_container = DIContainer::new(); + /// # + /// di_container.bind::().to_default_factory(&|_| { + /// Box::new(|| { + /// let buffer = TransientPtr::new(Buffer::::new()); + /// + /// buffer as TransientPtr + /// }) + /// }); + /// # + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "factory")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] + pub fn to_default_factory( + &self, + factory_func: &'static FactoryFunc, + ) -> Result, BindingBuilderError> + where + Return: 'static + ?Sized, + FactoryFunc: Fn< + (Rc,), + Output = crate::ptr::TransientPtr< + dyn Fn<(), Output = crate::ptr::TransientPtr>, + >, + >, + { + use crate::castable_factory::blocking::CastableFactory; + + { + let bindings = self.di_container.bindings.borrow(); + + if bindings.has::(None) { + return Err(BindingBuilderError::BindingAlreadyExists(type_name::< + Interface, + >( + ))); + } + } + + let mut bindings_mut = self.di_container.bindings.borrow_mut(); + + let factory_impl = CastableFactory::new(factory_func); + + bindings_mut.set::( + None, + Box::new(crate::provider::blocking::FactoryProvider::new( + crate::ptr::FactoryPtr::new(factory_impl), + true, + )), + ); + + Ok(BindingWhenConfigurator::new(self.di_container.clone())) + } +} + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use super::*; + use crate::ptr::TransientPtr; + use crate::test_utils::subjects; + + #[test] + fn can_bind_to() -> Result<(), Box> + { + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to::()?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + fn can_bind_to_transient() -> Result<(), Box> + { + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to::()? + .in_transient_scope(); + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + fn can_bind_to_transient_when_named() -> Result<(), Box> + { + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to::()? + .in_transient_scope() + .when_named("regular")?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + fn can_bind_to_singleton() -> Result<(), Box> + { + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to::()? + .in_singleton_scope()?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + fn can_bind_to_singleton_when_named() -> Result<(), Box> + { + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to::()? + .in_singleton_scope()? + .when_named("cool")?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + #[cfg(feature = "factory")] + fn can_bind_to_factory() -> Result<(), Box> + { + use crate as syrette; + use crate::factory; + + #[factory] + type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; + + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to_factory(&|_| { + Box::new(move || { + let user_manager: TransientPtr = + TransientPtr::new(subjects::UserManager::new()); + + user_manager + }) + })?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } + + #[test] + #[cfg(feature = "factory")] + fn can_bind_to_factory_when_named() -> Result<(), Box> + { + use crate as syrette; + use crate::factory; + + #[factory] + type IUserManagerFactory = dyn Fn() -> dyn subjects::IUserManager; + + let mut di_container = DIContainer::new(); + + assert_eq!(di_container.bindings.borrow().count(), 0); + + di_container + .bind::() + .to_factory(&|_| { + Box::new(move || { + let user_manager: TransientPtr = + TransientPtr::new(subjects::UserManager::new()); + + user_manager + }) + })? + .when_named("awesome")?; + + assert_eq!(di_container.bindings.borrow().count(), 1); + + Ok(()) + } +} diff --git a/src/di_container/blocking/binding/mod.rs b/src/di_container/blocking/binding/mod.rs new file mode 100644 index 0000000..6a09bff --- /dev/null +++ b/src/di_container/blocking/binding/mod.rs @@ -0,0 +1,5 @@ +//! Types for building & configurating DI container bindings. + +pub mod builder; +pub mod scope_configurator; +pub mod when_configurator; diff --git a/src/di_container/blocking/binding/scope_configurator.rs b/src/di_container/blocking/binding/scope_configurator.rs new file mode 100644 index 0000000..09897b6 --- /dev/null +++ b/src/di_container/blocking/binding/scope_configurator.rs @@ -0,0 +1,72 @@ +//! Scope configurator for a binding for types inside of a [`DIContainer`]. +use std::marker::PhantomData; +use std::rc::Rc; + +use crate::di_container::blocking::binding::when_configurator::BindingWhenConfigurator; +use crate::di_container::blocking::DIContainer; +use crate::errors::di_container::BindingScopeConfiguratorError; +use crate::interfaces::injectable::Injectable; +use crate::provider::blocking::{SingletonProvider, TransientTypeProvider}; +use crate::ptr::SingletonPtr; + +/// Scope configurator for a binding for type 'Interface' inside a [`DIContainer`]. +pub struct BindingScopeConfigurator +where + Interface: 'static + ?Sized, + Implementation: Injectable, +{ + di_container: Rc, + interface_phantom: PhantomData, + implementation_phantom: PhantomData, +} + +impl BindingScopeConfigurator +where + Interface: 'static + ?Sized, + Implementation: Injectable, +{ + pub(crate) fn new(di_container: Rc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + implementation_phantom: PhantomData, + } + } + + /// Configures the binding to be in a transient scope. + /// + /// This is the default. + #[allow(clippy::must_use_candidate)] + pub fn in_transient_scope(&self) -> BindingWhenConfigurator + { + let mut bindings_mut = self.di_container.bindings.borrow_mut(); + + bindings_mut.set::( + None, + Box::new(TransientTypeProvider::::new()), + ); + + BindingWhenConfigurator::new(self.di_container.clone()) + } + + /// Configures the binding to be in a singleton scope. + /// + /// # Errors + /// Will return Err if resolving the implementation fails. + pub fn in_singleton_scope( + &self, + ) -> Result, BindingScopeConfiguratorError> + { + let singleton: SingletonPtr = SingletonPtr::from( + Implementation::resolve(&self.di_container, Vec::new()) + .map_err(BindingScopeConfiguratorError::SingletonResolveFailed)?, + ); + + let mut bindings_mut = self.di_container.bindings.borrow_mut(); + + bindings_mut.set::(None, Box::new(SingletonProvider::new(singleton))); + + Ok(BindingWhenConfigurator::new(self.di_container.clone())) + } +} diff --git a/src/di_container/blocking/binding/when_configurator.rs b/src/di_container/blocking/binding/when_configurator.rs new file mode 100644 index 0000000..9cd9bb6 --- /dev/null +++ b/src/di_container/blocking/binding/when_configurator.rs @@ -0,0 +1,55 @@ +//! When configurator for a binding for types inside of a [`DIContainer`]. +use std::any::type_name; +use std::marker::PhantomData; +use std::rc::Rc; + +use crate::di_container::blocking::DIContainer; +use crate::errors::di_container::BindingWhenConfiguratorError; + +/// When configurator for a binding for type 'Interface' inside a [`DIContainer`]. +pub struct BindingWhenConfigurator +where + Interface: 'static + ?Sized, +{ + di_container: Rc, + interface_phantom: PhantomData, +} + +impl BindingWhenConfigurator +where + Interface: 'static + ?Sized, +{ + pub(crate) fn new(di_container: Rc) -> Self + { + Self { + di_container, + interface_phantom: PhantomData, + } + } + + /// Configures the binding to have a name. + /// + /// # Errors + /// Will return Err if no binding for the interface already exists. + pub fn when_named( + &self, + name: &'static str, + ) -> Result<(), BindingWhenConfiguratorError> + { + let mut bindings_mut = self.di_container.bindings.borrow_mut(); + + let binding = bindings_mut.remove::(None).map_or_else( + || { + Err(BindingWhenConfiguratorError::BindingNotFound(type_name::< + Interface, + >( + ))) + }, + Ok, + )?; + + bindings_mut.set::(Some(name), binding); + + Ok(()) + } +} diff --git a/src/di_container/blocking/mod.rs b/src/di_container/blocking/mod.rs new file mode 100644 index 0000000..f6b64fa --- /dev/null +++ b/src/di_container/blocking/mod.rs @@ -0,0 +1,599 @@ +//! Blocking dependency injection container. +//! +//! # Examples +//! ``` +//! use std::collections::HashMap; +//! use std::error::Error; +//! +//! use syrette::{injectable, DIContainer}; +//! +//! trait IDatabaseService +//! { +//! fn get_all_records(&self, table_name: String) -> HashMap; +//! } +//! +//! struct DatabaseService {} +//! +//! #[injectable(IDatabaseService)] +//! impl DatabaseService +//! { +//! fn new() -> Self +//! { +//! Self {} +//! } +//! } +//! +//! impl IDatabaseService for DatabaseService +//! { +//! fn get_all_records(&self, table_name: String) -> HashMap +//! { +//! // Do stuff here +//! HashMap::::new() +//! } +//! } +//! +//! fn main() -> Result<(), Box> +//! { +//! let mut di_container = DIContainer::new(); +//! +//! di_container +//! .bind::() +//! .to::() +//! .map_err(|err| err.to_string())?; +//! +//! let database_service = di_container +//! .get::() +//! .map_err(|err| err.to_string())? +//! .transient()?; +//! +//! Ok(()) +//! } +//! ``` +use std::any::type_name; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::di_container::binding_map::DIContainerBindingMap; +use crate::di_container::blocking::binding::builder::BindingBuilder; +use crate::errors::di_container::DIContainerError; +use crate::libs::intertrait::cast::{CastBox, CastRc}; +use crate::provider::blocking::{IProvider, Providable}; +use crate::ptr::SomePtr; + +pub mod binding; + +/// Dependency injection container. +pub struct DIContainer +{ + bindings: RefCell>, +} + +impl DIContainer +{ + /// Returns a new `DIContainer`. + #[must_use] + pub fn new() -> Rc + { + Rc::new(Self { + bindings: RefCell::new(DIContainerBindingMap::new()), + }) + } + + /// Returns a new [`BindingBuilder`] for the given interface. + #[must_use] + pub fn bind(self: &mut Rc) -> BindingBuilder + where + Interface: 'static + ?Sized, + { + BindingBuilder::::new(self.clone()) + } + + /// Returns the type bound with `Interface`. + /// + /// # Errors + /// Will return `Err` if: + /// - No binding for `Interface` exists + /// - Resolving the binding for `Interface` fails + /// - Casting the binding for `Interface` fails + pub fn get(self: &Rc) -> Result, DIContainerError> + where + Interface: 'static + ?Sized, + { + self.get_bound::(Vec::new(), None) + } + + /// Returns the type bound with `Interface` and the specified name. + /// + /// # Errors + /// Will return `Err` if: + /// - No binding for `Interface` with name `name` exists + /// - Resolving the binding for `Interface` fails + /// - Casting the binding for `Interface` fails + pub fn get_named( + self: &Rc, + name: &'static str, + ) -> Result, DIContainerError> + where + Interface: 'static + ?Sized, + { + self.get_bound::(Vec::new(), Some(name)) + } + + #[doc(hidden)] + pub fn get_bound( + self: &Rc, + dependency_history: Vec<&'static str>, + name: Option<&'static str>, + ) -> Result, DIContainerError> + where + Interface: 'static + ?Sized, + { + let binding_providable = + self.get_binding_providable::(name, dependency_history)?; + + self.handle_binding_providable(binding_providable) + } + + fn handle_binding_providable( + self: &Rc, + binding_providable: Providable, + ) -> Result, DIContainerError> + where + Interface: 'static + ?Sized, + { + match binding_providable { + Providable::Transient(transient_binding) => Ok(SomePtr::Transient( + transient_binding.cast::().map_err(|_| { + DIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "transient", + } + })?, + )), + Providable::Singleton(singleton_binding) => Ok(SomePtr::Singleton( + singleton_binding.cast::().map_err(|_| { + DIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "singleton", + } + })?, + )), + #[cfg(feature = "factory")] + Providable::Factory(factory_binding) => { + use crate::interfaces::factory::IFactory; + + let factory = factory_binding + .cast::,), Interface>>() + .map_err(|_| DIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "factory", + })?; + + Ok(SomePtr::Factory(factory(self.clone()).into())) + } + #[cfg(feature = "factory")] + Providable::DefaultFactory(factory_binding) => { + use crate::interfaces::factory::IFactory; + use crate::ptr::TransientPtr; + + let default_factory = factory_binding + .cast::,), + dyn Fn<(), Output = TransientPtr>, + >>() + .map_err(|_| DIContainerError::CastFailed { + interface: type_name::(), + binding_kind: "default factory", + })?; + + Ok(SomePtr::Transient(default_factory(self.clone())())) + } + } + } + + fn get_binding_providable( + self: &Rc, + name: Option<&'static str>, + dependency_history: Vec<&'static str>, + ) -> Result + where + Interface: 'static + ?Sized, + { + self.bindings + .borrow() + .get::(name) + .map_or_else( + || { + Err(DIContainerError::BindingNotFound { + interface: type_name::(), + name, + }) + }, + Ok, + )? + .provide(self, dependency_history) + .map_err(|err| DIContainerError::BindingResolveFailed { + reason: err, + interface: type_name::(), + }) + } +} + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use mockall::mock; + + use super::*; + use crate::errors::injectable::InjectableError; + use crate::provider::blocking::IProvider; + use crate::ptr::{SingletonPtr, TransientPtr}; + use crate::test_utils::subjects; + + #[test] + fn can_get() -> Result<(), Box> + { + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_provide().returning(|_, _| { + Ok(Providable::Transient(TransientPtr::new( + subjects::UserManager::new(), + ))) + }); + + di_container + .bindings + .borrow_mut() + .set::(None, Box::new(mock_provider)); + + di_container + .get::()? + .transient()?; + + Ok(()) + } + + #[test] + fn can_get_named() -> Result<(), Box> + { + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_provide().returning(|_, _| { + Ok(Providable::Transient(TransientPtr::new( + subjects::UserManager::new(), + ))) + }); + + di_container + .bindings + .borrow_mut() + .set::(Some("special"), Box::new(mock_provider)); + + di_container + .get_named::("special")? + .transient()?; + + Ok(()) + } + + #[test] + fn can_get_singleton() -> Result<(), Box> + { + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + let mut singleton = SingletonPtr::new(subjects::Number::new()); + + SingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; + + mock_provider + .expect_provide() + .returning_st(move |_, _| Ok(Providable::Singleton(singleton.clone()))); + + di_container + .bindings + .borrow_mut() + .set::(None, Box::new(mock_provider)); + + let first_number_rc = di_container.get::()?.singleton()?; + + assert_eq!(first_number_rc.get(), 2820); + + let second_number_rc = + di_container.get::()?.singleton()?; + + assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); + + Ok(()) + } + + #[test] + fn can_get_singleton_named() -> Result<(), Box> + { + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let mut mock_provider = MockProvider::new(); + + let mut singleton = SingletonPtr::new(subjects::Number::new()); + + SingletonPtr::get_mut(&mut singleton).unwrap().num = 2820; + + mock_provider + .expect_provide() + .returning_st(move |_, _| Ok(Providable::Singleton(singleton.clone()))); + + di_container + .bindings + .borrow_mut() + .set::(Some("cool"), Box::new(mock_provider)); + + let first_number_rc = di_container + .get_named::("cool")? + .singleton()?; + + assert_eq!(first_number_rc.get(), 2820); + + let second_number_rc = di_container + .get_named::("cool")? + .singleton()?; + + assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref()); + + Ok(()) + } + + #[test] + #[cfg(feature = "factory")] + fn can_get_factory() -> Result<(), Box> + { + use crate::castable_factory::blocking::CastableFactory; + use crate::ptr::FactoryPtr; + + trait IUserManager + { + fn add_user(&mut self, user_id: i128); + + fn remove_user(&mut self, user_id: i128); + } + + struct UserManager + { + users: Vec, + } + + impl UserManager + { + fn new(users: Vec) -> Self + { + Self { users } + } + } + + impl IUserManager for UserManager + { + fn add_user(&mut self, user_id: i128) + { + self.users.push(user_id); + } + + fn remove_user(&mut self, user_id: i128) + { + let user_index = + self.users.iter().position(|user| *user == user_id).unwrap(); + + self.users.remove(user_index); + } + } + + type FactoryFunc = dyn Fn< + (std::rc::Rc,), + Output = Box< + dyn Fn<(Vec,), Output = crate::ptr::TransientPtr>, + >, + >; + + use crate as syrette; + + #[crate::factory] + type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; + + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let factory_func: &'static FactoryFunc = &|_: Rc| { + Box::new(move |users| { + let user_manager: TransientPtr = + TransientPtr::new(UserManager::new(users)); + + user_manager + }) + }; + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_provide().returning_st(|_, _| { + Ok(Providable::Factory(FactoryPtr::new(CastableFactory::new( + factory_func, + )))) + }); + + di_container + .bindings + .borrow_mut() + .set::(None, Box::new(mock_provider)); + + di_container.get::()?.factory()?; + + Ok(()) + } + + #[test] + #[cfg(feature = "factory")] + fn can_get_factory_named() -> Result<(), Box> + { + use crate::castable_factory::blocking::CastableFactory; + use crate::ptr::FactoryPtr; + + trait IUserManager + { + fn add_user(&mut self, user_id: i128); + + fn remove_user(&mut self, user_id: i128); + } + + struct UserManager + { + users: Vec, + } + + impl UserManager + { + fn new(users: Vec) -> Self + { + Self { users } + } + } + + impl IUserManager for UserManager + { + fn add_user(&mut self, user_id: i128) + { + self.users.push(user_id); + } + + fn remove_user(&mut self, user_id: i128) + { + let user_index = + self.users.iter().position(|user| *user == user_id).unwrap(); + + self.users.remove(user_index); + } + } + + type FactoryFunc = dyn Fn< + (std::rc::Rc,), + Output = Box< + dyn Fn<(Vec,), Output = crate::ptr::TransientPtr>, + >, + >; + + use crate as syrette; + + #[crate::factory] + type IUserManagerFactory = dyn Fn(Vec) -> dyn IUserManager; + + mock! { + Provider {} + + impl IProvider for Provider + { + fn provide( + &self, + di_container: &Rc, + dependency_history: Vec<&'static str>, + ) -> Result; + } + } + + let di_container = DIContainer::new(); + + let factory_func: &'static FactoryFunc = &|_: Rc| { + Box::new(move |users| { + let user_manager: TransientPtr = + TransientPtr::new(UserManager::new(users)); + + user_manager + }) + }; + + let mut mock_provider = MockProvider::new(); + + mock_provider.expect_provide().returning_st(|_, _| { + Ok(Providable::Factory(FactoryPtr::new(CastableFactory::new( + factory_func, + )))) + }); + + di_container + .bindings + .borrow_mut() + .set::(Some("special"), Box::new(mock_provider)); + + di_container + .get_named::("special")? + .factory()?; + + Ok(()) + } +} diff --git a/src/di_container/mod.rs b/src/di_container/mod.rs new file mode 100644 index 0000000..bf2c7a0 --- /dev/null +++ b/src/di_container/mod.rs @@ -0,0 +1,9 @@ +//! Dependency injection container types. + +#[cfg(feature = "async")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "async")))] +pub mod asynchronous; + +pub mod blocking; + +pub(crate) mod binding_map; diff --git a/src/di_container_binding_map.rs b/src/di_container_binding_map.rs deleted file mode 100644 index eb71ff7..0000000 --- a/src/di_container_binding_map.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::any::TypeId; - -use ahash::AHashMap; - -#[derive(Debug, PartialEq, Eq, Hash)] -struct DIContainerBindingKey -{ - type_id: TypeId, - name: Option<&'static str>, -} - -pub struct DIContainerBindingMap -where - Provider: 'static + ?Sized, -{ - bindings: AHashMap>, -} - -impl DIContainerBindingMap -where - Provider: 'static + ?Sized, -{ - pub fn new() -> Self - { - Self { - bindings: AHashMap::new(), - } - } - - #[allow(clippy::borrowed_box)] - pub fn get(&self, name: Option<&'static str>) -> Option<&Box> - where - Interface: 'static + ?Sized, - { - let interface_typeid = TypeId::of::(); - - self.bindings.get(&DIContainerBindingKey { - type_id: interface_typeid, - name, - }) - } - - pub fn set(&mut self, name: Option<&'static str>, provider: Box) - where - Interface: 'static + ?Sized, - { - let interface_typeid = TypeId::of::(); - - self.bindings.insert( - DIContainerBindingKey { - type_id: interface_typeid, - name, - }, - provider, - ); - } - - pub fn remove( - &mut self, - name: Option<&'static str>, - ) -> Option> - where - Interface: 'static + ?Sized, - { - let interface_typeid = TypeId::of::(); - - self.bindings.remove(&DIContainerBindingKey { - type_id: interface_typeid, - name, - }) - } - - pub fn has(&self, name: Option<&'static str>) -> bool - where - Interface: 'static + ?Sized, - { - let interface_typeid = TypeId::of::(); - - self.bindings.contains_key(&DIContainerBindingKey { - type_id: interface_typeid, - name, - }) - } - - /// Only used by tests in the `di_container` module. - #[cfg(test)] - pub fn count(&self) -> usize - { - self.bindings.len() - } -} diff --git a/src/interfaces/async_injectable.rs b/src/interfaces/async_injectable.rs index 69c2a47..dadb603 100644 --- a/src/interfaces/async_injectable.rs +++ b/src/interfaces/async_injectable.rs @@ -2,11 +2,11 @@ use std::fmt::Debug; use std::sync::Arc; -use crate::async_di_container::AsyncDIContainer; use crate::errors::injectable::InjectableError; use crate::future::BoxFuture; use crate::libs::intertrait::CastFromSync; use crate::ptr::TransientPtr; +use crate::AsyncDIContainer; /// Interface for structs that can be injected into or be injected to. pub trait AsyncInjectable: CastFromSync diff --git a/src/lib.rs b/src/lib.rs index a462c21..d01ecc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,18 +14,14 @@ pub mod errors; pub mod interfaces; pub mod ptr; -#[cfg(feature = "async")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "async")))] -pub mod async_di_container; - #[cfg(feature = "async")] #[cfg_attr(doc_cfg, doc(cfg(feature = "async")))] pub mod future; #[cfg(feature = "async")] #[cfg_attr(doc_cfg, doc(cfg(feature = "async")))] -pub use async_di_container::AsyncDIContainer; -pub use di_container::DIContainer; +pub use di_container::asynchronous::AsyncDIContainer; +pub use di_container::blocking::DIContainer; #[cfg(feature = "factory")] #[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))] pub use syrette_macros::{declare_default_factory, factory}; @@ -42,9 +38,11 @@ pub mod dependency_trace; pub mod libs; // Private -mod di_container_binding_map; mod provider; +#[cfg(test)] +mod test_utils; + /// Shortcut for creating a DI container binding for a injectable without a declared /// interface. /// diff --git a/src/provider/async.rs b/src/provider/async.rs index 9925079..ec05b06 100644 --- a/src/provider/async.rs +++ b/src/provider/async.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use async_trait::async_trait; -use crate::async_di_container::AsyncDIContainer; use crate::errors::injectable::InjectableError; use crate::interfaces::async_injectable::AsyncInjectable; use crate::ptr::{ThreadsafeSingletonPtr, TransientPtr}; +use crate::AsyncDIContainer; #[derive(strum_macros::Display, Debug)] pub enum AsyncProvidable diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..1fc7015 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,253 @@ +pub mod subjects +{ + //! Test subjects. + + use std::fmt::Debug; + use std::rc::Rc; + + use syrette_macros::declare_interface; + + use crate::interfaces::injectable::Injectable; + use crate::ptr::TransientPtr; + use crate::DIContainer; + + pub trait IUserManager + { + fn add_user(&self, user_id: i128); + + fn remove_user(&self, user_id: i128); + } + + pub struct UserManager {} + + impl UserManager + { + pub fn new() -> Self + { + Self {} + } + } + + impl IUserManager for UserManager + { + fn add_user(&self, _user_id: i128) + { + // ... + } + + fn remove_user(&self, _user_id: i128) + { + // ... + } + } + + use crate as syrette; + + declare_interface!(UserManager -> IUserManager); + + impl Injectable for UserManager + { + fn resolve( + _di_container: &Rc, + _dependency_history: Vec<&'static str>, + ) -> Result, crate::errors::injectable::InjectableError> + where + Self: Sized, + { + Ok(TransientPtr::new(Self::new())) + } + } + + pub trait INumber + { + fn get(&self) -> i32; + + fn set(&mut self, number: i32); + } + + impl PartialEq for dyn INumber + { + fn eq(&self, other: &Self) -> bool + { + self.get() == other.get() + } + } + + impl Debug for dyn INumber + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.write_str(format!("{}", self.get()).as_str()) + } + } + + pub struct Number + { + pub num: i32, + } + + impl Number + { + pub fn new() -> Self + { + Self { num: 0 } + } + } + + impl INumber for Number + { + fn get(&self) -> i32 + { + self.num + } + + fn set(&mut self, number: i32) + { + self.num = number; + } + } + + declare_interface!(Number -> INumber); + + impl Injectable for Number + { + fn resolve( + _di_container: &Rc, + _dependency_history: Vec<&'static str>, + ) -> Result, crate::errors::injectable::InjectableError> + where + Self: Sized, + { + Ok(TransientPtr::new(Self::new())) + } + } +} + +#[cfg(feature = "async")] +pub mod subjects_async +{ + //! Test subjects. + + use std::fmt::Debug; + use std::sync::Arc; + + use async_trait::async_trait; + use syrette_macros::declare_interface; + + use crate::interfaces::async_injectable::AsyncInjectable; + use crate::ptr::TransientPtr; + use crate::AsyncDIContainer; + + pub trait IUserManager: Send + Sync + { + fn add_user(&self, user_id: i128); + + fn remove_user(&self, user_id: i128); + } + + pub struct UserManager {} + + impl UserManager + { + pub fn new() -> Self + { + Self {} + } + } + + impl IUserManager for UserManager + { + fn add_user(&self, _user_id: i128) + { + // ... + } + + fn remove_user(&self, _user_id: i128) + { + // ... + } + } + + use crate as syrette; + + declare_interface!(UserManager -> IUserManager); + + #[async_trait] + impl AsyncInjectable for UserManager + { + async fn resolve( + _: &Arc, + _dependency_history: Vec<&'static str>, + ) -> Result, crate::errors::injectable::InjectableError> + where + Self: Sized, + { + Ok(TransientPtr::new(Self::new())) + } + } + + pub trait INumber: Send + Sync + { + fn get(&self) -> i32; + + fn set(&mut self, number: i32); + } + + impl PartialEq for dyn INumber + { + fn eq(&self, other: &Self) -> bool + { + self.get() == other.get() + } + } + + impl Debug for dyn INumber + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.write_str(format!("{}", self.get()).as_str()) + } + } + + pub struct Number + { + pub num: i32, + } + + impl Number + { + pub fn new() -> Self + { + Self { num: 0 } + } + } + + impl INumber for Number + { + fn get(&self) -> i32 + { + self.num + } + + fn set(&mut self, number: i32) + { + self.num = number; + } + } + + declare_interface!(Number -> INumber, async = true); + + #[async_trait] + impl AsyncInjectable for Number + { + async fn resolve( + _: &Arc, + _dependency_history: Vec<&'static str>, + ) -> Result, crate::errors::injectable::InjectableError> + where + Self: Sized, + { + Ok(TransientPtr::new(Self::new())) + } + } +} -- cgit v1.2.3-18-g5258