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 --- 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 ++ 4 files changed, 788 insertions(+) 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 (limited to 'src/di_container/asynchronous/binding') 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(()) + } +} -- cgit v1.2.3-18-g5258