aboutsummaryrefslogtreecommitdiff
path: root/src/async_di_container.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-08-29 20:52:56 +0200
committerHampusM <hampus@hampusmat.com>2022-08-29 21:01:32 +0200
commit080cc42bb1da09059dbc35049a7ded0649961e0c (patch)
tree307ee564124373616022c1ba2b4d5af80845cd92 /src/async_di_container.rs
parent6e31d8f9e46fece348f329763b39b9c6f2741c07 (diff)
feat: implement async functionality
Diffstat (limited to 'src/async_di_container.rs')
-rw-r--r--src/async_di_container.rs1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/async_di_container.rs b/src/async_di_container.rs
new file mode 100644
index 0000000..374746f
--- /dev/null
+++ b/src/async_di_container.rs
@@ -0,0 +1,1110 @@
+//! Asynchronous dependency injection container.
+//!
+//! # Examples
+//! ```
+//! use std::collections::HashMap;
+//! use std::error::Error;
+//!
+//! use syrette::{injectable, AsyncDIContainer};
+//!
+//! trait IDatabaseService
+//! {
+//! fn get_all_records(&self, table_name: String) -> HashMap<String, String>;
+//! }
+//!
+//! 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<String, String>
+//! {
+//! // Do stuff here
+//! HashMap::<String, String>::new()
+//! }
+//! }
+//!
+//! #[tokio::main]
+//! async fn main() -> Result<(), Box<dyn Error>>
+//! {
+//! let mut di_container = AsyncDIContainer::new();
+//!
+//! di_container
+//! .bind::<dyn IDatabaseService>()
+//! .to::<DatabaseService>()?;
+//!
+//! let database_service = di_container
+//! .get::<dyn IDatabaseService>()
+//! .await?
+//! .transient()?;
+//!
+//! Ok(())
+//! }
+//! ```
+//!
+//! ---
+//!
+//! *This module is only available if Syrette is built with the "async" feature.*
+use std::any::type_name;
+use std::marker::PhantomData;
+
+#[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::interfaces::async_injectable::AsyncInjectable;
+use crate::libs::intertrait::cast::{CastArc, CastBox};
+use crate::provider::r#async::{
+ AsyncProvidable,
+ AsyncSingletonProvider,
+ AsyncTransientTypeProvider,
+ IAsyncProvider,
+};
+use crate::ptr::{SomeThreadsafePtr, ThreadsafeSingletonPtr};
+
+/// When configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`].
+pub struct AsyncBindingWhenConfigurator<'di_container, Interface>
+where
+ Interface: 'static + ?Sized,
+{
+ di_container: &'di_container mut AsyncDIContainer,
+ interface_phantom: PhantomData<Interface>,
+}
+
+impl<'di_container, Interface> AsyncBindingWhenConfigurator<'di_container, Interface>
+where
+ Interface: 'static + ?Sized,
+{
+ fn new(di_container: &'di_container mut AsyncDIContainer) -> 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(
+ &mut self,
+ name: &'static str,
+ ) -> Result<(), AsyncBindingWhenConfiguratorError>
+ {
+ let binding = self
+ .di_container
+ .bindings
+ .remove::<Interface>(None)
+ .map_or_else(
+ || {
+ Err(AsyncBindingWhenConfiguratorError::BindingNotFound(
+ type_name::<Interface>(),
+ ))
+ },
+ Ok,
+ )?;
+
+ self.di_container
+ .bindings
+ .set::<Interface>(Some(name), binding);
+
+ Ok(())
+ }
+}
+
+/// Scope configurator for a binding for type 'Interface' inside a [`AsyncDIContainer`].
+pub struct AsyncBindingScopeConfigurator<'di_container, Interface, Implementation>
+where
+ Interface: 'static + ?Sized,
+ Implementation: AsyncInjectable,
+{
+ di_container: &'di_container mut AsyncDIContainer,
+ interface_phantom: PhantomData<Interface>,
+ implementation_phantom: PhantomData<Implementation>,
+}
+
+impl<'di_container, Interface, Implementation>
+ AsyncBindingScopeConfigurator<'di_container, Interface, Implementation>
+where
+ Interface: 'static + ?Sized,
+ Implementation: AsyncInjectable,
+{
+ fn new(di_container: &'di_container mut AsyncDIContainer) -> Self
+ {
+ Self {
+ di_container,
+ interface_phantom: PhantomData,
+ implementation_phantom: PhantomData,
+ }
+ }
+
+ /// Configures the binding to be in a transient scope.
+ ///
+ /// This is the default.
+ pub fn in_transient_scope(&mut self) -> AsyncBindingWhenConfigurator<Interface>
+ {
+ self.di_container.bindings.set::<Interface>(
+ None,
+ Box::new(AsyncTransientTypeProvider::<Implementation>::new()),
+ );
+
+ AsyncBindingWhenConfigurator::new(self.di_container)
+ }
+
+ /// Configures the binding to be in a singleton scope.
+ ///
+ /// # Errors
+ /// Will return Err if resolving the implementation fails.
+ pub async fn in_singleton_scope(
+ &mut self,
+ ) -> Result<AsyncBindingWhenConfigurator<Interface>, AsyncBindingScopeConfiguratorError>
+ {
+ let singleton: ThreadsafeSingletonPtr<Implementation> =
+ ThreadsafeSingletonPtr::from(
+ Implementation::resolve(self.di_container, Vec::new())
+ .await
+ .map_err(
+ AsyncBindingScopeConfiguratorError::SingletonResolveFailed,
+ )?,
+ );
+
+ self.di_container
+ .bindings
+ .set::<Interface>(None, Box::new(AsyncSingletonProvider::new(singleton)));
+
+ Ok(AsyncBindingWhenConfigurator::new(self.di_container))
+ }
+}
+
+/// Binding builder for type `Interface` inside a [`AsyncDIContainer`].
+pub struct AsyncBindingBuilder<'di_container, Interface>
+where
+ Interface: 'static + ?Sized,
+{
+ di_container: &'di_container mut AsyncDIContainer,
+ interface_phantom: PhantomData<Interface>,
+}
+
+impl<'di_container, Interface> AsyncBindingBuilder<'di_container, Interface>
+where
+ Interface: 'static + ?Sized,
+{
+ fn new(di_container: &'di_container mut AsyncDIContainer) -> 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.
+ pub fn to<Implementation>(
+ &mut self,
+ ) -> Result<
+ AsyncBindingScopeConfigurator<Interface, Implementation>,
+ AsyncBindingBuilderError,
+ >
+ where
+ Implementation: AsyncInjectable,
+ {
+ if self.di_container.bindings.has::<Interface>(None) {
+ return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::<
+ Interface,
+ >(
+ )));
+ }
+
+ let mut binding_scope_configurator =
+ AsyncBindingScopeConfigurator::new(self.di_container);
+
+ binding_scope_configurator.in_transient_scope();
+
+ Ok(binding_scope_configurator)
+ }
+
+ /// Creates a binding of factory type `Interface` to a factory inside of the
+ /// associated [`AsyncDIContainer`].
+ ///
+ /// *This function is only available if Syrette is built with the "factory" feature.*
+ ///
+ /// # Errors
+ /// Will return Err if the associated [`AsyncDIContainer`] already have a binding for
+ /// the interface.
+ #[cfg(feature = "factory")]
+ pub fn to_factory<Args, Return>(
+ &mut self,
+ factory_func: &'static (dyn Fn<Args, Output = crate::ptr::TransientPtr<Return>>
+ + Send
+ + Sync),
+ ) -> Result<AsyncBindingWhenConfigurator<Interface>, AsyncBindingBuilderError>
+ where
+ Args: 'static,
+ Return: 'static + ?Sized,
+ Interface: crate::interfaces::factory::IFactory<Args, Return>,
+ {
+ if self.di_container.bindings.has::<Interface>(None) {
+ return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::<
+ Interface,
+ >(
+ )));
+ }
+
+ let factory_impl = ThreadsafeCastableFactory::new(factory_func);
+
+ self.di_container.bindings.set::<Interface>(
+ None,
+ Box::new(crate::provider::r#async::AsyncFactoryProvider::new(
+ crate::ptr::ThreadsafeFactoryPtr::new(factory_impl),
+ )),
+ );
+
+ Ok(AsyncBindingWhenConfigurator::new(self.di_container))
+ }
+
+ /// Creates a binding of type `Interface` to a factory that takes no arguments
+ /// inside of the associated [`AsyncDIContainer`].
+ ///
+ /// *This function is only available if Syrette is built with the "factory" feature.*
+ ///
+ /// # Errors
+ /// Will return Err if the associated [`AsyncDIContainer`] already have a binding for
+ /// the interface.
+ #[cfg(feature = "factory")]
+ pub fn to_default_factory<Return>(
+ &mut self,
+ factory_func: &'static (dyn Fn<(), Output = crate::ptr::TransientPtr<Return>>
+ + Send
+ + Sync),
+ ) -> Result<AsyncBindingWhenConfigurator<Interface>, AsyncBindingBuilderError>
+ where
+ Return: 'static + ?Sized,
+ {
+ if self.di_container.bindings.has::<Interface>(None) {
+ return Err(AsyncBindingBuilderError::BindingAlreadyExists(type_name::<
+ Interface,
+ >(
+ )));
+ }
+
+ let factory_impl = ThreadsafeCastableFactory::new(factory_func);
+
+ self.di_container.bindings.set::<Interface>(
+ None,
+ Box::new(crate::provider::r#async::AsyncFactoryProvider::new(
+ crate::ptr::ThreadsafeFactoryPtr::new(factory_impl),
+ )),
+ );
+
+ Ok(AsyncBindingWhenConfigurator::new(self.di_container))
+ }
+}
+
+/// Dependency injection container.
+pub struct AsyncDIContainer
+{
+ bindings: DIContainerBindingMap<dyn IAsyncProvider>,
+}
+
+impl AsyncDIContainer
+{
+ /// Returns a new `AsyncDIContainer`.
+ #[must_use]
+ pub fn new() -> Self
+ {
+ Self {
+ bindings: DIContainerBindingMap::new(),
+ }
+ }
+
+ /// Returns a new [`AsyncBindingBuilder`] for the given interface.
+ pub fn bind<Interface>(&mut self) -> AsyncBindingBuilder<Interface>
+ where
+ Interface: 'static + ?Sized,
+ {
+ AsyncBindingBuilder::<Interface>::new(self)
+ }
+
+ /// Returns the type bound with `Interface`.
+ ///
+ /// # Errors
+ /// Will return `Err` if:
+ /// - No binding for `Interface` exists
+ /// - Resolving the binding for fails
+ /// - Casting the binding for fails
+ pub async fn get<Interface>(
+ &self,
+ ) -> Result<SomeThreadsafePtr<Interface>, AsyncDIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.get_bound::<Interface>(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 fails
+ /// - Casting the binding for fails
+ pub async fn get_named<Interface>(
+ &self,
+ name: &'static str,
+ ) -> Result<SomeThreadsafePtr<Interface>, AsyncDIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.get_bound::<Interface>(Vec::new(), Some(name)).await
+ }
+
+ #[doc(hidden)]
+ pub async fn get_bound<Interface>(
+ &self,
+ dependency_history: Vec<&'static str>,
+ name: Option<&'static str>,
+ ) -> Result<SomeThreadsafePtr<Interface>, AsyncDIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ let binding_providable = self
+ .get_binding_providable::<Interface>(name, dependency_history)
+ .await?;
+
+ Self::handle_binding_providable(binding_providable)
+ }
+
+ fn handle_binding_providable<Interface>(
+ binding_providable: AsyncProvidable,
+ ) -> Result<SomeThreadsafePtr<Interface>, AsyncDIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ match binding_providable {
+ AsyncProvidable::Transient(transient_binding) => {
+ Ok(SomeThreadsafePtr::Transient(
+ transient_binding.cast::<Interface>().map_err(|_| {
+ AsyncDIContainerError::CastFailed(type_name::<Interface>())
+ })?,
+ ))
+ }
+ AsyncProvidable::Singleton(singleton_binding) => {
+ Ok(SomeThreadsafePtr::ThreadsafeSingleton(
+ singleton_binding.cast::<Interface>().map_err(|_| {
+ AsyncDIContainerError::CastFailed(type_name::<Interface>())
+ })?,
+ ))
+ }
+ #[cfg(feature = "factory")]
+ AsyncProvidable::Factory(factory_binding) => {
+ match factory_binding.clone().cast::<Interface>() {
+ Ok(factory) => Ok(SomeThreadsafePtr::ThreadsafeFactory(factory)),
+ Err(_err) => {
+ use crate::interfaces::factory::IFactory;
+
+ let default_factory =
+ factory_binding
+ .cast::<dyn IFactory<(), Interface>>()
+ .map_err(|_| {
+ AsyncDIContainerError::CastFailed(type_name::<
+ Interface,
+ >(
+ ))
+ })?;
+
+ Ok(SomeThreadsafePtr::Transient(default_factory()))
+ }
+ }
+ }
+ }
+ }
+
+ async fn get_binding_providable<Interface>(
+ &self,
+ name: Option<&'static str>,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, AsyncDIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.bindings
+ .get::<Interface>(name)
+ .map_or_else(
+ || {
+ Err(AsyncDIContainerError::BindingNotFound {
+ interface: type_name::<Interface>(),
+ name,
+ })
+ },
+ Ok,
+ )?
+ .provide(self, dependency_history)
+ .await
+ .map_err(|err| AsyncDIContainerError::BindingResolveFailed {
+ reason: err,
+ interface: type_name::<Interface>(),
+ })
+ }
+}
+
+impl Default for AsyncDIContainer
+{
+ fn default() -> Self
+ {
+ Self::new()
+ }
+}
+
+#[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 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
+ {
+ 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(
+ _: &AsyncDIContainer,
+ _dependency_history: Vec<&'static str>,
+ ) -> Result<TransientPtr<Self>, 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, async = true);
+
+ #[async_trait]
+ impl AsyncInjectable for Number
+ {
+ async fn resolve(
+ _: &AsyncDIContainer,
+ _dependency_history: Vec<&'static str>,
+ ) -> Result<TransientPtr<Self>, crate::errors::injectable::InjectableError>
+ where
+ Self: Sized,
+ {
+ Ok(TransientPtr::new(Self::new()))
+ }
+ }
+ }
+
+ #[test]
+ fn can_bind_to() -> Result<(), Box<dyn Error>>
+ {
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<dyn subjects::IUserManager>()
+ .to::<subjects::UserManager>()?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_bind_to_transient() -> Result<(), Box<dyn Error>>
+ {
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<dyn subjects::IUserManager>()
+ .to::<subjects::UserManager>()?
+ .in_transient_scope();
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_bind_to_transient_when_named() -> Result<(), Box<dyn Error>>
+ {
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<dyn subjects::IUserManager>()
+ .to::<subjects::UserManager>()?
+ .in_transient_scope()
+ .when_named("regular")?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn can_bind_to_singleton() -> Result<(), Box<dyn Error>>
+ {
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<dyn subjects::IUserManager>()
+ .to::<subjects::UserManager>()?
+ .in_singleton_scope()
+ .await?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn can_bind_to_singleton_when_named() -> Result<(), Box<dyn Error>>
+ {
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<dyn subjects::IUserManager>()
+ .to::<subjects::UserManager>()?
+ .in_singleton_scope()
+ .await?
+ .when_named("cool")?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[test]
+ #[cfg(feature = "factory")]
+ fn can_bind_to_factory() -> Result<(), Box<dyn Error>>
+ {
+ type IUserManagerFactory =
+ dyn crate::interfaces::factory::IFactory<(), dyn subjects::IUserManager>;
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container.bind::<IUserManagerFactory>().to_factory(&|| {
+ let user_manager: TransientPtr<dyn subjects::IUserManager> =
+ TransientPtr::new(subjects::UserManager::new());
+
+ user_manager
+ })?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[test]
+ #[cfg(feature = "factory")]
+ fn can_bind_to_factory_when_named() -> Result<(), Box<dyn Error>>
+ {
+ type IUserManagerFactory =
+ dyn crate::interfaces::factory::IFactory<(), dyn subjects::IUserManager>;
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ assert_eq!(di_container.bindings.count(), 0);
+
+ di_container
+ .bind::<IUserManagerFactory>()
+ .to_factory(&|| {
+ let user_manager: TransientPtr<dyn subjects::IUserManager> =
+ TransientPtr::new(subjects::UserManager::new());
+
+ user_manager
+ })?
+ .when_named("awesome")?;
+
+ assert_eq!(di_container.bindings.count(), 1);
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn can_get() -> Result<(), Box<dyn Error>>
+ {
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ let mut mock_provider = MockProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(AsyncProvidable::Transient(TransientPtr::new(
+ subjects::UserManager::new(),
+ )))
+ });
+
+ di_container
+ .bindings
+ .set::<dyn subjects::IUserManager>(None, Box::new(mock_provider));
+
+ di_container
+ .get::<dyn subjects::IUserManager>()
+ .await?
+ .transient()?;
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn can_get_named() -> Result<(), Box<dyn Error>>
+ {
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ let mut mock_provider = MockProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(AsyncProvidable::Transient(TransientPtr::new(
+ subjects::UserManager::new(),
+ )))
+ });
+
+ di_container
+ .bindings
+ .set::<dyn subjects::IUserManager>(Some("special"), Box::new(mock_provider));
+
+ di_container
+ .get_named::<dyn subjects::IUserManager>("special")
+ .await?
+ .transient()?;
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn can_get_singleton() -> Result<(), Box<dyn Error>>
+ {
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = 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_provide()
+ .returning_st(move |_, _| Ok(AsyncProvidable::Singleton(singleton.clone())));
+
+ di_container
+ .bindings
+ .set::<dyn subjects::INumber>(None, Box::new(mock_provider));
+
+ let first_number_rc = di_container
+ .get::<dyn subjects::INumber>()
+ .await?
+ .threadsafe_singleton()?;
+
+ assert_eq!(first_number_rc.get(), 2820);
+
+ let second_number_rc = di_container
+ .get::<dyn subjects::INumber>()
+ .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<dyn Error>>
+ {
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = 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_provide()
+ .returning_st(move |_, _| Ok(AsyncProvidable::Singleton(singleton.clone())));
+
+ di_container
+ .bindings
+ .set::<dyn subjects::INumber>(Some("cool"), Box::new(mock_provider));
+
+ let first_number_rc = di_container
+ .get_named::<dyn subjects::INumber>("cool")
+ .await?
+ .threadsafe_singleton()?;
+
+ assert_eq!(first_number_rc.get(), 2820);
+
+ let second_number_rc = di_container
+ .get_named::<dyn subjects::INumber>("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<dyn Error>>
+ {
+ trait IUserManager
+ {
+ fn add_user(&mut self, user_id: i128);
+
+ fn remove_user(&mut self, user_id: i128);
+ }
+
+ struct UserManager
+ {
+ users: Vec<i128>,
+ }
+
+ impl UserManager
+ {
+ fn new(users: Vec<i128>) -> 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(async = true)]
+ type IUserManagerFactory =
+ dyn crate::interfaces::factory::IFactory<(Vec<i128>,), dyn IUserManager>;
+
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ let mut mock_provider = MockProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(AsyncProvidable::Factory(
+ crate::ptr::ThreadsafeFactoryPtr::new(ThreadsafeCastableFactory::new(
+ &|users| {
+ let user_manager: TransientPtr<dyn IUserManager> =
+ TransientPtr::new(UserManager::new(users));
+
+ user_manager
+ },
+ )),
+ ))
+ });
+
+ di_container
+ .bindings
+ .set::<IUserManagerFactory>(None, Box::new(mock_provider));
+
+ di_container
+ .get::<IUserManagerFactory>()
+ .await?
+ .threadsafe_factory()?;
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ #[cfg(feature = "factory")]
+ async fn can_get_factory_named() -> Result<(), Box<dyn Error>>
+ {
+ trait IUserManager
+ {
+ fn add_user(&mut self, user_id: i128);
+
+ fn remove_user(&mut self, user_id: i128);
+ }
+
+ struct UserManager
+ {
+ users: Vec<i128>,
+ }
+
+ impl UserManager
+ {
+ fn new(users: Vec<i128>) -> 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(async = true)]
+ type IUserManagerFactory =
+ dyn crate::interfaces::factory::IFactory<(Vec<i128>,), dyn IUserManager>;
+
+ mock! {
+ Provider {}
+
+ #[async_trait]
+ impl IAsyncProvider for Provider
+ {
+ async fn provide(
+ &self,
+ di_container: &AsyncDIContainer,
+ dependency_history: Vec<&'static str>,
+ ) -> Result<AsyncProvidable, InjectableError>;
+ }
+ }
+
+ let mut di_container: AsyncDIContainer = AsyncDIContainer::new();
+
+ let mut mock_provider = MockProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(AsyncProvidable::Factory(
+ crate::ptr::ThreadsafeFactoryPtr::new(ThreadsafeCastableFactory::new(
+ &|users| {
+ let user_manager: TransientPtr<dyn IUserManager> =
+ TransientPtr::new(UserManager::new(users));
+
+ user_manager
+ },
+ )),
+ ))
+ });
+
+ di_container
+ .bindings
+ .set::<IUserManagerFactory>(Some("special"), Box::new(mock_provider));
+
+ di_container
+ .get_named::<IUserManagerFactory>("special")
+ .await?
+ .threadsafe_factory()?;
+
+ Ok(())
+ }
+}