From 05be92b334af1beab3e7a3f2ee7626eb26c47e22 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 16 Jul 2022 12:02:54 +0200 Subject: feat: add binding factories to DI container --- example/src/main.rs | 43 +++++++++- syrette/src/castable_factory.rs | 72 ++++++++++++++++ syrette/src/di_container.rs | 132 ++++++++++++++++++++++------- syrette/src/interfaces/factory.rs | 7 ++ syrette/src/interfaces/mod.rs | 1 + syrette/src/lib.rs | 3 + syrette/src/libs/intertrait/cast_rc.rs | 34 ++++++++ syrette/src/libs/intertrait/mod.rs | 1 + syrette/src/provider.rs | 48 +++++++++-- syrette_macros/src/lib.rs | 149 ++++++++++++++++++++++++++++++--- 10 files changed, 441 insertions(+), 49 deletions(-) create mode 100644 syrette/src/castable_factory.rs create mode 100644 syrette/src/interfaces/factory.rs create mode 100644 syrette/src/libs/intertrait/cast_rc.rs diff --git a/example/src/main.rs b/example/src/main.rs index 1f23179..28f29b4 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,4 +1,8 @@ +use std::rc::Rc; + use syrette::errors::di_container::DIContainerError; +use syrette::factory; +use syrette::interfaces::factory::IFactory; use syrette::{injectable, DIContainer}; trait IDog @@ -54,6 +58,32 @@ trait ICow fn moo(&self); } +struct Cow +{ + _moo_cnt: i32, +} + +impl Cow +{ + fn new(moo_cnt: i32) -> Self + { + Self { _moo_cnt: moo_cnt } + } +} + +impl ICow for Cow +{ + fn moo(&self) + { + for _ in 0..self._moo_cnt { + println!("Moo"); + } + } +} + +#[factory] +type CowFactory = dyn IFactory<(i32,), dyn ICow>; + trait IHuman { fn make_pets_make_sounds(&self); @@ -63,16 +93,18 @@ struct Human { _dog: Box, _cat: Box, + _cow_factory: Rc, } #[injectable(IHuman)] impl Human { - fn new(dog: Box, cat: Box) -> Self + fn new(dog: Box, cat: Box, cow_factory: Rc) -> Self { Self { _dog: dog, _cat: cat, + _cow_factory: cow_factory, } } } @@ -88,6 +120,10 @@ impl IHuman for Human println!("Hi kitty!"); self._cat.meow(); + + let cow: Box = (self._cow_factory)(3); + + cow.moo(); } } @@ -101,6 +137,11 @@ fn main() -> error_stack::Result<(), DIContainerError> di_container.bind::().to::(); di_container.bind::().to::(); + di_container.bind::().to_factory(&|moo_cnt| { + let cow: Box = Box::new(Cow::new(moo_cnt)); + cow + }); + let dog = di_container.get::()?; dog.woof(); diff --git a/syrette/src/castable_factory.rs b/syrette/src/castable_factory.rs new file mode 100644 index 0000000..8713ec4 --- /dev/null +++ b/syrette/src/castable_factory.rs @@ -0,0 +1,72 @@ +use crate::interfaces::factory::IFactory; +use crate::libs::intertrait::CastFrom; + +pub trait AnyFactory: CastFrom {} + +pub struct CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ + _func: &'static dyn Fn>, +} + +impl CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ + pub fn new(func: &'static dyn Fn>) -> Self + { + Self { _func: func } + } +} + +impl IFactory for CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ +} + +impl Fn for CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ + extern "rust-call" fn call(&self, args: Args) -> Self::Output + { + self._func.call(args) + } +} + +impl FnMut for CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ + extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output + { + self.call(args) + } +} + +impl FnOnce for CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ + type Output = Box; + + extern "rust-call" fn call_once(self, args: Args) -> Self::Output + { + self.call(args) + } +} + +impl AnyFactory for CastableFactory +where + Args: 'static, + Return: 'static + ?Sized, +{ +} diff --git a/syrette/src/di_container.rs b/syrette/src/di_container.rs index 32d53f2..53c4287 100644 --- a/syrette/src/di_container.rs +++ b/syrette/src/di_container.rs @@ -5,23 +5,26 @@ use std::rc::Rc; use error_stack::{Report, ResultExt}; +use crate::castable_factory::CastableFactory; use crate::errors::di_container::DIContainerError; +use crate::interfaces::factory::IFactory; use crate::interfaces::injectable::Injectable; use crate::libs::intertrait::cast_box::CastBox; -use crate::provider::{IInjectableTypeProvider, InjectableTypeProvider}; +use crate::libs::intertrait::cast_rc::CastRc; +use crate::provider::{FactoryProvider, IProvider, InjectableTypeProvider, Providable}; -/// Binding builder for `InterfaceTrait` in a [`DIContainer`]. -pub struct BindingBuilder<'a, InterfaceTrait> +/// Binding builder for type `Interface` inside a [`DIContainer`]. +pub struct BindingBuilder<'a, Interface> where - InterfaceTrait: 'static + ?Sized, + Interface: 'static + ?Sized, { _di_container: &'a mut DIContainer, - _phantom_data: PhantomData, + _phantom_data: PhantomData, } -impl<'a, InterfaceTrait> BindingBuilder<'a, InterfaceTrait> +impl<'a, Interface> BindingBuilder<'a, Interface> where - InterfaceTrait: 'static + ?Sized, + Interface: 'static + ?Sized, { fn new(di_container: &'a mut DIContainer) -> Self { @@ -31,19 +34,39 @@ where } } - /// Creates a binding of `InterfaceTrait` to type `Implementation` inside of the + /// Creates a binding of type `Interface` to type `Implementation` inside of the /// associated [`DIContainer`]. pub fn to(&mut self) where Implementation: Injectable, { - let interface_typeid = TypeId::of::(); + let interface_typeid = TypeId::of::(); self._di_container._bindings.insert( interface_typeid, Rc::new(InjectableTypeProvider::::new()), ); } + + /// Creates a binding of factory type `Interface` to a factory inside of the + /// associated [`DIContainer`]. + pub fn to_factory( + &mut self, + factory_func: &'static dyn Fn>, + ) where + Args: 'static, + Return: 'static + ?Sized, + Interface: IFactory, + { + let interface_typeid = TypeId::of::(); + + let factory_impl = CastableFactory::new(factory_func); + + self._di_container._bindings.insert( + interface_typeid, + Rc::new(FactoryProvider::new(Rc::new(factory_impl))), + ); + } } /// Dependency injection container. @@ -56,7 +79,7 @@ where /// ``` pub struct DIContainer { - _bindings: HashMap>, + _bindings: HashMap>, } impl<'a> DIContainer @@ -69,46 +92,95 @@ impl<'a> DIContainer } } - /// Returns a new [`BindingBuilder`] for the given interface trait. - pub fn bind(&'a mut self) -> BindingBuilder + /// Returns a new [`BindingBuilder`] for the given interface. + pub fn bind(&'a mut self) -> BindingBuilder where - InterfaceTrait: 'static + ?Sized, + Interface: 'static + ?Sized, { - BindingBuilder::::new(self) + BindingBuilder::::new(self) } - /// Returns the value bound with `InterfaceTrait`. - pub fn get( - &self, - ) -> error_stack::Result, DIContainerError> + /// Returns a new instance of the type bound with `Interface`. + pub fn get(&self) -> error_stack::Result, DIContainerError> where - InterfaceTrait: 'static + ?Sized, + Interface: 'static + ?Sized, { - let interface_typeid = TypeId::of::(); + let interface_typeid = TypeId::of::(); - let interface_name = type_name::(); + let interface_name = type_name::(); let binding = self._bindings.get(&interface_typeid).ok_or_else(|| { Report::new(DIContainerError) .attach_printable(format!("No binding exists for {}", interface_name)) })?; - let binding_injectable = binding + let binding_providable = binding .provide(self) .change_context(DIContainerError) .attach_printable(format!( - "Failed to resolve interface {}", + "Failed to resolve binding for interface {}", interface_name ))?; - let interface_box_result = binding_injectable.cast::(); + match binding_providable { + Providable::Injectable(binding_injectable) => { + let interface_box_result = binding_injectable.cast::(); + + match interface_box_result { + Ok(interface_box) => Ok(interface_box), + Err(_) => Err(Report::new(DIContainerError).attach_printable( + format!("Unable to cast binding for {}", interface_name), + )), + } + } + Providable::Factory(_) => Err(Report::new(DIContainerError) + .attach_printable(format!( + "Binding for {} is not injectable", + interface_name + ))), + } + } + + /// Returns the factory bound with factory type `Interface`. + pub fn get_factory( + &self, + ) -> error_stack::Result, DIContainerError> + where + Interface: 'static + ?Sized, + { + let interface_typeid = TypeId::of::(); + + let interface_name = type_name::(); + + let binding = self._bindings.get(&interface_typeid).ok_or_else(|| { + Report::new(DIContainerError) + .attach_printable(format!("No binding exists for {}", interface_name)) + })?; + + let binding_providable = binding + .provide(self) + .change_context(DIContainerError) + .attach_printable(format!( + "Failed to resolve binding for interface {}", + interface_name + ))?; - match interface_box_result { - Ok(interface_box) => Ok(interface_box), - Err(_) => Err(Report::new(DIContainerError).attach_printable(format!( - "Unable to cast binding for {}", - interface_name - ))), + match binding_providable { + Providable::Factory(binding_factory) => { + let factory_box_result = binding_factory.cast::(); + + match factory_box_result { + Ok(interface_box) => Ok(interface_box), + Err(_) => Err(Report::new(DIContainerError).attach_printable( + format!("Unable to cast binding for {}", interface_name), + )), + } + } + Providable::Injectable(_) => Err(Report::new(DIContainerError) + .attach_printable(format!( + "Binding for {} is not a factory", + interface_name + ))), } } } diff --git a/syrette/src/interfaces/factory.rs b/syrette/src/interfaces/factory.rs new file mode 100644 index 0000000..ed03cce --- /dev/null +++ b/syrette/src/interfaces/factory.rs @@ -0,0 +1,7 @@ +use crate::libs::intertrait::CastFrom; + +pub trait IFactory: Fn> + CastFrom +where + Return: 'static + ?Sized, +{ +} diff --git a/syrette/src/interfaces/mod.rs b/syrette/src/interfaces/mod.rs index 31e53af..921bb9c 100644 --- a/syrette/src/interfaces/mod.rs +++ b/syrette/src/interfaces/mod.rs @@ -1 +1,2 @@ +pub mod factory; pub mod injectable; diff --git a/syrette/src/lib.rs b/syrette/src/lib.rs index 7278c37..945c0c0 100644 --- a/syrette/src/lib.rs +++ b/syrette/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(unboxed_closures, fn_traits)] + //! Syrette //! //! Syrette is a collection of utilities useful for performing dependency injection. @@ -115,6 +117,7 @@ //! //! ``` +pub mod castable_factory; pub mod di_container; pub mod errors; pub mod interfaces; diff --git a/syrette/src/libs/intertrait/cast_rc.rs b/syrette/src/libs/intertrait/cast_rc.rs new file mode 100644 index 0000000..58d212a --- /dev/null +++ b/syrette/src/libs/intertrait/cast_rc.rs @@ -0,0 +1,34 @@ +/** + * Originally from Intertrait by CodeChain + * + * https://github.com/CodeChain-io/intertrait + * https://crates.io/crates/intertrait/0.2.2 + * + * Licensed under either of + * + * Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) + + * at your option. +*/ +use std::rc::Rc; + +use crate::libs::intertrait::{caster, CastFrom}; + +pub trait CastRc +{ + /// Casts an `Rc` for this trait into that for type `T`. + fn cast(self: Rc) -> Result, Rc>; +} + +/// A blanket implementation of `CastRc` for traits extending `CastFrom`. +impl CastRc for S +{ + fn cast(self: Rc) -> Result, Rc> + { + match caster::((*self).type_id()) { + Some(caster) => Ok((caster.cast_rc)(self.rc_any())), + None => Err(self), + } + } +} diff --git a/syrette/src/libs/intertrait/mod.rs b/syrette/src/libs/intertrait/mod.rs index b07d91e..e7b3bdd 100644 --- a/syrette/src/libs/intertrait/mod.rs +++ b/syrette/src/libs/intertrait/mod.rs @@ -27,6 +27,7 @@ mod hasher; use hasher::BuildFastHasher; pub mod cast_box; +pub mod cast_rc; pub type BoxedCaster = Box; diff --git a/syrette/src/provider.rs b/syrette/src/provider.rs index 0d6a1cc..800315f 100644 --- a/syrette/src/provider.rs +++ b/syrette/src/provider.rs @@ -1,17 +1,25 @@ use std::marker::PhantomData; +use std::rc::Rc; -extern crate error_stack; - +use crate::castable_factory::AnyFactory; use crate::errors::injectable::ResolveError; use crate::interfaces::injectable::Injectable; use crate::DIContainer; -pub trait IInjectableTypeProvider +extern crate error_stack; + +pub enum Providable +{ + Injectable(Box), + Factory(Rc), +} + +pub trait IProvider { fn provide( &self, di_container: &DIContainer, - ) -> error_stack::Result, ResolveError>; + ) -> error_stack::Result; } pub struct InjectableTypeProvider @@ -33,15 +41,41 @@ where } } -impl IInjectableTypeProvider for InjectableTypeProvider +impl IProvider for InjectableTypeProvider where InjectableType: Injectable, { fn provide( &self, di_container: &DIContainer, - ) -> error_stack::Result, ResolveError> + ) -> error_stack::Result + { + Ok(Providable::Injectable(InjectableType::resolve( + di_container, + )?)) + } +} + +pub struct FactoryProvider +{ + _factory: Rc, +} + +impl FactoryProvider +{ + pub fn new(factory: Rc) -> Self + { + Self { _factory: factory } + } +} + +impl IProvider for FactoryProvider +{ + fn provide( + &self, + _di_container: &DIContainer, + ) -> error_stack::Result { - Ok(InjectableType::resolve(di_container)?) + Ok(Providable::Factory(self._factory.clone())) } } diff --git a/syrette_macros/src/lib.rs b/syrette_macros/src/lib.rs index 1761534..0302c07 100644 --- a/syrette_macros/src/lib.rs +++ b/syrette_macros/src/lib.rs @@ -2,8 +2,8 @@ use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{ parse, parse_macro_input, parse_str, punctuated::Punctuated, token::Comma, - AttributeArgs, FnArg, GenericArgument, ImplItem, ItemImpl, Meta, NestedMeta, Path, - PathArguments, Type, TypePath, + AttributeArgs, ExprMethodCall, FnArg, GenericArgument, ImplItem, ItemImpl, ItemType, + Meta, NestedMeta, Path, PathArguments, Type, TypeParamBound, TypePath, }; mod libs; @@ -27,8 +27,16 @@ const IMPL_NO_NEW_METHOD_ERR_MESSAGE: &str = const IMPL_NEW_METHOD_SELF_PARAM_ERR_MESSAGE: &str = "The new method of the attached to trait implementation cannot have a self parameter"; -const IMPL_NEW_METHOD_BOX_PARAMS_ERR_MESSAGE: &str = - "All parameters of the new method of the attached to trait implementation must be std::boxed::Box"; +const IMPL_NEW_METHOD_PARAM_TYPES_ERR_MESSAGE: &str = concat!( + "All parameters of the new method of the attached to trait implementation ", + "must be either std::boxed::Box or std::rc:Rc (for factories)" +); + +const INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE: &str = + "Invalid aliased trait. Must be 'dyn IFactory'"; + +const INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE: &str = + "Invalid arguments for 'dyn IFactory'"; fn path_to_string(path: &Path) -> String { @@ -67,6 +75,10 @@ fn get_fn_arg_type_paths(fn_args: &Punctuated) -> Vec match arg { FnArg::Typed(typed_fn_arg) => match typed_fn_arg.ty.as_ref() { Type::Path(arg_type_path) => acc.push(arg_type_path.clone()), + Type::Reference(ref_type_path) => match ref_type_path.elem.as_ref() { + Type::Path(arg_type_path) => acc.push(arg_type_path.clone()), + &_ => {} + }, &_ => {} }, FnArg::Receiver(_receiver_fn_arg) => {} @@ -109,8 +121,11 @@ fn get_dependency_types(item_impl: &ItemImpl) -> Vec if arg_type_path_string != "Box" && arg_type_path_string != "std::boxed::Box" && arg_type_path_string != "boxed::Box" + && arg_type_path_string != "Rc" + && arg_type_path_string != "std::rc::Rc" + && arg_type_path_string != "rc::Rc" { - panic!("{}", IMPL_NEW_METHOD_BOX_PARAMS_ERR_MESSAGE); + panic!("{}", IMPL_NEW_METHOD_PARAM_TYPES_ERR_MESSAGE); } // Assume the type path has a last segment. @@ -202,6 +217,49 @@ pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenSt let dependency_types = get_dependency_types(&item_impl); + let get_dependencies = dependency_types.iter().fold( + Vec::::new(), + |mut acc, dep_type| { + match dep_type { + Type::TraitObject(dep_type_trait) => { + acc.push( + parse_str( + format!( + "di_container.get::<{}>()", + dep_type_trait.to_token_stream() + ) + .as_str(), + ) + .unwrap(), + ); + } + Type::Path(dep_type_path) => { + let dep_type_path_str = path_to_string(&dep_type_path.path); + + let get_method_name = if dep_type_path_str.ends_with("Factory") { + "get_factory" + } else { + "get" + }; + + acc.push( + parse_str( + format!( + "di_container.{}::<{}>()", + get_method_name, dep_type_path_str + ) + .as_str(), + ) + .unwrap(), + ); + } + &_ => {} + } + + acc + }, + ); + quote! { #item_impl @@ -213,13 +271,15 @@ pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenSt use error_stack::ResultExt; return Ok(Box::new(Self::new( - #(di_container.get::<#dependency_types>() + #(#get_dependencies .change_context(syrette::errors::injectable::ResolveError) - .attach_printable(format!( - "Unable to resolve a dependency of {}", - std::any::type_name::<#self_type_path>() - ))?, - )* + .attach_printable( + format!( + "Unable to resolve a dependency of {}", + std::any::type_name::<#self_type_path>() + ) + )? + ),* ))); } } @@ -229,6 +289,73 @@ pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenSt .into() } +#[proc_macro_attribute] +pub fn factory(_: TokenStream, type_alias_stream: TokenStream) -> TokenStream +{ + let type_alias: ItemType = parse(type_alias_stream).unwrap(); + + let aliased_trait = match &type_alias.ty.as_ref() { + Type::TraitObject(alias_type) => alias_type, + &_ => panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE), + }; + + if aliased_trait.bounds.len() != 1 { + panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE); + } + + let type_bound = aliased_trait.bounds.first().unwrap(); + + let trait_bound = match type_bound { + TypeParamBound::Trait(trait_bound) => trait_bound, + &_ => panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE), + }; + + let trait_bound_path = &trait_bound.path; + + if trait_bound_path.segments.is_empty() + || trait_bound_path.segments.last().unwrap().ident != "IFactory" + { + panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE); + } + + let factory_path_segment = trait_bound_path.segments.last().unwrap(); + + let factory_path_segment_args = &match &factory_path_segment.arguments { + syn::PathArguments::AngleBracketed(args) => args, + &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), + } + .args; + + let factory_arg_types_type = match &factory_path_segment_args[0] { + GenericArgument::Type(arg_type) => arg_type, + &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), + }; + + let factory_return_type = match &factory_path_segment_args[1] { + GenericArgument::Type(arg_type) => arg_type, + &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), + }; + + quote! { + #type_alias + + syrette::castable_to!( + syrette::castable_factory::CastableFactory< + #factory_arg_types_type, + #factory_return_type + > => #trait_bound_path + ); + + syrette::castable_to!( + syrette::castable_factory::CastableFactory< + #factory_arg_types_type, + #factory_return_type + > => syrette::castable_factory::AnyFactory + ); + } + .into() +} + #[doc(hidden)] #[proc_macro] pub fn castable_to(input: TokenStream) -> TokenStream -- cgit v1.2.3-18-g5258