aboutsummaryrefslogtreecommitdiff
path: root/src/di_container/blocking.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/di_container/blocking.rs')
-rw-r--r--src/di_container/blocking.rs722
1 files changed, 722 insertions, 0 deletions
diff --git a/src/di_container/blocking.rs b/src/di_container/blocking.rs
new file mode 100644
index 0000000..d8b0d59
--- /dev/null
+++ b/src/di_container/blocking.rs
@@ -0,0 +1,722 @@
+//! 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<String, String>;
+//! }
+//!
+//! struct DatabaseService {}
+//!
+//! #[injectable(IDatabaseService)]
+//! 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()
+//! }
+//! }
+//!
+//! fn main() -> Result<(), Box<dyn Error>>
+//! {
+//! let mut di_container = DIContainer::new();
+//!
+//! di_container
+//! .bind::<dyn IDatabaseService>()
+//! .to::<DatabaseService>()
+//! .map_err(|err| err.to_string())?;
+//!
+//! let database_service = di_container
+//! .get::<dyn IDatabaseService>()
+//! .map_err(|err| err.to_string())?
+//! .transient()?;
+//!
+//! Ok(())
+//! }
+//! ```
+use std::any::type_name;
+
+use crate::di_container::binding_storage::DIContainerBindingStorage;
+use crate::di_container::blocking::binding::builder::BindingBuilder;
+use crate::di_container::BindingOptions;
+use crate::errors::di_container::DIContainerError;
+use crate::private::cast::boxed::CastBox;
+use crate::private::cast::rc::CastRc;
+use crate::provider::blocking::{IProvider, Providable};
+use crate::ptr::SomePtr;
+use crate::util::use_double;
+
+use_double!(crate::dependency_history::DependencyHistory);
+
+pub mod binding;
+
+#[cfg(not(test))]
+pub(crate) type BindingOptionsWithLt<'a> = BindingOptions<'a>;
+
+#[cfg(test)]
+pub(crate) type BindingOptionsWithLt = BindingOptions<'static>;
+
+/// Blocking dependency injection container.
+#[derive(Default)]
+pub struct DIContainer
+{
+ binding_storage: DIContainerBindingStorage<dyn IProvider<Self>>,
+}
+
+impl DIContainer
+{
+ /// Returns a new `DIContainer`.
+ #[must_use]
+ pub fn new() -> Self
+ {
+ Self {
+ binding_storage: DIContainerBindingStorage::new(),
+ }
+ }
+}
+
+#[cfg_attr(test, mockall::automock)]
+impl DIContainer
+{
+ /// Returns a new [`BindingBuilder`] for the given interface.
+ ///
+ /// # Examples
+ /// ```
+ /// # use syrette::{DIContainer, injectable};
+ /// #
+ /// # struct DiskWriter {}
+ /// #
+ /// # #[injectable]
+ /// # impl DiskWriter
+ /// # {
+ /// # fn new() -> Self
+ /// # {
+ /// # Self {}
+ /// # }
+ /// # }
+ /// #
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut di_container = DIContainer::new();
+ ///
+ /// di_container.bind::<DiskWriter>().to::<DiskWriter>()?;
+ /// #
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[allow(clippy::missing_panics_doc)]
+ pub fn bind<Interface>(&mut self) -> BindingBuilder<'_, Interface>
+ where
+ Interface: 'static + ?Sized,
+ {
+ #[cfg(test)]
+ panic!("Nope");
+
+ #[cfg(not(test))]
+ BindingBuilder::new(self, DependencyHistory::new)
+ }
+
+ /// 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
+ ///
+ /// # Examples
+ /// ```
+ /// # use syrette::{DIContainer, injectable};
+ /// #
+ /// # struct DeviceManager {}
+ /// #
+ /// # #[injectable]
+ /// # impl DeviceManager
+ /// # {
+ /// # fn new() -> Self
+ /// # {
+ /// # Self {}
+ /// # }
+ /// # }
+ /// #
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut di_container = DIContainer::new();
+ ///
+ /// di_container.bind::<DeviceManager>().to::<DeviceManager>()?;
+ ///
+ /// let device_manager = di_container.get::<DeviceManager>()?.transient();
+ /// #
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn get<Interface>(&self) -> Result<SomePtr<Interface>, DIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.get_bound::<Interface>(DependencyHistory::new(), BindingOptions::new())
+ }
+
+ /// 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
+ ///
+ /// # Examples
+ /// ```
+ /// # use syrette::{DIContainer, injectable};
+ /// #
+ /// # struct DeviceManager {}
+ /// #
+ /// # #[injectable]
+ /// # impl DeviceManager
+ /// # {
+ /// # fn new() -> Self
+ /// # {
+ /// # Self {}
+ /// # }
+ /// # }
+ /// #
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut di_container = DIContainer::new();
+ ///
+ /// di_container
+ /// .bind::<DeviceManager>()
+ /// .to::<DeviceManager>()?
+ /// .in_transient_scope()
+ /// .when_named("usb")?;
+ ///
+ /// let device_manager = di_container.get_named::<DeviceManager>("usb")?.transient();
+ /// #
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn get_named<Interface>(
+ &self,
+ name: &'static str,
+ ) -> Result<SomePtr<Interface>, DIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.get_bound::<Interface>(
+ DependencyHistory::new(),
+ BindingOptions::new().name(name),
+ )
+ }
+
+ /// Returns the type bound with `Interface` where the binding has the specified
+ /// options.
+ ///
+ /// `dependency_history` is passed to the bound type when it is being resolved.
+ ///
+ /// # Errors
+ /// Will return `Err` if:
+ /// - No binding for `Interface` exists
+ /// - Resolving the binding for `Interface` fails
+ /// - Casting the binding for `Interface` fails
+ ///
+ /// # Examples
+ /// ```no_run
+ /// # use syrette::di_container::blocking::DIContainer;
+ /// # use syrette::dependency_history::DependencyHistory;
+ /// # use syrette::di_container::BindingOptions;
+ /// #
+ /// # struct EventHandler {}
+ /// # struct Button {}
+ /// #
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// # let di_container = DIContainer::new();
+ /// #
+ /// let mut dependency_history = DependencyHistory::new();
+ ///
+ /// dependency_history.push::<EventHandler>();
+ ///
+ /// di_container.get_bound::<Button>(
+ /// dependency_history,
+ /// BindingOptions::new().name("huge_red"),
+ /// )?;
+ /// #
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn get_bound<Interface>(
+ &self,
+ dependency_history: DependencyHistory,
+ binding_options: BindingOptionsWithLt,
+ ) -> Result<SomePtr<Interface>, DIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ let binding_providable = self
+ .get_binding_providable::<Interface>(binding_options, dependency_history)?;
+
+ match binding_providable {
+ Providable::Transient(transient_binding) => Ok(SomePtr::Transient(
+ transient_binding.cast::<Interface>().map_err(|_| {
+ DIContainerError::CastFailed {
+ interface: type_name::<Interface>(),
+ binding_kind: "transient",
+ }
+ })?,
+ )),
+ Providable::Singleton(singleton_binding) => Ok(SomePtr::Singleton(
+ singleton_binding.cast::<Interface>().map_err(|_| {
+ DIContainerError::CastFailed {
+ interface: type_name::<Interface>(),
+ binding_kind: "singleton",
+ }
+ })?,
+ )),
+ #[cfg(feature = "factory")]
+ Providable::Factory(factory_binding) => {
+ use crate::castable_function::CastableFunction;
+
+ let factory = factory_binding
+ .as_any()
+ .downcast_ref::<CastableFunction<Interface, Self>>()
+ .ok_or_else(|| DIContainerError::CastFailed {
+ interface: type_name::<Interface>(),
+ binding_kind: "factory",
+ })?;
+
+ Ok(SomePtr::Factory(factory.call(self).into()))
+ }
+ #[cfg(feature = "factory")]
+ Providable::DefaultFactory(factory_binding) => {
+ use crate::castable_function::CastableFunction;
+ use crate::ptr::TransientPtr;
+
+ type DefaultFactoryFn<Interface> =
+ CastableFunction<dyn Fn() -> TransientPtr<Interface>, DIContainer>;
+
+ let default_factory = factory_binding
+ .as_any()
+ .downcast_ref::<DefaultFactoryFn<Interface>>()
+ .ok_or_else(|| DIContainerError::CastFailed {
+ interface: type_name::<Interface>(),
+ binding_kind: "default factory",
+ })?;
+
+ Ok(SomePtr::Transient(default_factory.call(self)()))
+ }
+ }
+ }
+
+ fn has_binding<Interface>(&self, binding_options: BindingOptionsWithLt) -> bool
+ where
+ Interface: ?Sized + 'static,
+ {
+ self.binding_storage.has::<Interface>(binding_options)
+ }
+
+ fn set_binding<Interface>(
+ &mut self,
+ binding_options: BindingOptions<'static>,
+ provider: Box<dyn IProvider<Self>>,
+ ) where
+ Interface: 'static + ?Sized,
+ {
+ self.binding_storage
+ .set::<Interface>(binding_options, provider);
+ }
+
+ fn remove_binding<Interface>(
+ &mut self,
+ binding_options: BindingOptions<'static>,
+ ) -> Option<Box<dyn IProvider<Self>>>
+ where
+ Interface: 'static + ?Sized,
+ {
+ self.binding_storage.remove::<Interface>(binding_options)
+ }
+}
+
+impl DIContainer
+{
+ fn get_binding_providable<Interface>(
+ &self,
+ binding_options: BindingOptionsWithLt,
+ dependency_history: DependencyHistory,
+ ) -> Result<Providable<Self>, DIContainerError>
+ where
+ Interface: 'static + ?Sized,
+ {
+ let name = binding_options.name;
+
+ self.binding_storage
+ .get::<Interface>(binding_options)
+ .map_or_else(
+ || {
+ Err(DIContainerError::BindingNotFound {
+ interface: type_name::<Interface>(),
+ name: name.as_ref().map(ToString::to_string),
+ })
+ },
+ Ok,
+ )?
+ .provide(self, dependency_history)
+ .map_err(|err| DIContainerError::BindingResolveFailed {
+ reason: err,
+ interface: type_name::<Interface>(),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+ use crate::provider::blocking::MockIProvider;
+ use crate::ptr::{SingletonPtr, TransientPtr};
+ use crate::test_utils::subjects;
+
+ #[test]
+ fn can_get()
+ {
+ let mut di_container = DIContainer::new();
+
+ let mut mock_provider = MockIProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(Providable::Transient(TransientPtr::new(
+ subjects::UserManager::new(),
+ )))
+ });
+
+ di_container
+ .binding_storage
+ .set::<dyn subjects::IUserManager>(
+ BindingOptions::new(),
+ Box::new(mock_provider),
+ );
+
+ di_container
+ .get::<dyn subjects::IUserManager>()
+ .unwrap()
+ .transient()
+ .unwrap();
+ }
+
+ #[test]
+ fn can_get_named()
+ {
+ let mut di_container = DIContainer::new();
+
+ let mut mock_provider = MockIProvider::new();
+
+ mock_provider.expect_provide().returning(|_, _| {
+ Ok(Providable::Transient(TransientPtr::new(
+ subjects::UserManager::new(),
+ )))
+ });
+
+ di_container
+ .binding_storage
+ .set::<dyn subjects::IUserManager>(
+ BindingOptions::new().name("special"),
+ Box::new(mock_provider),
+ );
+
+ di_container
+ .get_named::<dyn subjects::IUserManager>("special")
+ .unwrap()
+ .transient()
+ .unwrap();
+ }
+
+ #[test]
+ fn can_get_singleton()
+ {
+ let mut di_container = DIContainer::new();
+
+ let mut mock_provider = MockIProvider::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
+ .binding_storage
+ .set::<dyn subjects::INumber>(BindingOptions::new(), Box::new(mock_provider));
+
+ let first_number_rc = di_container
+ .get::<dyn subjects::INumber>()
+ .unwrap()
+ .singleton()
+ .unwrap();
+
+ assert_eq!(first_number_rc.get(), 2820);
+
+ let second_number_rc = di_container
+ .get::<dyn subjects::INumber>()
+ .unwrap()
+ .singleton()
+ .unwrap();
+
+ assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref());
+ }
+
+ #[test]
+ fn can_get_singleton_named()
+ {
+ let mut di_container = DIContainer::new();
+
+ let mut mock_provider = MockIProvider::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.binding_storage.set::<dyn subjects::INumber>(
+ BindingOptions::new().name("cool"),
+ Box::new(mock_provider),
+ );
+
+ let first_number_rc = di_container
+ .get_named::<dyn subjects::INumber>("cool")
+ .unwrap()
+ .singleton()
+ .unwrap();
+
+ assert_eq!(first_number_rc.get(), 2820);
+
+ let second_number_rc = di_container
+ .get_named::<dyn subjects::INumber>("cool")
+ .unwrap()
+ .singleton()
+ .unwrap();
+
+ assert_eq!(first_number_rc.as_ref(), second_number_rc.as_ref());
+ }
+
+ #[test]
+ #[cfg(feature = "factory")]
+ fn can_get_factory()
+ {
+ use crate::castable_function::CastableFunction;
+ 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<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);
+ }
+ }
+
+ type IUserManagerFactory = dyn Fn(Vec<i128>) -> TransientPtr<dyn IUserManager>;
+
+ let mut di_container = DIContainer::new();
+
+ let factory_func: &dyn Fn(&DIContainer) -> Box<IUserManagerFactory> = &|_| {
+ Box::new(move |users| {
+ let user_manager: TransientPtr<dyn IUserManager> =
+ TransientPtr::new(UserManager::new(users));
+
+ user_manager
+ })
+ };
+
+ let mut mock_provider = MockIProvider::new();
+
+ mock_provider.expect_provide().returning_st(|_, _| {
+ Ok(Providable::Factory(FactoryPtr::new(CastableFunction::new(
+ factory_func,
+ ))))
+ });
+
+ di_container
+ .binding_storage
+ .set::<IUserManagerFactory>(BindingOptions::new(), Box::new(mock_provider));
+
+ di_container
+ .get::<IUserManagerFactory>()
+ .unwrap()
+ .factory()
+ .unwrap();
+ }
+
+ #[test]
+ #[cfg(feature = "factory")]
+ fn can_get_factory_named()
+ {
+ use crate::castable_function::CastableFunction;
+ 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<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);
+ }
+ }
+
+ type IUserManagerFactory = dyn Fn(Vec<i128>) -> TransientPtr<dyn IUserManager>;
+
+ let mut di_container = DIContainer::new();
+
+ let factory_func: &dyn Fn(&DIContainer) -> Box<IUserManagerFactory> = &|_| {
+ Box::new(move |users| {
+ let user_manager: TransientPtr<dyn IUserManager> =
+ TransientPtr::new(UserManager::new(users));
+
+ user_manager
+ })
+ };
+
+ let mut mock_provider = MockIProvider::new();
+
+ mock_provider.expect_provide().returning_st(|_, _| {
+ Ok(Providable::Factory(FactoryPtr::new(CastableFunction::new(
+ factory_func,
+ ))))
+ });
+
+ di_container.binding_storage.set::<IUserManagerFactory>(
+ BindingOptions::new().name("special"),
+ Box::new(mock_provider),
+ );
+
+ di_container
+ .get_named::<IUserManagerFactory>("special")
+ .unwrap()
+ .factory()
+ .unwrap();
+ }
+
+ #[test]
+ fn has_binding_works()
+ {
+ let mut di_container = DIContainer::new();
+
+ // No binding is present yet
+ assert!(!di_container.has_binding::<subjects::Ninja>(BindingOptions::new()));
+
+ di_container.binding_storage.set::<subjects::Ninja>(
+ BindingOptions::new(),
+ Box::new(MockIProvider::new()),
+ );
+
+ assert!(di_container.has_binding::<subjects::Ninja>(BindingOptions::new()));
+ }
+
+ #[test]
+ fn set_binding_works()
+ {
+ let mut di_container = DIContainer::new();
+
+ di_container.set_binding::<subjects::Ninja>(
+ BindingOptions::new(),
+ Box::new(MockIProvider::new()),
+ );
+
+ assert!(di_container
+ .binding_storage
+ .has::<subjects::Ninja>(BindingOptions::new()));
+ }
+
+ #[test]
+ fn remove_binding_works()
+ {
+ let mut di_container = DIContainer::new();
+
+ di_container.binding_storage.set::<subjects::Ninja>(
+ BindingOptions::new(),
+ Box::new(MockIProvider::new()),
+ );
+
+ assert!(
+ // Formatting is weird without this comment
+ di_container
+ .remove_binding::<subjects::Ninja>(BindingOptions::new())
+ .is_some()
+ );
+
+ assert!(
+ // Formatting is weird without this comment
+ !di_container
+ .binding_storage
+ .has::<subjects::Ninja>(BindingOptions::new())
+ );
+ }
+}