From 04c705904f90308707a874a205099af49477fa38 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 27 May 2021 11:50:50 -0700 Subject: [PATCH 01/26] Change 'ServiceFactory::invoke' to take '&self' --- crates/runtime_injector/src/services/fallible.rs | 2 +- crates/runtime_injector/src/services/func.rs | 6 +++--- crates/runtime_injector/src/tests.rs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/runtime_injector/src/services/fallible.rs b/crates/runtime_injector/src/services/fallible.rs index 1e52412..e7fe9bb 100644 --- a/crates/runtime_injector/src/services/fallible.rs +++ b/crates/runtime_injector/src/services/fallible.rs @@ -28,7 +28,7 @@ where type Result = R; fn invoke( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult { diff --git a/crates/runtime_injector/src/services/func.rs b/crates/runtime_injector/src/services/func.rs index db020c2..796242a 100644 --- a/crates/runtime_injector/src/services/func.rs +++ b/crates/runtime_injector/src/services/func.rs @@ -31,7 +31,7 @@ pub trait ServiceFactory: Service { /// Invokes this service factory, creating an instance of the service. fn invoke( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult; @@ -48,7 +48,7 @@ macro_rules! impl_provider_function { (@impl ($($type_name:ident),*)) => { impl ServiceFactory<($($type_name,)*)> for F where - F: Service + FnMut($($type_name),*) -> R, + F: Service + Fn($($type_name),*) -> R, R: Service, $($type_name: Request,)* { @@ -56,7 +56,7 @@ macro_rules! impl_provider_function { #[allow(unused_variables, unused_mut, unused_assignments, non_snake_case)] fn invoke( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo ) -> InjectResult { diff --git a/crates/runtime_injector/src/tests.rs b/crates/runtime_injector/src/tests.rs index c4dd674..6a0c96a 100644 --- a/crates/runtime_injector/src/tests.rs +++ b/crates/runtime_injector/src/tests.rs @@ -108,6 +108,7 @@ fn singleton() { assert_ne!(svc1.0, svc2.dep1.0); assert_ne!(svc1.0, svc3.dep1.0); + assert_ne!(svc1.0, svc3.dep2.dep1.0); assert_ne!(svc2.dep1.0, svc3.dep1.0); } @@ -134,6 +135,7 @@ fn constants() { assert_ne!(svc1.0, svc2.dep1.0); assert_ne!(svc1.0, svc3.dep1.0); + assert_ne!(svc1.0, svc3.dep2.dep1.0); assert_ne!(svc2.dep1.0, svc3.dep1.0); } From f22ee875521885f494890c75e84fe6e3124d691b Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 27 May 2021 11:58:06 -0700 Subject: [PATCH 02/26] Remove 'Service' constraint from 'ServiceFactory' --- crates/runtime_injector/src/services/func.rs | 4 ++-- crates/runtime_injector/src/services/singleton.rs | 2 +- crates/runtime_injector/src/services/transient.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/runtime_injector/src/services/func.rs b/crates/runtime_injector/src/services/func.rs index 796242a..5633f31 100644 --- a/crates/runtime_injector/src/services/func.rs +++ b/crates/runtime_injector/src/services/func.rs @@ -25,7 +25,7 @@ use crate::{ /// factory.invoke(&injector, &RequestInfo::new()); /// # } /// ``` -pub trait ServiceFactory: Service { +pub trait ServiceFactory { /// The resulting service from invoking this service factory. type Result: Service; @@ -48,7 +48,7 @@ macro_rules! impl_provider_function { (@impl ($($type_name:ident),*)) => { impl ServiceFactory<($($type_name,)*)> for F where - F: Service + Fn($($type_name),*) -> R, + F: Fn($($type_name),*) -> R, R: Service, $($type_name: Request,)* { diff --git a/crates/runtime_injector/src/services/singleton.rs b/crates/runtime_injector/src/services/singleton.rs index b4cf835..60e006a 100644 --- a/crates/runtime_injector/src/services/singleton.rs +++ b/crates/runtime_injector/src/services/singleton.rs @@ -37,7 +37,7 @@ impl TypedProvider for SingletonProvider where D: Service, R: Service, - F: ServiceFactory, + F: Service + ServiceFactory, { type Result = R; diff --git a/crates/runtime_injector/src/services/transient.rs b/crates/runtime_injector/src/services/transient.rs index ebb6b4d..e3b9668 100644 --- a/crates/runtime_injector/src/services/transient.rs +++ b/crates/runtime_injector/src/services/transient.rs @@ -35,7 +35,7 @@ impl TypedProvider for TransientProvider where D: Service, R: Service, - F: ServiceFactory, + F: Service + ServiceFactory, { type Result = R; From b08c66056c4a8079d1c5089eceb8d60351fa9492 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:16:51 -0700 Subject: [PATCH 03/26] Increment version number --- Cargo.toml | 3 ++- crates/runtime_injector/Cargo.toml | 4 ++-- crates/runtime_injector/src/injector.rs | 14 -------------- crates/runtime_injector_actix/Cargo.toml | 6 +++--- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b242c2e..a848b85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["crates/runtime_injector", "crates/runtime_injector_actix"] +resolver = "2" +members = ["crates/*"] diff --git a/crates/runtime_injector/Cargo.toml b/crates/runtime_injector/Cargo.toml index 1d39299..5791982 100644 --- a/crates/runtime_injector/Cargo.toml +++ b/crates/runtime_injector/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "runtime_injector" -version = "0.4.0" +version = "0.5.0" authors = ["TehPers "] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0" description = "Runtime dependency injection container" repository = "https://github.com/TehPers/runtime_injector" diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index 6351479..8f8851d 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -128,20 +128,6 @@ impl Injector { InjectorBuilder::default() } - /// Creates a new injector directly from its providers and implementations. - /// Prefer [`Injector::builder()`] for creating new injectors instead. - #[must_use] - #[deprecated( - note = "prefer using a builder; this will be removed in 0.5", - since = "0.3.1" - )] - pub fn new(providers: ProviderMap) -> Self { - Injector { - provider_map: MapContainerEx::new(providers), - root_request_info: Svc::new(RequestInfo::default()), - } - } - pub(crate) fn new_from_parts( providers: ProviderMap, request_info: RequestInfo, diff --git a/crates/runtime_injector_actix/Cargo.toml b/crates/runtime_injector_actix/Cargo.toml index 6cf426b..61d5075 100644 --- a/crates/runtime_injector_actix/Cargo.toml +++ b/crates/runtime_injector_actix/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "runtime_injector_actix" -version = "0.2.0" -edition = "2018" +version = "0.3.0" +edition = "2021" authors = ["TehPers "] license = "MIT OR Apache-2.0" description = "Runtime dependency injection container for actix-web" @@ -20,7 +20,7 @@ actix-web = "3" futures-util = "0.3" [dependencies.runtime_injector] -version = "0.4" +version = "0.5" path = "../runtime_injector" default_features = false features = ["arc"] From d02dfa9c1bad1135ce82fb2101378326b330a313 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:19:16 -0700 Subject: [PATCH 04/26] Add #[non_exhaustive] to error types --- crates/runtime_injector/src/requests/arg.rs | 1 + crates/runtime_injector/src/services/service.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index b3842f8..18f85f3 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -87,6 +87,7 @@ impl Request for Arg { /// An error occurred while injecting an instance of [`Arg`]. #[derive(Debug)] +#[non_exhaustive] pub enum ArgRequestError { /// The argument value was not provided. MissingParameter, diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index 4915751..feb84b9 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -112,6 +112,7 @@ impl ServiceInfo { /// An error that has occurred during creation of a service. #[derive(Debug)] +#[non_exhaustive] pub enum InjectError { /// Failed to find a provider for the requested type. MissingProvider { From c47e05054c88b90e4e4d305b721464ce0cdc1e09 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:20:57 -0700 Subject: [PATCH 05/26] Add unit tests --- crates/runtime_injector/src/requests/arg.rs | 76 +++++++++++-- crates/runtime_injector/src/requests/info.rs | 28 +++++ .../src/services/conditional.rs | 77 +++++++++++++ .../runtime_injector/src/services/constant.rs | 15 +++ .../runtime_injector/src/services/fallible.rs | 105 ++++++++++++++++++ .../src/services/singleton.rs | 37 ++++++ .../src/services/transient.rs | 20 ++++ 7 files changed, 350 insertions(+), 8 deletions(-) diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index 18f85f3..edc50d2 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -164,19 +164,49 @@ mod tests { IntoSingleton, ServiceInfo, Svc, }; + #[derive(Debug)] + struct Foo(Arg); + #[test] fn request_fails_if_missing_arg() { - struct Foo(Arg); - + // Create a module with a single service. let module = define_module! { services = [Foo.singleton()], }; + // Create an injector with the module. let mut builder = Injector::builder(); builder.add_module(module); + // Attempt to get the service. let injector = builder.build(); - match injector.get::>() { + let error = injector.get::>().unwrap_err(); + match error { + InjectError::ActivationFailed { + service_info, + inner, + } => { + // Check that the service info is correct. + assert_eq!( + ServiceInfo::of::>(), + service_info, + ); + + // Check that the inner error is correct. + match inner.downcast_ref::() { + Some(ArgRequestError::MissingParameter) => (), + _ => panic!("unexpected error: {:?}", inner), + } + } + _ => panic!("unexpected error: {:?}", error), + } + } + + #[test] + fn request_fails_if_arg_has_no_parent_request() { + let builder = Injector::builder(); + let injector = builder.build(); + match injector.get::>() { Ok(_) => unreachable!("request should have failed"), Err(InjectError::ActivationFailed { service_info, @@ -186,7 +216,7 @@ mod tests { let inner: &ArgRequestError = inner.downcast_ref().expect("failed to downcast error"); match inner { - ArgRequestError::MissingParameter => {} + ArgRequestError::NoParentRequest => {} inner => Err(inner).unwrap(), } } @@ -195,10 +225,21 @@ mod tests { } #[test] - fn request_fails_if_arg_has_no_parent_request() { - let builder = Injector::builder(); + fn request_fails_if_arg_is_wrong_type() { + struct Foo(Arg); + + let module = define_module! { + services = [Foo.singleton()], + arguments = { + Foo = [42u32], + }, + }; + + let mut builder = Injector::builder(); + builder.add_module(module); + let injector = builder.build(); - match injector.get::>() { + match injector.get::>() { Ok(_) => unreachable!("request should have failed"), Err(InjectError::ActivationFailed { service_info, @@ -208,11 +249,30 @@ mod tests { let inner: &ArgRequestError = inner.downcast_ref().expect("failed to downcast error"); match inner { - ArgRequestError::NoParentRequest => {} + ArgRequestError::MissingParameter => {} inner => Err(inner).unwrap(), } } Err(error) => Err(error).unwrap(), } } + + #[test] + fn request_succeeds_if_arg_is_correct_type() { + struct Foo(Arg); + + let module = define_module! { + services = [Foo.singleton()], + arguments = { + Foo = [42i32], + }, + }; + + let mut builder = Injector::builder(); + builder.add_module(module); + + let injector = builder.build(); + let foo = injector.get::>().unwrap(); + assert_eq!(42, foo.0.0); + } } diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index 55c23ef..9c92065 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -130,5 +130,33 @@ impl Debug for RequestInfo { f.debug_struct("RequestInfo") .field("service_path", &self.service_path) .finish() + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parameter_is_inserted_then_removed() { + let mut info = RequestInfo::new(); + info.insert_parameter("foo", "bar".to_string()); + assert_eq!( + Some(&"bar".to_string()), + info.get_parameter("foo") + .map(|p| p.downcast_ref::()) + .flatten() + ); + assert_eq!( + Some(Box::new("bar".to_string())), + info.remove_parameter("foo") + .map(|p| p.downcast::().ok()) + .flatten() + ); + assert!(info.get_parameter("foo").is_none()); + } + + #[test] + fn missing_parameter_is_not_removed() { + let mut info = RequestInfo::new(); + assert!(info.remove_parameter("foo").is_none()); } } diff --git a/crates/runtime_injector/src/services/conditional.rs b/crates/runtime_injector/src/services/conditional.rs index cdf815d..4ba5f4b 100644 --- a/crates/runtime_injector/src/services/conditional.rs +++ b/crates/runtime_injector/src/services/conditional.rs @@ -99,3 +99,80 @@ where } } } + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use super::*; + use crate::IntoSingleton; + + #[derive(Default)] + struct Foo; + + /// When condition returns true, then a value is provided. + #[test] + fn test_condition_true() { + let mut builder = Injector::builder(); + builder.provide(Foo::default.singleton().with_condition(|_, _| true)); + + let injector = builder.build(); + let foo: Option> = injector.get().unwrap(); + + assert!(foo.is_some()); + } + + /// When condition returns true only once, then a value is provided only once. + #[test] + fn test_condition_true_once() { + let mut builder = Injector::builder(); + let provided = Mutex::new(false); + builder.provide( + Foo::default.singleton() + .with_condition(move |_, _| { + let mut provided = provided.lock().unwrap(); + if *provided { + return false; + } + *provided = true; + true + }), + ); + + // Create first value + let injector = builder.build(); + let foo: Option> = injector.get().unwrap(); + assert!(foo.is_some()); + + // Create second value + let foo: Option> = injector.get().unwrap(); + assert!(foo.is_none()); + } + + /// When condition returns true after returning false, then a value is provided. + #[test] + fn test_condition_true_after_false() { + let mut builder = Injector::builder(); + let provided = Mutex::new(false); + builder.provide( + Foo::default.singleton() + .with_condition(move |_, _| { + let mut provided = provided.lock().unwrap(); + if *provided { + return true; + } + *provided = true; + false + }), + ); + + // Create first value + let injector = builder.build(); + let foo: Option> = injector.get().unwrap(); + assert!(foo.is_none()); + + // Create second value + let foo: Option> = injector.get().unwrap(); + assert!(foo.is_some()); + } +} diff --git a/crates/runtime_injector/src/services/constant.rs b/crates/runtime_injector/src/services/constant.rs index c1fc19d..556167d 100644 --- a/crates/runtime_injector/src/services/constant.rs +++ b/crates/runtime_injector/src/services/constant.rs @@ -98,3 +98,18 @@ impl From for ConstantProvider { pub fn constant(value: T) -> ConstantProvider { ConstantProvider::new(value) } + +#[cfg(test)] +mod tests { + use super::*; + + /// Constant provider provides the correct value. + #[test] + fn constant_provider_provides_correct_value() { + let mut builder = Injector::builder(); + builder.provide(constant(8i32)); + let injector = builder.build(); + let value: Svc = injector.get().unwrap(); + assert_eq!(8, *value); + } +} diff --git a/crates/runtime_injector/src/services/fallible.rs b/crates/runtime_injector/src/services/fallible.rs index e7fe9bb..d934836 100644 --- a/crates/runtime_injector/src/services/fallible.rs +++ b/crates/runtime_injector/src/services/fallible.rs @@ -116,3 +116,108 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{constant, IntoTransient, Svc}; + use std::{ + fmt::{Display, Formatter}, + sync::Mutex, + }; + + #[derive(Debug)] + struct FooError; + + impl Error for FooError {} + + impl Display for FooError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "An error occurred while creating a Foo") + } + } + + struct Foo; + fn make_foo(succeed: Svc) -> Result { + if *succeed { + Ok(Foo) + } else { + Err(FooError) + } + } + + /// A value is returned if the service factory succeeds. + #[test] + fn test_fallible_service_factory_success() { + let mut builder = Injector::builder(); + builder.provide(make_foo.fallible().transient()); + builder.provide(constant(true)); + + let injector = builder.build(); + let _: Svc = injector.get().unwrap(); + } + + /// A value is not returned if the service factory fails. + #[test] + fn test_fallible_service_factory_failure() { + let mut builder = Injector::builder(); + builder.provide(make_foo.fallible().transient()); + builder.provide(constant(false)); + + let injector = builder.build(); + let foo_result: InjectResult> = injector.get(); + assert!(foo_result.is_err()); + } + + /// If a value fails after succeeding, an error is returned. + #[test] + fn test_fallible_service_factory_failure_after_success() { + let mut builder = Injector::builder(); + builder.provide(make_foo.fallible().transient()); + builder.provide(constant(Mutex::new(true))); + builder.provide( + (|should_succeed: Svc>| { + *should_succeed.lock().unwrap() + }) + .transient(), + ); + + // First request succeeds + let injector = builder.build(); + let foo_result: InjectResult> = injector.get(); + assert!(foo_result.is_ok()); + + // Second request fails + let should_succeed: Svc> = injector.get().unwrap(); + *should_succeed.lock().unwrap() = false; + drop(should_succeed); + let foo_result: InjectResult> = injector.get(); + assert!(foo_result.is_err()); + } + + /// If a value succeeds after failing, a value is returned. + #[test] + fn test_fallible_service_factory_success_after_failure() { + let mut builder = Injector::builder(); + builder.provide(make_foo.fallible().transient()); + builder.provide(constant(Mutex::new(false))); + builder.provide( + (|should_succeed: Svc>| { + *should_succeed.lock().unwrap() + }) + .transient(), + ); + + // First request fails + let injector = builder.build(); + let foo_result: InjectResult> = injector.get(); + assert!(foo_result.is_err()); + + // Second request succeeds + let should_succeed: Svc> = injector.get().unwrap(); + *should_succeed.lock().unwrap() = true; + drop(should_succeed); + let foo_result: InjectResult> = injector.get(); + assert!(foo_result.is_ok()); + } +} diff --git a/crates/runtime_injector/src/services/singleton.rs b/crates/runtime_injector/src/services/singleton.rs index 60e006a..b848d68 100644 --- a/crates/runtime_injector/src/services/singleton.rs +++ b/crates/runtime_injector/src/services/singleton.rs @@ -108,3 +108,40 @@ where func.singleton() } } + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + use super::*; + + #[derive(PartialEq, Eq, Debug)] + struct Foo(i32); + + /// Singleton provider provides the correct value. + #[test] + fn singleton_provider_provides_correct_value() { + let mut builder = Injector::builder(); + builder.provide((|| Foo(42)).singleton()); + + let injector = builder.build(); + let foo: Svc = injector.get().unwrap(); + assert_eq!(&*foo, &Foo(42)); + } + + /// When value is mutated, the provider returns the mutated value. + #[test] + fn singleton_provider_returns_mutated_value() { + let mut builder = Injector::builder(); + builder.provide((|| Mutex::new(Foo(0))).singleton()); + + let injector = builder.build(); + let foo: Svc> = injector.get().unwrap(); + let mut foo = foo.lock().unwrap(); + foo.0 = 42; + drop(foo); + + let foo: Svc> = injector.get().unwrap(); + let foo = foo.lock().unwrap(); + assert_eq!(&*foo, &Foo(42)); + } +} diff --git a/crates/runtime_injector/src/services/transient.rs b/crates/runtime_injector/src/services/transient.rs index e3b9668..09f9dc6 100644 --- a/crates/runtime_injector/src/services/transient.rs +++ b/crates/runtime_injector/src/services/transient.rs @@ -109,3 +109,23 @@ where func.transient() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(PartialEq, Eq, Debug)] + struct Foo(i32); + + /// Transient provider provides the correct value. + #[test] + fn transient_provider_provides_correct_value() { + let mut builder = Injector::builder(); + builder.provide((|| Foo(42)).transient()); + + let injector = builder.build(); + let foo: Svc = injector.get().unwrap(); + + assert_eq!(&*foo, &Foo(42)); + } +} From 9f1eeb83a42b1496f6aa2b24c98399c4cc96b66c Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:21:35 -0700 Subject: [PATCH 06/26] Change RequestInfo to use finish_non_exhaustive --- crates/runtime_injector/src/requests/info.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index 9c92065..88ee280 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -126,10 +126,11 @@ impl Default for RequestInfo { impl Debug for RequestInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - // TODO: maybe use finish_non_exhaustive when 1.53 hits stable f.debug_struct("RequestInfo") .field("service_path", &self.service_path) - .finish() + .finish_non_exhaustive() + } +} #[cfg(test)] mod tests { From afb19576257e6c31d76436e793a3f1264e1149f3 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:22:23 -0700 Subject: [PATCH 07/26] Add more upcasting/downcasting methods --- crates/runtime_injector/src/any.rs | 7 +++++++ crates/runtime_injector/src/requests/parameter.rs | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/runtime_injector/src/any.rs b/crates/runtime_injector/src/any.rs index 3407cd5..13853af 100644 --- a/crates/runtime_injector/src/any.rs +++ b/crates/runtime_injector/src/any.rs @@ -7,6 +7,9 @@ pub trait AsAny: Any { /// Converts `self` into a mutable trait object. fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Converts `Box` into a trait object. + fn into_any(self: Box) -> Box; } impl AsAny for T { @@ -17,4 +20,8 @@ impl AsAny for T { fn as_any_mut(&mut self) -> &mut dyn Any { self } + + fn into_any(self: Box) -> Box { + self + } } diff --git a/crates/runtime_injector/src/requests/parameter.rs b/crates/runtime_injector/src/requests/parameter.rs index 4c496f4..ab89132 100644 --- a/crates/runtime_injector/src/requests/parameter.rs +++ b/crates/runtime_injector/src/requests/parameter.rs @@ -1,3 +1,4 @@ +use std::any::Any; use crate::{AsAny, Service}; /// A parameter for configuring requested services. @@ -13,10 +14,20 @@ impl RequestParameter for T { } impl dyn RequestParameter { - /// Tries to downcast this request parameter to a concrete type. + /// Tries to downcast this request parameter to a concrete reference type. pub fn downcast_ref(&self) -> Option<&T> { self.as_any().downcast_ref() } + + /// Tries to downcast this request parameter to a concrete mutable reference type. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.as_any_mut().downcast_mut() + } + + /// Tries to downcast this request parameter to a concrete owned type. + pub fn downcast(self: Box) -> Result, Box> { + self.into_any().downcast() + } } impl Clone for Box { From 3d0104d2f64049975c13633b798f19eea0626a8a Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 7 Apr 2022 12:22:45 -0700 Subject: [PATCH 08/26] Implement common traits for Arg --- crates/runtime_injector/src/requests/arg.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index edc50d2..77232f3 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -25,6 +25,7 @@ use std::{ /// let foo: Box = injector.get().unwrap(); /// assert_eq!(12, *foo.0); /// ``` +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Arg(T); impl Arg { @@ -56,6 +57,12 @@ impl DerefMut for Arg { } } +impl Display for Arg { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// Allows custom pre-defined values to be passed as arguments to services. impl Request for Arg { fn request(_injector: &Injector, info: &RequestInfo) -> InjectResult { From 98b50d155d87164150fdf4ed19c1dfaafcdf1a65 Mon Sep 17 00:00:00 2001 From: TehPers Date: Mon, 11 Apr 2022 12:32:11 -0700 Subject: [PATCH 09/26] Remove need to specify implementations of interfaces --- crates/runtime_injector/Cargo.toml | 3 + crates/runtime_injector/src/builder.rs | 243 +++++++++++-- crates/runtime_injector/src/injector.rs | 96 +++++- crates/runtime_injector/src/iter.rs | 319 +----------------- .../src/iter/from_provider.rs | 59 ++++ crates/runtime_injector/src/iter/services.rs | 182 ++++++++++ .../runtime_injector/src/iter/services_of.rs | 261 ++++++++++++++ crates/runtime_injector/src/lib.rs | 11 + crates/runtime_injector/src/module.rs | 22 +- .../runtime_injector/src/provider_registry.rs | 157 +++++++++ crates/runtime_injector/src/providers.rs | 17 + .../{services => providers}/conditional.rs | 45 ++- .../src/{services => providers}/constant.rs | 1 + .../src/{services => providers}/fallible.rs | 0 .../src/{services => providers}/func.rs | 9 +- .../src/providers/interface.rs | 98 ++++++ .../src/{services => providers}/providers.rs | 120 ++----- .../src/{services => providers}/singleton.rs | 3 +- .../src/{services => providers}/transient.rs | 1 + crates/runtime_injector/src/requests.rs | 4 +- crates/runtime_injector/src/requests/arg.rs | 16 +- crates/runtime_injector/src/requests/info.rs | 8 +- .../src/requests/parameter.rs | 31 +- .../runtime_injector/src/requests/request.rs | 63 ++-- crates/runtime_injector/src/services.rs | 14 - .../src/services/interface.rs | 151 +++------ .../runtime_injector/src/services/service.rs | 60 ++-- crates/runtime_injector/src/tests.rs | 17 +- 28 files changed, 1296 insertions(+), 715 deletions(-) create mode 100644 crates/runtime_injector/src/iter/from_provider.rs create mode 100644 crates/runtime_injector/src/iter/services.rs create mode 100644 crates/runtime_injector/src/iter/services_of.rs create mode 100644 crates/runtime_injector/src/provider_registry.rs create mode 100644 crates/runtime_injector/src/providers.rs rename crates/runtime_injector/src/{services => providers}/conditional.rs (85%) rename crates/runtime_injector/src/{services => providers}/constant.rs (98%) rename crates/runtime_injector/src/{services => providers}/fallible.rs (100%) rename crates/runtime_injector/src/{services => providers}/func.rs (94%) create mode 100644 crates/runtime_injector/src/providers/interface.rs rename crates/runtime_injector/src/{services => providers}/providers.rs (51%) rename crates/runtime_injector/src/{services => providers}/singleton.rs (99%) rename crates/runtime_injector/src/{services => providers}/transient.rs (98%) diff --git a/crates/runtime_injector/Cargo.toml b/crates/runtime_injector/Cargo.toml index 5791982..09aefc8 100644 --- a/crates/runtime_injector/Cargo.toml +++ b/crates/runtime_injector/Cargo.toml @@ -15,3 +15,6 @@ exclude = [] default = ["arc"] arc = [] # Svc = Arc rc = [] # Svc = Rc + +[dependencies] +downcast-rs = "1" diff --git a/crates/runtime_injector/src/builder.rs b/crates/runtime_injector/src/builder.rs index 8995ef8..4580714 100644 --- a/crates/runtime_injector/src/builder.rs +++ b/crates/runtime_injector/src/builder.rs @@ -1,39 +1,76 @@ use crate::{ - Injector, Module, Provider, ProviderMap, RequestInfo, ServiceInfo, + provider_registry::{ProviderRegistry, ProviderRegistryType}, + Injector, Interface, InterfaceRegistry, Module, Provider, RequestInfo, + Service, ServiceInfo, }; +use downcast_rs::impl_downcast; +use std::collections::{hash_map::Entry, HashMap}; /// A builder for an [`Injector`]. #[derive(Default)] pub struct InjectorBuilder { - providers: ProviderMap, + registry_builder: InterfaceRegistryBuilder, root_info: RequestInfo, } impl InjectorBuilder { /// Assigns the provider for a service type. Multiple providers can be /// registered for a service. - pub fn provide(&mut self, provider: P) { + pub fn provide

(&mut self, provider: P) + where + P: Provider, + { self.add_provider(Box::new(provider)) } /// Adds a provider to the injector. #[allow(clippy::missing_panics_doc)] - pub fn add_provider(&mut self, provider: Box) { - // Should never panic - self.providers - .entry(provider.result()) - .or_insert_with(|| Some(Vec::new())) - .as_mut() - .unwrap() - .push(provider) + pub fn add_provider( + &mut self, + provider: Box>, + ) where + I: ?Sized + Interface, + { + self.registry_builder + .ensure_providers_mut() + .add_provider_for(provider.result(), provider); } /// Removes all providers for a service type. pub fn remove_providers( &mut self, service_info: ServiceInfo, - ) -> Option>> { - self.providers.remove(&service_info).flatten() + ) -> Vec>> { + self.remove_providers_for::(service_info) + } + + /// Removes all providers for a service type from an interface. + pub fn remove_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> Vec>> + where + I: ?Sized + Interface, + { + self.registry_builder + .providers_mut::() + .map(|providers| providers.remove_providers_for(service_info)) + .unwrap_or_default() + } + + /// Clears all providers. + pub fn clear_providers(&mut self) { + self.registry_builder.clear(); + } + + /// Clears all providers for an interface. + pub fn clear_providers_for(&mut self) + where + I: ?Sized + Interface, + { + self.registry_builder + .providers + .remove(&ServiceInfo::of::()); } /// Borrows the root [`RequestInfo`] that will be used by calls to @@ -57,26 +94,180 @@ impl InjectorBuilder { /// module, they are overridden. #[allow(clippy::missing_panics_doc)] pub fn add_module(&mut self, module: Module) { - for (result, module_providers) in module.providers { - // Should never panic - let mut module_providers = module_providers.unwrap(); - self.providers - .entry(result) - .and_modify(|providers| { - // Should never panic - providers.as_mut().unwrap().append(&mut module_providers) - }) - .or_insert_with(|| Some(module_providers)); - } + // Merge providers + self.registry_builder.merge(module.registry_builder); + // Merge parameters for (key, value) in module.parameters { - drop(self.root_info_mut().insert_parameter_boxed(&key, value)); + self.root_info_mut().insert_parameter_boxed(&key, value); } } /// Builds the injector. #[must_use] pub fn build(self) -> Injector { - Injector::new_from_parts(self.providers, self.root_info) + Injector::new_from_parts(self.registry_builder.build(), self.root_info) + } +} + +pub(crate) struct ProviderRegistryBuilder +where + I: ?Sized + Interface, +{ + providers: + HashMap>>>>, +} + +impl ProviderRegistryBuilder +where + I: ?Sized + Interface, +{ + pub fn add_provider_for( + &mut self, + service_info: ServiceInfo, + provider: Box>, + ) { + #[allow(clippy::missing_panics_doc)] + self.providers + .entry(service_info) + .or_insert_with(|| Some(Vec::new())) + .as_mut() + .unwrap() + .push(provider); + } + + pub fn remove_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> Vec>> { + #[allow(clippy::missing_panics_doc)] + self.providers + .remove(&service_info) + .map(Option::unwrap) + .unwrap_or_default() + } +} + +impl Default for ProviderRegistryBuilder +where + I: ?Sized + Interface, +{ + fn default() -> Self { + Self { + providers: HashMap::new(), + } + } +} + +pub(crate) trait ProviderRegistryBuilderType: Service { + fn merge( + &mut self, + other: Box, + ) -> Result<(), Box>; + + fn build(&mut self) -> Box; +} + +impl ProviderRegistryBuilderType for ProviderRegistryBuilder +where + I: ?Sized + Interface, +{ + fn merge( + &mut self, + other: Box, + ) -> Result<(), Box> { + let other: Box = other.downcast()?; + for (service_info, other_providers) in other.providers { + #[allow(clippy::missing_panics_doc)] + let mut other_providers = other_providers.unwrap(); + self.providers + .entry(service_info) + .or_insert_with(|| Some(Vec::new())) + .as_mut() + .unwrap() + .append(&mut other_providers); + } + + Ok(()) + } + + fn build(&mut self) -> Box { + Box::new(ProviderRegistry::new(std::mem::take(&mut self.providers))) + } +} + +#[cfg(feature = "arc")] +impl_downcast!(sync ProviderRegistryBuilderType); + +#[cfg(feature = "rc")] +impl_downcast!(ProviderRegistryBuilderType); + +#[derive(Default)] +pub(crate) struct InterfaceRegistryBuilder { + providers: HashMap>, +} + +impl InterfaceRegistryBuilder { + pub fn providers_mut( + &mut self, + ) -> Option<&mut ProviderRegistryBuilder> + where + I: ?Sized + Interface, + { + #[allow(clippy::missing_panics_doc)] + self.providers + .get_mut(&ServiceInfo::of::()) + .map(|providers| { + providers + .downcast_mut::>() + .unwrap() + }) + } + + pub fn ensure_providers_mut(&mut self) -> &mut ProviderRegistryBuilder + where + I: ?Sized + Interface, + { + #[allow(clippy::missing_panics_doc)] + self.providers + .entry(ServiceInfo::of::()) + .or_insert_with( + || Box::new(ProviderRegistryBuilder::::default()), + ) + .downcast_mut() + .unwrap() + } + + pub fn clear(&mut self) { + self.providers.clear(); + } + + pub fn merge(&mut self, other: InterfaceRegistryBuilder) { + for (service_info, other_providers) in other.providers { + match self.providers.entry(service_info) { + Entry::Occupied(entry) => { + #[allow(clippy::missing_panics_doc)] + entry + .into_mut() + .merge(other_providers) + .map_err(|_| "error merging provider builders") + .unwrap(); + } + Entry::Vacant(entry) => { + entry.insert(other_providers); + } + } + } + } + + pub fn build(self) -> InterfaceRegistry { + let providers = self + .providers + .into_iter() + .map(|(service_info, mut providers)| { + (service_info, providers.build()) + }) + .collect(); + InterfaceRegistry::new(providers) } } diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index 8f8851d..c17ae57 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -1,11 +1,9 @@ use crate::{ - InjectResult, InjectorBuilder, Interface, Provider, Request, RequestInfo, - ServiceInfo, Services, Svc, + FromProvider, InjectError, InjectResult, InjectorBuilder, Interface, + InterfaceRegistry, Provider, Request, RequestInfo, ServiceInfo, Services, + Svc, }; -use std::collections::HashMap; - -pub(crate) type ProviderMap = - HashMap>>>; +use std::ops::{Deref, DerefMut}; pub(crate) trait MapContainerEx { fn new(value: T) -> Self; @@ -116,7 +114,7 @@ pub(crate) use types::*; /// ``` #[derive(Clone, Default)] pub struct Injector { - provider_map: MapContainer, + interface_registry: MapContainer, root_request_info: Svc, } @@ -129,11 +127,11 @@ impl Injector { } pub(crate) fn new_from_parts( - providers: ProviderMap, + interface_registry: InterfaceRegistry, request_info: RequestInfo, ) -> Self { Injector { - provider_map: MapContainerEx::new(providers), + interface_registry: MapContainerEx::new(interface_registry), root_request_info: Svc::new(request_info), } } @@ -293,15 +291,77 @@ impl Injector { /// Gets implementations of a service from the container. This is /// equivalent to requesting [`Services`] from [`Injector::get()`]. - pub(crate) fn get_service( + #[doc(hidden)] + pub fn get_all( &self, request_info: &RequestInfo, - ) -> InjectResult> { - Services::new( - self.clone(), - self.provider_map.clone(), - request_info.clone(), - ) + ) -> InjectResult> { + let service_info = ServiceInfo::of::(); + let providers = self.interface_registry.with_inner_mut(|registry| { + Ok(ProvidersLease { + parent_registry: self.interface_registry.clone(), + providers: registry.take_providers_for(service_info)?, + }) + })?; + + Ok(Services::new(self.clone(), request_info.clone(), providers)) + } +} + +pub(crate) struct ProvidersLease +where + I: ?Sized + Interface, +{ + parent_registry: MapContainer, + providers: Vec>>, +} + +impl Deref for ProvidersLease +where + I: ?Sized + Interface, +{ + type Target = [Box>]; + + fn deref(&self) -> &Self::Target { + self.providers.as_slice() + } +} + +impl DerefMut for ProvidersLease +where + I: ?Sized + Interface, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.providers.as_mut_slice() + } +} + +impl Drop for ProvidersLease +where + I: ?Sized + Interface, +{ + fn drop(&mut self) { + let providers = std::mem::take(&mut self.providers); + let result = self + .parent_registry + .lock() + .map_err(|_| { + InjectError::InternalError( + "failed to acquire lock for interface registry".into(), + ) + }) + .and_then(|mut registry| { + registry + .reclaim_providers_for(ServiceInfo::of::(), providers) + }); + + if let Err(error) = result { + eprintln!( + "An error occurred while releasing providiers for {}: {}", + ServiceInfo::of::().name(), + error + ); + } } } @@ -309,7 +369,7 @@ impl Injector { mod tests { use crate::{ DynSvc, InjectError, InjectResult, Injector, Provider, RequestInfo, - ServiceInfo, Svc, + Service, ServiceInfo, Svc, }; use core::panic; @@ -317,6 +377,8 @@ mod tests { fn get_exact_returns_error_on_invalid_provider() { struct BadProvider; impl Provider for BadProvider { + type Interface = dyn Service; + fn result(&self) -> ServiceInfo { ServiceInfo::of::() } diff --git a/crates/runtime_injector/src/iter.rs b/crates/runtime_injector/src/iter.rs index f954983..fa295d2 100644 --- a/crates/runtime_injector/src/iter.rs +++ b/crates/runtime_injector/src/iter.rs @@ -1,314 +1,7 @@ -use crate::{ - InjectError, InjectResult, Injector, Interface, MapContainer, - MapContainerEx, Provider, ProviderMap, RequestInfo, ServiceInfo, Svc, -}; -use std::{marker::PhantomData, slice::IterMut}; +mod from_provider; +mod services; +// mod services_of; -/// A collection of all the providers for a particular interface. -/// -/// If an interface will only have one implementation registered for it, then -/// it may be easier to request [`Svc`] from the container instead. However, -/// if multiple implementations are registered (or no implementations are -/// registered), then this will allow all of those implementations to be -/// iterated over. -/// -/// An iterator over all the implementations of an interface. Each service is -/// activated on demand. -/// -/// ``` -/// use runtime_injector::{ -/// interface, Injector, IntoTransient, Services, Svc, TypedProvider, Service -/// }; -/// -/// trait Fooable: Service { -/// fn baz(&self) {} -/// } -/// -/// interface!(dyn Fooable = [Foo, Bar]); -/// -/// #[derive(Default)] -/// struct Foo; -/// impl Fooable for Foo {} -/// -/// #[derive(Default)] -/// struct Bar; -/// impl Fooable for Bar {} -/// -/// let mut builder = Injector::builder(); -/// builder.provide(Foo::default.transient().with_interface::()); -/// builder.provide(Bar::default.transient().with_interface::()); -/// -/// let injector = builder.build(); -/// let mut counter = 0; -/// let mut fooables: Services = injector.get().unwrap(); -/// for foo in fooables.get_all() { -/// counter += 1; -/// foo.unwrap().baz(); -/// } -/// -/// assert_eq!(2, counter); -/// ``` -pub struct Services { - injector: Injector, - service_info: ServiceInfo, - request_info: RequestInfo, - provider_map: MapContainer, - providers: Option>>, - marker: PhantomData I>, -} - -impl Services { - pub(crate) fn new( - injector: Injector, - provider_map: MapContainer, - request_info: RequestInfo, - ) -> InjectResult { - let service_info = ServiceInfo::of::(); - let providers = provider_map.with_inner_mut(|provider_map| { - let providers = provider_map - .get_mut(&service_info) - .ok_or(InjectError::MissingProvider { service_info })?; - - providers.take().ok_or_else(|| InjectError::CycleDetected { - service_info, - cycle: vec![service_info], - }) - })?; - - Ok(Services { - injector, - service_info, - request_info, - provider_map, - providers: Some(providers), - marker: PhantomData, - }) - } - - /// Lazily gets all the implementations of this interface. Each service - /// will be requested on demand rather than all at once. - #[allow(clippy::missing_panics_doc)] - pub fn get_all(&mut self) -> ServicesIter<'_, I> { - ServicesIter { - provider_iter: self.providers.as_mut().unwrap().iter_mut(), /* Should never panic */ - injector: &self.injector, - request_info: &self.request_info, - marker: PhantomData, - } - } - - /// Lazily gets all the implementations of this interface as owned service - /// pointers. Each service will be requested on demand rather than all at - /// once. Not all providers can provide owned service pointers, so some - /// requests may fail. - #[allow(clippy::missing_panics_doc)] - pub fn get_all_owned(&mut self) -> OwnedServicesIter<'_, I> { - OwnedServicesIter { - provider_iter: self.providers.as_mut().unwrap().iter_mut(), /* Should never panic */ - injector: &self.injector, - request_info: &self.request_info, - marker: PhantomData, - } - } - - /// Gets the max number of possible implementations of this interface. This - /// does not take into account conditional providers, which may not return - /// an implementation of the service. - #[must_use] - #[allow(clippy::missing_panics_doc)] - pub fn len(&self) -> usize { - // Should never panic - self.providers.as_ref().unwrap().len() - } - - /// Returns `true` if there are no possible implementations of this - /// interface. This does not take into account conditional providers, which - /// may not return an implementation of the service. - #[must_use] - #[allow(clippy::missing_panics_doc)] - pub fn is_empty(&self) -> bool { - // Should never panic - self.providers.as_ref().unwrap().is_empty() - } -} - -impl Drop for Services { - fn drop(&mut self) { - let Services { - ref service_info, - ref mut provider_map, - ref mut providers, - .. - } = self; - - let result = provider_map.with_inner_mut(|provider_map| { - let provider_entry = - provider_map.get_mut(service_info).ok_or_else(|| { - InjectError::InternalError(format!( - "activated provider for {} is no longer registered", - service_info.name() - )) - })?; - - // Should never panic - #[allow(clippy::missing_panics_doc)] - if provider_entry.replace(providers.take().unwrap()).is_some() { - Err(InjectError::InternalError(format!( - "another provider for {} was added during its activation", - service_info.name() - ))) - } else { - Ok(()) - } - }); - - if let Err(error) = result { - eprintln!( - "An error occurred while releasing providiers for {}: {}", - service_info.name(), - error - ); - } - } -} - -/// An iterator over all the implementations of an interface. Each service is -/// activated on demand. -/// -/// ``` -/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; -/// use std::sync::Mutex; -/// -/// struct Foo; -/// -/// fn make_foo(counter: Svc>) -> Foo { -/// // Increment the counter to track how many Foos have been created -/// let mut counter = counter.lock().unwrap(); -/// *counter += 1; -/// Foo -/// } -/// -/// let mut builder = Injector::builder(); -/// builder.provide(constant(Mutex::new(0usize))); -/// builder.provide(make_foo.transient()); -/// -/// let injector = builder.build(); -/// let counter: Svc> = injector.get().unwrap(); -/// let mut foos: Services = injector.get().unwrap(); -/// -/// let mut iter = foos.get_all(); -/// assert_eq!(0, *counter.lock().unwrap()); -/// assert!(iter.next().is_some()); -/// assert_eq!(1, *counter.lock().unwrap()); -/// assert!(iter.next().is_none()); -/// ``` -pub struct ServicesIter<'a, I: ?Sized + Interface> { - provider_iter: IterMut<'a, Box>, - injector: &'a Injector, - request_info: &'a RequestInfo, - marker: PhantomData I>, -} - -impl<'a, I: ?Sized + Interface> Iterator for ServicesIter<'a, I> { - type Item = InjectResult>; - - fn next(&mut self) -> Option { - let ServicesIter { - provider_iter, - injector, - request_info, - .. - } = self; - - provider_iter.find_map(|provider| { - match provider.provide(injector, request_info) { - Ok(result) => Some(I::downcast(result)), - Err(InjectError::ConditionsNotMet { .. }) => None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })) - } - Err(error) => Some(Err(error)), - } - }) - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.provider_iter.len())) - } -} - -/// An iterator over all the implementations of an interface. Each service is -/// activated on demand. -/// -/// ``` -/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; -/// use std::sync::Mutex; -/// -/// #[derive(Clone, Copy, PartialEq, Eq, Debug)] -/// struct Foo(usize); -/// -/// fn make_foo(counter: Svc>) -> Foo { -/// // Increment the counter to track how many Foos have been created -/// let mut counter = counter.lock().unwrap(); -/// *counter += 1; -/// Foo(*counter) -/// } -/// -/// let mut builder = Injector::builder(); -/// builder.provide(constant(Mutex::new(0usize))); -/// builder.provide(make_foo.transient()); -/// -/// let injector = builder.build(); -/// let counter: Svc> = injector.get().unwrap(); -/// let mut foos: Services = injector.get().unwrap(); -/// -/// let mut iter = foos.get_all_owned(); -/// assert_eq!(0, *counter.lock().unwrap()); -/// assert_eq!(Foo(1), *iter.next().unwrap().unwrap()); -/// assert_eq!(1, *counter.lock().unwrap()); -/// assert!(iter.next().is_none()); -/// ``` -pub struct OwnedServicesIter<'a, I: ?Sized + Interface> { - provider_iter: IterMut<'a, Box>, - injector: &'a Injector, - request_info: &'a RequestInfo, - marker: PhantomData I>, -} - -impl<'a, I: ?Sized + Interface> Iterator for OwnedServicesIter<'a, I> { - type Item = InjectResult>; - - fn next(&mut self) -> Option { - let OwnedServicesIter { - provider_iter, - injector, - request_info, - .. - } = self; - - provider_iter.find_map(|provider| { - match provider.provide_owned(injector, request_info) { - Ok(result) => Some(I::downcast_owned(result)), - Err(InjectError::ConditionsNotMet { .. }) => None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })) - } - Err(error) => Some(Err(error)), - } - }) - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.provider_iter.len())) - } -} +pub use from_provider::*; +pub use services::*; +// pub use services_of::*; diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs new file mode 100644 index 0000000..c84b62e --- /dev/null +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -0,0 +1,59 @@ +use crate::{ + InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, Svc, +}; + +pub trait FromProvider: Service { + type Interface: ?Sized + Interface; + + fn should_provide( + provider: &dyn Provider, + ) -> bool; + + fn from_provided(provided: Svc) + -> InjectResult>; + + fn from_provided_owned( + provided: Box, + ) -> InjectResult>; +} + +impl FromProvider for S { + type Interface = dyn Service; + + #[inline] + fn should_provide( + provider: &dyn Provider, + ) -> bool { + provider.result() == ServiceInfo::of::() + } + + #[inline] + fn from_provided( + provided: Svc, + ) -> InjectResult> { + #[cfg(feature = "arc")] + let provided = provided.downcast_arc().map_err(|_| { + InjectError::InvalidProvider { + service_info: ServiceInfo::of::(), + } + })?; + #[cfg(feature = "rc")] + let provided = service.downcast_rc().map_err(|_| { + InjectError::InvalidProvider { + service_info: ServiceInfo::of::(), + } + })?; + Ok(provided) + } + + #[inline] + fn from_provided_owned( + provided: Box, + ) -> InjectResult> { + provided + .downcast() + .map_err(|_| InjectError::InvalidProvider { + service_info: ServiceInfo::of::(), + }) + } +} diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs new file mode 100644 index 0000000..7c0cb9f --- /dev/null +++ b/crates/runtime_injector/src/iter/services.rs @@ -0,0 +1,182 @@ +use crate::{ + FromProvider, InjectError, InjectResult, Injector, Provider, + ProvidersLease, RequestInfo, ServiceInfo, Svc, +}; +use std::{marker::PhantomData, slice::IterMut}; + +pub struct Services +where + S: ?Sized + FromProvider, +{ + injector: Injector, + request_info: RequestInfo, + providers: ProvidersLease, + _marker: PhantomData S>, +} + +impl Services +where + S: ?Sized + FromProvider, +{ + #[inline] + pub(crate) fn new( + injector: Injector, + request_info: RequestInfo, + providers: ProvidersLease, + ) -> Self { + Services { + injector, + request_info, + providers, + _marker: PhantomData, + } + } + + /// Lazily gets all provided services of the given type. Each service will + /// be requested on demand rather than all at once. + #[inline] + pub fn iter(&mut self) -> ServicesIter<'_, S> { + ServicesIter { + injector: &self.injector, + request_info: &self.request_info, + provider_iter: self.providers.iter_mut(), + _marker: PhantomData, + } + } + + /// Lazily gets all provided owned services of the given type. Each service + /// will be requested on demand rather than all at once. + #[inline] + pub fn iter_owned(&mut self) -> OwnedServicesIter<'_, S> { + OwnedServicesIter { + injector: &self.injector, + request_info: &self.request_info, + provider_iter: self.providers.iter_mut(), + _marker: PhantomData, + } + } + + /// Gets the number of provided services of the given type registered to + /// this interface. This does not take into account conditional providers + /// which may not return an implementation of the service. + #[inline] + pub fn len(&self) -> usize { + self.providers.len() + } + + /// Returns whether there are no providers for the given service and + /// interface. Conditional providers still may not return an implementation + /// of the service even if this returns `true`. + #[inline] + pub fn is_empty(&self) -> bool { + self.providers.is_empty() + } +} + +pub struct ServicesIter<'a, S> +where + S: ?Sized + FromProvider, +{ + injector: &'a Injector, + request_info: &'a RequestInfo, + provider_iter: IterMut<'a, Box>>, + _marker: PhantomData S>, +} + +impl<'a, S> Iterator for ServicesIter<'a, S> +where + S: ?Sized + FromProvider, +{ + type Item = InjectResult>; + + fn next(&mut self) -> Option { + self.provider_iter.find_map(|provider| { + // Skip providers that don't match the requested service + if !S::should_provide(provider.as_ref()) { + return None; + } + + // Provide the service + let service = + match provider.provide(self.injector, self.request_info) { + Ok(service) => service, + Err(InjectError::ConditionsNotMet { .. }) => return None, + Err(InjectError::CycleDetected { mut cycle, .. }) => { + let service_info = ServiceInfo::of::(); + cycle.push(service_info); + return Some(Err(InjectError::CycleDetected { + service_info, + cycle, + })); + } + Err(error) => return Some(Err(error)), + }; + + // Downcast the service + let service = S::from_provided(service); + Some(service) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} + +pub struct OwnedServicesIter<'a, S> +where + S: ?Sized + FromProvider, +{ + injector: &'a Injector, + request_info: &'a RequestInfo, + provider_iter: IterMut<'a, Box>>, + _marker: PhantomData S>, +} + +impl<'a, S> Iterator for OwnedServicesIter<'a, S> +where + S: ?Sized + FromProvider, +{ + type Item = InjectResult>; + + fn next(&mut self) -> Option { + self.provider_iter.find_map(|provider| { + // Skip providers that don't match the requested service + if !S::should_provide(provider.as_ref()) { + return None; + } + + // Provide the service + let service = match provider + .provide_owned(self.injector, self.request_info) + { + Ok(service) => service, + Err(InjectError::ConditionsNotMet { .. }) => return None, + Err(InjectError::CycleDetected { mut cycle, .. }) => { + let service_info = ServiceInfo::of::(); + cycle.push(service_info); + return Some(Err(InjectError::CycleDetected { + service_info, + cycle, + })); + } + Err(error) => return Some(Err(error)), + }; + + // Downcast the service + let service = S::from_provided_owned(service); + Some(service) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} + +#[cfg(test)] +mod tests { + use super::*; +} diff --git a/crates/runtime_injector/src/iter/services_of.rs b/crates/runtime_injector/src/iter/services_of.rs new file mode 100644 index 0000000..9d7480a --- /dev/null +++ b/crates/runtime_injector/src/iter/services_of.rs @@ -0,0 +1,261 @@ +use crate::{ + InjectError, InjectResult, Injector, Interface, Provider, ProvidersLease, + RequestInfo, Service, ServiceInfo, Services, Svc, +}; +use std::slice::IterMut; + +/// A collection of all the providers for a particular interface. Each service +/// is activated only during iteration of this collection. +/// +/// If an interface will only have one implementation registered for it, then +/// it may be easier to request [`Svc`] from the container instead. However, +/// if multiple implementations are registered (or no implementations are +/// registered), then this will allow all of those implementations to be +/// iterated over. +/// +/// ``` +/// use runtime_injector::{ +/// interface, Injector, IntoTransient, Services, Svc, TypedProvider, Service +/// }; +/// +/// trait Fooable: Service { +/// fn baz(&self) {} +/// } +/// +/// interface!(Fooable); +/// +/// #[derive(Default)] +/// struct Foo; +/// impl Fooable for Foo {} +/// +/// #[derive(Default)] +/// struct Bar; +/// impl Fooable for Bar {} +/// +/// let mut builder = Injector::builder(); +/// builder.provide(Foo::default.transient().with_interface::()); +/// builder.provide(Bar::default.transient().with_interface::()); +/// +/// let injector = builder.build(); +/// let mut counter = 0; +/// let mut fooables: Services = injector.get().unwrap(); +/// for foo in fooables.get_all() { +/// counter += 1; +/// foo.unwrap().baz(); +/// } +/// +/// assert_eq!(2, counter); +/// ``` +pub struct ServicesOf +where + I: ?Sized + Interface, +{ + injector: Injector, + request_info: RequestInfo, + providers: ProvidersLease, +} + +impl ServicesOf +where + I: ?Sized + Interface, +{ + #[inline] + pub(crate) fn new( + injector: Injector, + request_info: RequestInfo, + providers: ProvidersLease, + ) -> Self { + ServicesOf { + injector, + request_info, + providers, + } + } + + /// Lazily gets all the implementations of this interface. Each service + /// will be requested on demand rather than all at once. + #[inline] + pub fn iter(&mut self) -> ServicesOfIter<'_, I> { + ServicesOfIter { + injector: &self.injector, + request_info: &self.request_info, + provider_iter: self.providers.iter_mut(), + } + } + + /// Lazily gets all the implementations of this interface as owned service + /// pointers. Each service will be requested on demand rather than all at + /// once. Not all providers can provide owned service pointers, so some + /// requests may fail. + #[inline] + pub fn iter_owned(&mut self) -> OwnedServicesOfIter<'_, I> { + OwnedServicesOfIter { + injector: &self.injector, + request_info: &self.request_info, + provider_iter: self.providers.iter_mut(), + } + } + + /// Gets the max number of possible implementations of this interface. This + /// does not take into account conditional providers, which may not return + /// an implementation of the service. + #[inline] + pub fn len(&self) -> usize { + self.providers.len() + } + + /// Returns `true` if there are no possible implementations of this + /// interface. This does not take into account conditional providers, which + /// may not return an implementation of the service. + #[inline] + pub fn is_empty(&self) -> bool { + self.providers.is_empty() + } +} + +impl ServicesOf { + pub fn of_service(self) -> Services + where + S: Service, + { + Services::new(self.injector, self.request_info, self.providers) + } +} + +/// An iterator over all the implementations of an interface. Each service is +/// activated on demand. +/// +/// ``` +/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; +/// use std::sync::Mutex; +/// +/// struct Foo; +/// +/// fn make_foo(counter: Svc>) -> Foo { +/// // Increment the counter to track how many Foos have been created +/// let mut counter = counter.lock().unwrap(); +/// *counter += 1; +/// Foo +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide(constant(Mutex::new(0usize))); +/// builder.provide(make_foo.transient()); +/// +/// let injector = builder.build(); +/// let counter: Svc> = injector.get().unwrap(); +/// let mut foos: Services = injector.get().unwrap(); +/// +/// let mut iter = foos.get_all(); +/// assert_eq!(0, *counter.lock().unwrap()); +/// assert!(iter.next().is_some()); +/// assert_eq!(1, *counter.lock().unwrap()); +/// assert!(iter.next().is_none()); +/// ``` +pub struct ServicesOfIter<'a, I> +where + I: ?Sized + Interface, +{ + injector: &'a Injector, + request_info: &'a RequestInfo, + provider_iter: IterMut<'a, Box>>, +} + +impl<'a, I> Iterator for ServicesOfIter<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult>; + + fn next(&mut self) -> Option { + self.provider_iter.find_map(|provider| { + match provider.provide(self.injector, self.request_info) { + Ok(service) => Some(Ok(service)), + Err(InjectError::ConditionsNotMet { .. }) => None, + Err(InjectError::CycleDetected { mut cycle, .. }) => { + let service_info = ServiceInfo::of::(); + cycle.push(service_info); + Some(Err(InjectError::CycleDetected { + service_info, + cycle, + })) + } + Err(error) => Some(Err(error)), + } + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} + +/// An iterator over all the implementations of an interface. Each service is +/// activated on demand. +/// +/// ``` +/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; +/// use std::sync::Mutex; +/// +/// #[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// struct Foo(usize); +/// +/// fn make_foo(counter: Svc>) -> Foo { +/// // Increment the counter to track how many Foos have been created +/// let mut counter = counter.lock().unwrap(); +/// *counter += 1; +/// Foo(*counter) +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide(constant(Mutex::new(0usize))); +/// builder.provide(make_foo.transient()); +/// +/// let injector = builder.build(); +/// let counter: Svc> = injector.get().unwrap(); +/// let mut foos: Services = injector.get().unwrap(); +/// +/// let mut iter = foos.get_all_owned(); +/// assert_eq!(0, *counter.lock().unwrap()); +/// assert_eq!(Foo(1), *iter.next().unwrap().unwrap()); +/// assert_eq!(1, *counter.lock().unwrap()); +/// assert!(iter.next().is_none()); +/// ``` +pub struct OwnedServicesOfIter<'a, I> +where + I: ?Sized + Interface, +{ + injector: &'a Injector, + request_info: &'a RequestInfo, + provider_iter: IterMut<'a, Box>>, +} + +impl<'a, I> Iterator for OwnedServicesOfIter<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult>; + + fn next(&mut self) -> Option { + self.provider_iter.find_map(|provider| { + match provider.provide_owned(self.injector, self.request_info) { + Ok(service) => Some(Ok(service)), + Err(InjectError::ConditionsNotMet { .. }) => None, + Err(InjectError::CycleDetected { mut cycle, .. }) => { + let service_info = ServiceInfo::of::(); + cycle.push(service_info); + Some(Err(InjectError::CycleDetected { + service_info, + cycle, + })) + } + Err(error) => Some(Err(error)), + } + }) + } + + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 7e3789f..6bdf603 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -257,6 +257,8 @@ mod builder; mod injector; mod iter; mod module; +mod provider_registry; +mod providers; mod requests; mod services; @@ -265,10 +267,19 @@ pub use builder::*; pub use injector::*; pub use iter::*; pub use module::*; +pub(crate) use provider_registry::*; +pub use providers::*; pub use requests::*; pub use services::*; pub mod docs; +#[doc(hidden)] +pub mod private { + //! Private re-exports. Changes in this module are never considered breaking. + + pub use downcast_rs; +} + #[cfg(test)] mod tests; diff --git a/crates/runtime_injector/src/module.rs b/crates/runtime_injector/src/module.rs index 18c7713..94771cb 100644 --- a/crates/runtime_injector/src/module.rs +++ b/crates/runtime_injector/src/module.rs @@ -1,4 +1,4 @@ -use crate::{Provider, ProviderMap, RequestParameter}; +use crate::{InterfaceRegistryBuilder, Provider, RequestParameter}; use std::collections::HashMap; /// A collection of providers that can be added all at once to an @@ -10,22 +10,22 @@ use std::collections::HashMap; /// [`define_module!`]. #[derive(Default)] pub struct Module { - pub(crate) providers: ProviderMap, + pub(crate) registry_builder: InterfaceRegistryBuilder, pub(crate) parameters: HashMap>, } impl Module { - /// Assigns the provider for a service type. Multiple providers can be - /// registered for a service. + /// Inserts a provider for a service type and interface. Multiple providers + /// can be registered for a service at once. #[allow(clippy::missing_panics_doc)] - pub fn provide(&mut self, provider: P) { + pub fn provide

(&mut self, provider: P) + where + P: Provider, + { // Should never panic - self.providers - .entry(provider.result()) - .or_insert_with(|| Some(Vec::new())) - .as_mut() - .unwrap() - .push(Box::new(provider)); + self.registry_builder + .ensure_providers_mut() + .add_provider_for(provider.result(), Box::new(provider)); } /// Sets the of a value request parameter for requests made by the injector diff --git a/crates/runtime_injector/src/provider_registry.rs b/crates/runtime_injector/src/provider_registry.rs new file mode 100644 index 0000000..66767fa --- /dev/null +++ b/crates/runtime_injector/src/provider_registry.rs @@ -0,0 +1,157 @@ +use crate::{ + InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, +}; +use std::collections::HashMap; + +/// Stores providers for a particular interface +#[derive(Default)] +pub(crate) struct ProviderRegistry +where + I: ?Sized + Interface, +{ + providers: + HashMap>>>>, +} + +impl ProviderRegistry +where + I: ?Sized + Interface, +{ + pub fn new( + providers: HashMap< + ServiceInfo, + Option>>>, + >, + ) -> Self { + ProviderRegistry { providers } + } + + /// Gets the providers for a particular service type. + pub fn take_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> InjectResult>>> { + // Get the provider list slot + let slot = self + .providers + .get_mut(&service_info) + .ok_or_else(|| InjectError::MissingProvider { service_info })?; + + // Ensure the providers are not in use + slot.take().ok_or_else(|| InjectError::CycleDetected { + service_info, + cycle: vec![service_info], + }) + } + + /// Reclaims the providers for a particular service type. + pub fn reclaim_providers_for( + &mut self, + service_info: ServiceInfo, + providers: Vec>>, + ) -> InjectResult<()> { + // Get the provider list slot + let slot = self.providers.get_mut(&service_info).ok_or_else(|| { + InjectError::InternalError(format!( + "activated provider for {} is no longer registered", + service_info.name() + )) + })?; + + // Insert the providers back into the list, ensuring the list is in use + if slot.replace(providers).is_some() { + Err(InjectError::InternalError(format!( + "another provider for {} was added during its activation", + service_info.name() + ))) + } else { + Ok(()) + } + } +} + +/// Marker trait for provider registries. +pub(crate) trait ProviderRegistryType: Service {} +impl ProviderRegistryType for ProviderRegistry where I: ?Sized + Interface {} + +#[cfg(feature = "arc")] +downcast_rs::impl_downcast!(sync ProviderRegistryType); + +#[cfg(feature = "rc")] +downcast_rs::impl_downcast!(ProviderRegistryType); + +#[derive(Default)] +pub(crate) struct InterfaceRegistry { + provider_registries: HashMap>, +} + +impl InterfaceRegistry { + pub fn new( + provider_registries: HashMap< + ServiceInfo, + Box, + >, + ) -> Self { + InterfaceRegistry { + provider_registries, + } + } + + pub fn take_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> InjectResult>>> + where + I: ?Sized + Interface, + { + // Get provider registry + let interface_info = ServiceInfo::of::(); + let provider_registry = self + .provider_registries + .get_mut(&interface_info) + .ok_or_else(|| InjectError::MissingProvider { service_info })?; + + // Downcast provider list + let provider_registry: &mut ProviderRegistry = provider_registry + .downcast_mut() + .ok_or_else(|| InjectError::InvalidProvider { + service_info: { interface_info }, + })?; + + // Get providers + provider_registry.take_providers_for(service_info) + } + + pub fn reclaim_providers_for( + &mut self, + service_info: ServiceInfo, + providers: Vec>>, + ) -> InjectResult<()> + where + I: ?Sized + Interface, + { + // Get the provider registry + let interface_info = ServiceInfo::of::(); + let provider_registry = self + .provider_registries + .get_mut(&interface_info) + .ok_or_else(|| { + InjectError::InternalError(format!( + "activated provider for {} is no longer registered", + interface_info.name() + )) + })?; + + // Downcast the provider registry + let provider_registry: &mut ProviderRegistry<_> = + provider_registry.downcast_mut().ok_or_else(|| { + InjectError::InternalError(format!( + "provider for {} is the wrong type", + interface_info.name() + )) + })?; + + // Reclaim the providers + provider_registry.reclaim_providers_for(service_info, providers) + } +} diff --git a/crates/runtime_injector/src/providers.rs b/crates/runtime_injector/src/providers.rs new file mode 100644 index 0000000..522390d --- /dev/null +++ b/crates/runtime_injector/src/providers.rs @@ -0,0 +1,17 @@ +mod conditional; +mod constant; +mod fallible; +mod func; +mod interface; +mod providers; +mod singleton; +mod transient; + +pub use conditional::*; +pub use constant::*; +pub use fallible::*; +pub use func::*; +pub use interface::*; +pub use providers::*; +pub use singleton::*; +pub use transient::*; diff --git a/crates/runtime_injector/src/services/conditional.rs b/crates/runtime_injector/src/providers/conditional.rs similarity index 85% rename from crates/runtime_injector/src/services/conditional.rs rename to crates/runtime_injector/src/providers/conditional.rs index 4ba5f4b..86dbd70 100644 --- a/crates/runtime_injector/src/services/conditional.rs +++ b/crates/runtime_injector/src/providers/conditional.rs @@ -22,6 +22,7 @@ where P: TypedProvider, F: Service + Fn(&Injector, &RequestInfo) -> bool, { + type Interface =

::Interface; type Result = P::Result; #[inline] @@ -56,7 +57,7 @@ where } /// Defines a conversion into a conditional provider. This trait is -/// automatically implemented for all types that implement [`TypedProvider`]. +/// automatically implemented for all types that implement [`TypedProvider`]. pub trait WithCondition: TypedProvider { /// Creates a conditional provider. Conditional providers create their /// values only if their condition is met. If the condition is not met, @@ -127,17 +128,16 @@ mod tests { fn test_condition_true_once() { let mut builder = Injector::builder(); let provided = Mutex::new(false); - builder.provide( - Foo::default.singleton() - .with_condition(move |_, _| { - let mut provided = provided.lock().unwrap(); - if *provided { - return false; - } - *provided = true; - true - }), - ); + builder.provide(Foo::default.singleton().with_condition( + move |_, _| { + let mut provided = provided.lock().unwrap(); + if *provided { + return false; + } + *provided = true; + true + }, + )); // Create first value let injector = builder.build(); @@ -154,17 +154,16 @@ mod tests { fn test_condition_true_after_false() { let mut builder = Injector::builder(); let provided = Mutex::new(false); - builder.provide( - Foo::default.singleton() - .with_condition(move |_, _| { - let mut provided = provided.lock().unwrap(); - if *provided { - return true; - } - *provided = true; - false - }), - ); + builder.provide(Foo::default.singleton().with_condition( + move |_, _| { + let mut provided = provided.lock().unwrap(); + if *provided { + return true; + } + *provided = true; + false + }, + )); // Create first value let injector = builder.build(); diff --git a/crates/runtime_injector/src/services/constant.rs b/crates/runtime_injector/src/providers/constant.rs similarity index 98% rename from crates/runtime_injector/src/services/constant.rs rename to crates/runtime_injector/src/providers/constant.rs index 556167d..a3d10f3 100644 --- a/crates/runtime_injector/src/services/constant.rs +++ b/crates/runtime_injector/src/providers/constant.rs @@ -27,6 +27,7 @@ impl TypedProvider for ConstantProvider where R: Service, { + type Interface = dyn Service; type Result = R; fn provide_typed( diff --git a/crates/runtime_injector/src/services/fallible.rs b/crates/runtime_injector/src/providers/fallible.rs similarity index 100% rename from crates/runtime_injector/src/services/fallible.rs rename to crates/runtime_injector/src/providers/fallible.rs diff --git a/crates/runtime_injector/src/services/func.rs b/crates/runtime_injector/src/providers/func.rs similarity index 94% rename from crates/runtime_injector/src/services/func.rs rename to crates/runtime_injector/src/providers/func.rs index 5633f31..fd86673 100644 --- a/crates/runtime_injector/src/services/func.rs +++ b/crates/runtime_injector/src/providers/func.rs @@ -1,6 +1,5 @@ -use crate::{ - InjectResult, Injector, Request, RequestInfo, Service, ServiceInfo, -}; +use crate::{InjectResult, Injector, Request, RequestInfo, ServiceInfo}; +use std::any::Any; /// A factory for creating instances of a service. All functions of arity 12 or /// less are automatically service factories if the arguments to that function @@ -27,7 +26,7 @@ use crate::{ /// ``` pub trait ServiceFactory { /// The resulting service from invoking this service factory. - type Result: Service; + type Result: Any; /// Invokes this service factory, creating an instance of the service. fn invoke( @@ -49,7 +48,7 @@ macro_rules! impl_provider_function { impl ServiceFactory<($($type_name,)*)> for F where F: Fn($($type_name),*) -> R, - R: Service, + R: Any, $($type_name: Request,)* { type Result = F::Output; diff --git a/crates/runtime_injector/src/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs new file mode 100644 index 0000000..fc63b7b --- /dev/null +++ b/crates/runtime_injector/src/providers/interface.rs @@ -0,0 +1,98 @@ +use crate::{ + InjectResult, Injector, InterfaceFor, RequestInfo, Svc, TypedProvider, +}; +use std::marker::PhantomData; + +/// Provides a service as an implementation of an interface. See +/// [`TypedProvider::with_interface()`] for more information. +pub struct InterfaceProvider +where + P: TypedProvider, + I: ?Sized + InterfaceFor, +{ + inner: P, + _marker: PhantomData I>, +} + +impl TypedProvider for InterfaceProvider +where + P: TypedProvider, + I: ?Sized + InterfaceFor, +{ + type Interface = I; + type Result = P::Result; + + fn provide_typed( + &mut self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + self.inner.provide_typed(injector, request_info) + } + + fn provide_owned_typed( + &mut self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + self.inner.provide_owned_typed(injector, request_info) + } +} + +/// Defines a conversion into an interface provider. This trait is +/// automatically implemented for all types that implement [`TypedProvider`]. +pub trait WithInterface: TypedProvider { + /// Provides this service as an implementation of a particular interface. + /// Rather than requesting this service with its concrete type, it can + /// instead be requested by its interface type. + /// + /// *Note: it cannot be requested with its concrete type once it has been + /// assigned an interface.* + /// + /// ## Example + /// + /// ``` + /// use runtime_injector::{ + /// interface, InjectResult, Injector, IntoSingleton, Service, Svc, + /// TypedProvider, + /// }; + /// + /// trait Fooable: Service { + /// fn bar(&self) {} + /// } + /// + /// interface!(Fooable); + /// + /// #[derive(Default)] + /// struct Foo; + /// impl Fooable for Foo {} + /// + /// let mut builder = Injector::builder(); + /// builder.provide(Foo::default.singleton().with_interface::()); + /// + /// // Foo can now be requested through its interface of `dyn Fooable`. + /// let injector = builder.build(); + /// let fooable: Svc = injector.get().unwrap(); + /// fooable.bar(); + /// + /// // It can't be requested through its original type + /// assert!(injector.get::>().is_err()); + /// ``` + fn with_interface>( + self, + ) -> InterfaceProvider; +} + +impl

WithInterface for P +where + P: TypedProvider, +{ + fn with_interface>( + self, + ) -> InterfaceProvider { + InterfaceProvider { + inner: self, + _marker: PhantomData, + } + } +} diff --git a/crates/runtime_injector/src/services/providers.rs b/crates/runtime_injector/src/providers/providers.rs similarity index 51% rename from crates/runtime_injector/src/services/providers.rs rename to crates/runtime_injector/src/providers/providers.rs index 32cb751..87ac627 100644 --- a/crates/runtime_injector/src/services/providers.rs +++ b/crates/runtime_injector/src/providers/providers.rs @@ -1,17 +1,18 @@ -use std::marker::PhantomData; - use crate::{ - DynSvc, InjectError, InjectResult, Injector, Interface, InterfaceFor, - OwnedDynSvc, RequestInfo, Service, ServiceInfo, Svc, + InjectError, InjectResult, Injector, Interface, InterfaceFor, RequestInfo, + Service, ServiceInfo, Svc, }; /// Weakly typed service provider. /// -/// Given an injector, this can provide an instance of a service. This is +/// Given an injector, this can provide an instance of an interface. This is /// automatically implemented for all types that implement [`TypedProvider`], /// and [`TypedProvider`] should be preferred if possible for custom service /// providers to allow for stronger type checking. pub trait Provider: Service { + /// The interface this provider is providing for. + type Interface: ?Sized + Interface; + /// The [`ServiceInfo`] which describes the type returned by this provider. fn result(&self) -> ServiceInfo; @@ -20,14 +21,14 @@ pub trait Provider: Service { &mut self, injector: &Injector, request_info: &RequestInfo, - ) -> InjectResult; + ) -> InjectResult>; /// Provides an owned instance of the service. fn provide_owned( &mut self, _injector: &Injector, _request_info: &RequestInfo, - ) -> InjectResult { + ) -> InjectResult> { Err(InjectError::OwnedNotSupported { service_info: self.result(), }) @@ -38,6 +39,8 @@ impl Provider for T where T: TypedProvider, { + type Interface = ::Interface; + fn result(&self) -> ServiceInfo { ServiceInfo::of::() } @@ -46,18 +49,18 @@ where &mut self, injector: &Injector, request_info: &RequestInfo, - ) -> InjectResult { - let result = self.provide_typed(injector, request_info)?; - Ok(result as DynSvc) + ) -> InjectResult> { + let service = self.provide_typed(injector, request_info)?; + Ok(Self::Interface::from_svc(service)) } fn provide_owned( &mut self, injector: &Injector, request_info: &RequestInfo, - ) -> InjectResult { - let result = self.provide_owned_typed(injector, request_info)?; - Ok(result as OwnedDynSvc) + ) -> InjectResult> { + let service = self.provide_owned_typed(injector, request_info)?; + Ok(Self::Interface::from_owned_svc(service)) } } @@ -98,9 +101,14 @@ where /// let injector = builder.build(); /// let _foo: Svc = injector.get().unwrap(); /// ``` -pub trait TypedProvider: Sized + Provider { +pub trait TypedProvider: + Sized + Provider::Interface> +{ + /// The interface this provider is providing for. + type Interface: ?Sized + InterfaceFor; + /// The type of service this can provide. - type Result: Interface; + type Result: Service; /// Provides an instance of the service. The [`Injector`] passed in can be /// used to retrieve instances of any dependencies this service has. @@ -121,86 +129,4 @@ pub trait TypedProvider: Sized + Provider { service_info: ServiceInfo::of::(), }) } - - /// Provides this service as an implementation of a particular interface. - /// Rather than requesting this service with its concrete type, it can - /// instead be requested by its interface type. - /// - /// *Note: it cannot be requested with its concrete type once it has been - /// assigned an interface.* - /// - /// ## Example - /// - /// ``` - /// use runtime_injector::{ - /// interface, InjectResult, Injector, IntoSingleton, Service, Svc, - /// TypedProvider, - /// }; - /// - /// trait Fooable: Service { - /// fn bar(&self) {} - /// } - /// - /// interface!(dyn Fooable = [Foo]); - /// - /// #[derive(Default)] - /// struct Foo; - /// impl Fooable for Foo {} - /// - /// let mut builder = Injector::builder(); - /// builder.provide(Foo::default.singleton().with_interface::()); - /// - /// // Foo can now be requested through its interface of `dyn Fooable`. - /// let injector = builder.build(); - /// let fooable: Svc = injector.get().unwrap(); - /// fooable.bar(); - /// - /// // It can't be requested through its original type - /// assert!(injector.get::>().is_err()); - /// ``` - fn with_interface>( - self, - ) -> InterfaceProvider { - InterfaceProvider { - inner: self, - marker: PhantomData, - } - } -} - -/// Provides a service as an implementation of an interface. See -/// [`TypedProvider::with_interface()`] for more information. -pub struct InterfaceProvider -where - P: TypedProvider, - I: ?Sized + InterfaceFor, -{ - inner: P, - marker: PhantomData I>, -} - -impl Provider for InterfaceProvider -where - P: TypedProvider, - I: ?Sized + InterfaceFor, -{ - fn result(&self) -> ServiceInfo { - ServiceInfo::of::() - } - - fn provide( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult { - self.inner.provide(injector, request_info) - } - - fn provide_owned( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult { - self.inner.provide_owned(injector, request_info) - } } diff --git a/crates/runtime_injector/src/services/singleton.rs b/crates/runtime_injector/src/providers/singleton.rs similarity index 99% rename from crates/runtime_injector/src/services/singleton.rs rename to crates/runtime_injector/src/providers/singleton.rs index b848d68..6aff9a7 100644 --- a/crates/runtime_injector/src/services/singleton.rs +++ b/crates/runtime_injector/src/providers/singleton.rs @@ -39,6 +39,7 @@ where R: Service, F: Service + ServiceFactory, { + type Interface = dyn Service; type Result = R; fn provide_typed( @@ -111,8 +112,8 @@ where #[cfg(test)] mod tests { - use std::sync::Mutex; use super::*; + use std::sync::Mutex; #[derive(PartialEq, Eq, Debug)] struct Foo(i32); diff --git a/crates/runtime_injector/src/services/transient.rs b/crates/runtime_injector/src/providers/transient.rs similarity index 98% rename from crates/runtime_injector/src/services/transient.rs rename to crates/runtime_injector/src/providers/transient.rs index 09f9dc6..bb37f57 100644 --- a/crates/runtime_injector/src/services/transient.rs +++ b/crates/runtime_injector/src/providers/transient.rs @@ -37,6 +37,7 @@ where R: Service, F: Service + ServiceFactory, { + type Interface = dyn Service; type Result = R; fn provide_typed( diff --git a/crates/runtime_injector/src/requests.rs b/crates/runtime_injector/src/requests.rs index bfbab42..d6705ef 100644 --- a/crates/runtime_injector/src/requests.rs +++ b/crates/runtime_injector/src/requests.rs @@ -1,10 +1,10 @@ -mod arg; +// mod arg; mod factory; mod info; mod parameter; mod request; -pub use arg::*; +// pub use arg::*; pub use factory::*; pub use info::*; pub use parameter::*; diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index 77232f3..e27c7e4 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -26,14 +26,14 @@ use std::{ /// assert_eq!(12, *foo.0); /// ``` #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Arg(T); +pub struct Arg(T); -impl Arg { +impl Arg { pub(crate) fn param_name(target: ServiceInfo) -> String { format!( "runtime_injector::Arg[target={:?},type={:?}]", - target.id(), - ServiceInfo::of::().id() + target.name(), + ServiceInfo::of::().name() ) } @@ -43,7 +43,7 @@ impl Arg { } } -impl Deref for Arg { +impl Deref for Arg { type Target = T; fn deref(&self) -> &Self::Target { @@ -51,20 +51,20 @@ impl Deref for Arg { } } -impl DerefMut for Arg { +impl DerefMut for Arg { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl Display for Arg { +impl Display for Arg { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } /// Allows custom pre-defined values to be passed as arguments to services. -impl Request for Arg { +impl Request for Arg { fn request(_injector: &Injector, info: &RequestInfo) -> InjectResult { let parent_request = info.service_path().last().ok_or_else(|| { InjectError::ActivationFailed { diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index 88ee280..ebf5f27 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -5,7 +5,7 @@ use std::{ }; /// Information about an active request. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct RequestInfo { service_path: Vec, parameters: HashMap>, @@ -118,12 +118,6 @@ impl RequestInfo { } } -impl Default for RequestInfo { - fn default() -> Self { - RequestInfo::new() - } -} - impl Debug for RequestInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("RequestInfo") diff --git a/crates/runtime_injector/src/requests/parameter.rs b/crates/runtime_injector/src/requests/parameter.rs index ab89132..3bdc3ac 100644 --- a/crates/runtime_injector/src/requests/parameter.rs +++ b/crates/runtime_injector/src/requests/parameter.rs @@ -1,32 +1,21 @@ -use std::any::Any; -use crate::{AsAny, Service}; +use crate::Service; +use downcast_rs::impl_downcast; /// A parameter for configuring requested services. -pub trait RequestParameter: Service + AsAny { +pub trait RequestParameter: Service { /// Clones this parameter into a boxed trait object. fn clone_dyn(&self) -> Box; } -impl RequestParameter for T { - fn clone_dyn(&self) -> Box { - Box::new(self.clone()) - } -} +#[cfg(feature = "arc")] +impl_downcast!(sync RequestParameter); -impl dyn RequestParameter { - /// Tries to downcast this request parameter to a concrete reference type. - pub fn downcast_ref(&self) -> Option<&T> { - self.as_any().downcast_ref() - } +#[cfg(feature = "rc")] +impl_downcast!(RequestParameter); - /// Tries to downcast this request parameter to a concrete mutable reference type. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - self.as_any_mut().downcast_mut() - } - - /// Tries to downcast this request parameter to a concrete owned type. - pub fn downcast(self: Box) -> Result, Box> { - self.into_any().downcast() +impl RequestParameter for T { + fn clone_dyn(&self) -> Box { + Box::new(self.clone()) } } diff --git a/crates/runtime_injector/src/requests/request.rs b/crates/runtime_injector/src/requests/request.rs index 5256043..aba28bc 100644 --- a/crates/runtime_injector/src/requests/request.rs +++ b/crates/runtime_injector/src/requests/request.rs @@ -1,6 +1,6 @@ use crate::{ - InjectError, InjectResult, Injector, Interface, RequestInfo, ServiceInfo, - Services, Svc, + FromProvider, InjectError, InjectResult, Injector, RequestInfo, + ServiceInfo, Services, Svc, }; /// A request to an injector. @@ -63,6 +63,7 @@ pub trait Request: Sized { /// Requests the injector used to resolve services. impl Request for Injector { + #[inline] fn request(injector: &Injector, _info: &RequestInfo) -> InjectResult { Ok(injector.clone()) } @@ -70,25 +71,27 @@ impl Request for Injector { /// Requests the information about the current request. impl Request for RequestInfo { + #[inline] fn request(_injector: &Injector, info: &RequestInfo) -> InjectResult { Ok(info.clone()) } } -/// Requests a service pointer to a service or interface. This request fails if -/// there is not exactly one implementation of the given interface. -impl Request for Svc { +/// Requests a service pointer to a service. This request fails if there is not +/// exactly one implementation of the given service type. +impl Request for Svc { + #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - let mut services: Services = injector.get_with(info)?; + let mut services: Services = injector.get_with(info)?; if services.len() > 1 { Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), + service_info: ServiceInfo::of::(), providers: services.len(), }) } else { - let service = services.get_all().next().transpose()?.ok_or( + let service = services.iter().next().transpose()?.ok_or( InjectError::MissingProvider { - service_info: ServiceInfo::of::(), + service_info: ServiceInfo::of::(), }, )?; @@ -97,21 +100,21 @@ impl Request for Svc { } } -/// Requests an owned pointer to a service or interface. Not all providers can -/// provide owned pointers to their service, so this may fail where [`Svc`] -/// requests would otherwise succeed. -impl Request for Box { +/// Requests an owned pointer to a service. Not all providers can provide owned +/// pointers to their service, so this may fail where [`Svc`] requests would +/// otherwise succeed. +impl Request for Box { fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - let mut services: Services = injector.get_with(info)?; + let mut services: Services = injector.get_with(info)?; if services.len() > 1 { Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), + service_info: ServiceInfo::of::(), providers: services.len(), }) } else { - let service = services.get_all_owned().next().transpose()?.ok_or( + let service = services.iter_owned().next().transpose()?.ok_or( InjectError::MissingProvider { - service_info: ServiceInfo::of::(), + service_info: ServiceInfo::of::(), }, )?; @@ -120,20 +123,22 @@ impl Request for Box { } } -/// Lazily requests all the implementations of an interface. -impl Request for Services { +/// Lazily requests all the implementations of a service. +impl Request for Services { + #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - injector.get_service(info) + injector.get_all(info) } } /// Requests all the implementations of an interface. For sized types, this /// will return at most one implementation. If no provider is registered for /// the given interface, then this will return an empty [`Vec`]. -impl Request for Vec> { +impl Request for Vec> { + #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - let mut impls: Services = injector.get_with(info)?; - impls.get_all().collect() + let mut impls: Services<_> = injector.get_with(info)?; + impls.iter().collect() } } @@ -141,17 +146,19 @@ impl Request for Vec> { /// If no provider is registered for the given interface, then this will return /// an empty [`Vec`]. If any provider cannot provide an owned service /// pointer, then an error is returned instead. -impl Request for Vec> { +impl Request for Vec> { + #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - let mut impls: Services = injector.get_with(info)?; - impls.get_all_owned().collect() + let mut impls: Services<_> = injector.get_with(info)?; + impls.iter_owned().collect() } } /// Tries to request a service pointer for a service or interface. If no /// provider has been registered for it, then returns `None`. This fails if /// there are multiple implementations of the given interface. -impl Request for Option> { +impl Request for Option> { + #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { match injector.get_with(info) { Ok(response) => Ok(Some(response)), @@ -165,7 +172,7 @@ impl Request for Option> { /// provider has been registered for it, then returns `None`. This fails if /// there are multiple implementations of the given interface or if the service /// cannot be provided via an owned service pointer. -impl Request for Option> { +impl Request for Option> { fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { match injector.get_with(info) { Ok(response) => Ok(Some(response)), diff --git a/crates/runtime_injector/src/services.rs b/crates/runtime_injector/src/services.rs index 2011b52..d86b69d 100644 --- a/crates/runtime_injector/src/services.rs +++ b/crates/runtime_injector/src/services.rs @@ -1,19 +1,5 @@ -mod conditional; -mod constant; -mod fallible; -mod func; mod interface; -mod providers; mod service; -mod singleton; -mod transient; -pub use conditional::*; -pub use constant::*; -pub use fallible::*; -pub use func::*; pub use interface::*; -pub use providers::*; pub use service::*; -pub use singleton::*; -pub use transient::*; diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index 4fb2aaf..528b963 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -1,62 +1,24 @@ -use crate::{ - DynSvc, InjectError, InjectResult, OwnedDynSvc, Service, ServiceInfo, Svc, -}; +use crate::{Service, Svc}; -/// Indicates functionality that can be implemented. -/// -/// For example, each [`Sized`] [`Service`] type is an interface that -/// implements itself. This is done by requesting instances of itself from the -/// injector. However, the injector cannot provide instances of dynamic types -/// (`dyn Trait`) automatically because they are unsized. For this reason, any -/// interfaces using traits must be declared explicitly before use. This trait -/// should usually be implemented by the [`interface!`] macro in the same -/// module the trait was declared in. -/// -/// Since implementations of interfaces must be services, interfaces should be -/// declared with a supertrait of [`Service`]. This will ensure that the -/// implementors can be cast to [`dyn Any`](std::any::Any), and with the "arc" -/// feature enabled, those implementors are also [`Send`] + [`Sync`]. -/// Additionally, trait interfaces must be convertible to trait objects so they -/// can be used behind service pointers. You can read more about trait objects -/// [here](https://doc.rust-lang.org/book/ch17-02-trait-objects.html). -/// -/// See the documentation for the [`interface!`] macro for more information. -pub trait Interface: Service { - /// Downcasts a dynamic service pointer into a service pointer of this - /// interface type. - fn downcast(service: DynSvc) -> InjectResult>; - - /// Downcasts an owned dynamic service pointer into an owned service - /// pointer of this interface type. - fn downcast_owned(service: OwnedDynSvc) -> InjectResult>; -} - -impl Interface for T { - fn downcast(service: DynSvc) -> InjectResult> { - service - .downcast() - .map_err(|_| InjectError::InvalidProvider { - service_info: ServiceInfo::of::(), - }) - } - - fn downcast_owned(service: OwnedDynSvc) -> InjectResult> { - service - .downcast() - .map_err(|_| InjectError::InvalidProvider { - service_info: ServiceInfo::of::(), - }) - } -} +/// Implemented for trait objects. +pub trait Interface: Service {} /// Marker trait that indicates that a type is an interface for another type. /// -/// Each sized type is an interface for itself, and each `dyn Trait` is an -/// interface for the types that it can resolve. This trait should usually be -/// implemented by the [`interface!`] macro, and is strictly used to enforce -/// stronger type checking when assigning implementations for interfaces. -pub trait InterfaceFor: Interface {} -impl InterfaceFor for T {} +/// Each `dyn Trait` is an interface for the types that it can resolve. This +/// trait should usually be implemented by the [`interface!`] macro, and is +/// primarily used to enforce stronger type checking when assigning +/// implementations for interfaces. +pub trait InterfaceFor: Interface +where + S: Service, +{ + #[doc(hidden)] + fn from_svc(service: Svc) -> Svc; + + #[doc(hidden)] + fn from_owned_svc(service: Box) -> Box; +} /// Marks a trait as being an interface for many other types. This means that /// a request for the given trait can resolve to any of the types indicated by @@ -83,59 +45,46 @@ impl InterfaceFor for T {} /// impl Foo for MockBar {} /// /// // Requests for `dyn Foo` can resolve to either `Bar` or, in a test run, -/// // `MockBar`. Note that attributes are allowed on each of the listed types. -/// interface! { -/// dyn Foo = [ -/// Bar, -/// #[cfg(test)] -/// MockBar, -/// ], -/// }; +/// // `MockBar`. +/// interface!(Foo); /// ``` #[macro_export] macro_rules! interface { - { - $( - $interface:ty = [ - $($(#[$($attr:meta),*])* $impl:ty),* - $(,)? - ] - ),* - $(,)? - } => { - $( - impl $crate::Interface for $interface { - #[allow(unused_assignments)] - fn downcast(mut service: $crate::DynSvc) -> $crate::InjectResult<$crate::Svc> { - $( - $(#[$($attr),*])* - match service.downcast::<$impl>() { - Ok(downcasted) => return Ok(downcasted as $crate::Svc), - Err(input) => service = input, - } - )* + ($interface:tt) => { + impl $crate::Interface for dyn $interface {} + + impl $crate::InterfaceFor for dyn $interface { + fn from_svc(service: $crate::Svc) -> $crate::Svc { + service + } - Err($crate::InjectError::MissingProvider { service_info: $crate::ServiceInfo::of::() }) - } + fn from_owned_svc( + service: ::std::boxed::Box, + ) -> ::std::boxed::Box { + service + } + } + + impl $crate::FromProvider for dyn $interface { + type Interface = Self; - #[allow(unused_assignments)] - fn downcast_owned(mut service: $crate::OwnedDynSvc) -> $crate::InjectResult<::std::boxed::Box> { - $( - $(#[$($attr),*])* - match service.downcast::<$impl>() { - Ok(downcasted) => return Ok(downcasted as ::std::boxed::Box), - Err(input) => service = input, - } - )* + fn should_provide( + _provider: &dyn $crate::Provider, + ) -> bool { + true + } - Err($crate::InjectError::MissingProvider { service_info: $crate::ServiceInfo::of::() }) - } + fn from_provided( + provided: Svc, + ) -> InjectResult> { + Ok(provided) } - $( - $(#[$($attr),*])* - impl $crate::InterfaceFor<$impl> for $interface {} - )* - )* + fn from_provided_owned( + provided: Box, + ) -> InjectResult> { + Ok(provided) + } + } }; } diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index feb84b9..281254f 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -1,3 +1,5 @@ +use crate::interface; +use downcast_rs::{impl_downcast, DowncastSync}; use std::{ any::{Any, TypeId}, error::Error, @@ -29,37 +31,28 @@ feature_unique!( /// - **arc**: Pointer type is [`Arc`](std::sync::Arc) (default) }, { + #[cfg_attr( + not(doc), + doc = "", + doc = "The current pointer type is [`Rc`](std::rc::Rc)." + )] pub type Svc = std::rc::Rc; }, { + #[cfg_attr( + not(doc), + doc = "", + doc = "The current pointer type is [`Arc`](std::sync::Arc)." + )] pub type Svc = std::sync::Arc; } ); -feature_unique!( - { - /// A reference-counted service pointer holding an instance of `dyn - /// Any`. - }, - { - pub type DynSvc = Svc; - }, - { - pub type DynSvc = Svc; - } -); +/// A service pointer holding an instance of `dyn Service`. +pub type DynSvc = Svc; -feature_unique!( - { - /// An owned service pointer holding an instance of `dyn Any`. - }, - { - pub type OwnedDynSvc = Box; - }, - { - pub type OwnedDynSvc = Box; - } -); +/// An owned service pointer holding an instance of `dyn Service`. +pub type OwnedDynSvc = Box; feature_unique!( { @@ -67,15 +60,23 @@ feature_unique!( /// service. }, { - pub trait Service: Any {} - impl Service for T {} + pub trait Service: Downcast {} + impl Service for T {} }, { - pub trait Service: Any + Send + Sync {} - impl Service for T {} + pub trait Service: DowncastSync {} + impl Service for T {} } ); +interface!(Service); + +#[cfg(feature = "arc")] +impl_downcast!(sync Service); + +#[cfg(feature = "rc")] +impl_downcast!(Service); + /// A result from attempting to inject dependencies into a service and /// construct an instance of it. pub type InjectResult = Result; @@ -89,6 +90,7 @@ pub struct ServiceInfo { impl ServiceInfo { /// Creates a [`ServiceInfo`] for the given type. + #[inline] #[must_use] pub fn of() -> Self { ServiceInfo { @@ -98,12 +100,14 @@ impl ServiceInfo { } /// Gets the [`TypeId`] for this service. + #[inline] #[must_use] pub fn id(&self) -> TypeId { self.id } /// Gets the type name of this service. + #[inline] #[must_use] pub fn name(&self) -> &'static str { self.name @@ -262,7 +266,7 @@ impl Display for InjectError { write!(f, "an error occurred during activation of {}", service_info.name()) }, InjectError::InternalError(message) => { - write!(f, "an unexpected error occurred (please report this): {}", message) + write!(f, "an unexpected error occurred (please report this to https://github.com/TehPers/runtime_injector/issues): {message}") }, } } diff --git a/crates/runtime_injector/src/tests.rs b/crates/runtime_injector/src/tests.rs index 6a0c96a..9396f68 100644 --- a/crates/runtime_injector/src/tests.rs +++ b/crates/runtime_injector/src/tests.rs @@ -1,9 +1,8 @@ #![allow(clippy::blacklisted_name)] - use crate::{ constant, interface, InjectError, InjectResult, Injector, IntoSingleton, IntoTransient, RequestInfo, Service, ServiceInfo, Services, Svc, - TypedProvider, + WithInterface, }; use std::sync::Mutex; @@ -145,15 +144,7 @@ fn interfaces() { fn bar(&self) -> i32; } - interface!( - dyn Foo = [ - Svc1, - #[cfg(test)] - Svc2, - #[cfg(not(test))] - Svc3, - ] - ); + interface!(Foo); impl Foo for Svc1 { fn bar(&self) -> i32 { @@ -216,7 +207,7 @@ fn multi_injection() { impl Foo for Svc2 {} impl Foo for Svc3 {} - interface!(dyn Foo = [Svc1, Svc2, Svc3]); + interface!(Foo); let mut builder = Injector::builder(); builder.provide(Svc1::default.transient().with_interface::()); @@ -226,7 +217,7 @@ fn multi_injection() { assert_eq!(1, foos.len()); let foos: Vec> = - foos.get_all().collect::>().unwrap(); + foos.iter().collect::>().unwrap(); assert_eq!(1, foos.len()); } From bcf3aeacf0f77f30c25c0d4c99887546ba709fa6 Mon Sep 17 00:00:00 2001 From: TehPers Date: Wed, 13 Apr 2022 12:55:29 -0700 Subject: [PATCH 10/26] Update docs + test, get code working --- crates/runtime_injector/src/builder.rs | 94 +++++-- .../src/docs/getting_started.rs | 35 +-- crates/runtime_injector/src/injector.rs | 105 +++---- crates/runtime_injector/src/iter.rs | 4 +- .../src/iter/from_provider.rs | 10 + crates/runtime_injector/src/iter/providers.rs | 195 +++++++++++++ crates/runtime_injector/src/iter/services.rs | 155 +++++++++-- .../runtime_injector/src/iter/services_of.rs | 261 ------------------ crates/runtime_injector/src/lib.rs | 84 +++--- crates/runtime_injector/src/module.rs | 11 +- .../runtime_injector/src/provider_registry.rs | 247 +++++++++++++++-- .../src/providers/interface.rs | 13 +- .../src/providers/providers.rs | 73 ++--- crates/runtime_injector/src/requests.rs | 4 +- crates/runtime_injector/src/requests/arg.rs | 13 +- .../runtime_injector/src/requests/request.rs | 53 ++-- .../src/services/interface.rs | 41 ++- .../runtime_injector/src/services/service.rs | 6 +- crates/runtime_injector/src/tests.rs | 6 +- 19 files changed, 822 insertions(+), 588 deletions(-) create mode 100644 crates/runtime_injector/src/iter/providers.rs delete mode 100644 crates/runtime_injector/src/iter/services_of.rs diff --git a/crates/runtime_injector/src/builder.rs b/crates/runtime_injector/src/builder.rs index 4580714..153b6df 100644 --- a/crates/runtime_injector/src/builder.rs +++ b/crates/runtime_injector/src/builder.rs @@ -1,13 +1,18 @@ use crate::{ - provider_registry::{ProviderRegistry, ProviderRegistryType}, + provider_registry::{ + ProviderRegistry, ProviderRegistryType, ProviderSlot, Slot, + }, Injector, Interface, InterfaceRegistry, Module, Provider, RequestInfo, Service, ServiceInfo, }; use downcast_rs::impl_downcast; -use std::collections::{hash_map::Entry, HashMap}; +use std::{ + collections::{hash_map::Entry, HashMap}, + fmt::{Debug, Formatter}, +}; /// A builder for an [`Injector`]. -#[derive(Default)] +#[derive(Debug, Default)] pub struct InjectorBuilder { registry_builder: InterfaceRegistryBuilder, root_info: RequestInfo, @@ -54,7 +59,12 @@ impl InjectorBuilder { { self.registry_builder .providers_mut::() - .map(|providers| providers.remove_providers_for(service_info)) + .map(|providers| { + providers + .remove_providers_for(service_info) + .into_inner() + .unwrap() + }) .unwrap_or_default() } @@ -114,8 +124,7 @@ pub(crate) struct ProviderRegistryBuilder where I: ?Sized + Interface, { - providers: - HashMap>>>>, + providers: HashMap>, } impl ProviderRegistryBuilder @@ -130,21 +139,17 @@ where #[allow(clippy::missing_panics_doc)] self.providers .entry(service_info) - .or_insert_with(|| Some(Vec::new())) - .as_mut() - .unwrap() - .push(provider); + .or_default() + .with_inner_mut(|providers| providers.push(provider)) + .unwrap(); } pub fn remove_providers_for( &mut self, service_info: ServiceInfo, - ) -> Vec>> { + ) -> ProviderSlot { #[allow(clippy::missing_panics_doc)] - self.providers - .remove(&service_info) - .map(Option::unwrap) - .unwrap_or_default() + self.providers.remove(&service_info).unwrap_or_default() } } @@ -159,7 +164,18 @@ where } } -pub(crate) trait ProviderRegistryBuilderType: Service { +impl Debug for ProviderRegistryBuilder +where + I: ?Sized + Interface, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProviderRegistryBuilder") + .field("providers", &self.providers) + .finish() + } +} + +pub(crate) trait ProviderRegistryBuilderType: Service + Debug { fn merge( &mut self, other: Box, @@ -179,13 +195,14 @@ where let other: Box = other.downcast()?; for (service_info, other_providers) in other.providers { #[allow(clippy::missing_panics_doc)] - let mut other_providers = other_providers.unwrap(); + let mut other_providers = other_providers.into_inner().unwrap(); self.providers .entry(service_info) - .or_insert_with(|| Some(Vec::new())) - .as_mut() - .unwrap() - .append(&mut other_providers); + .or_default() + .with_inner_mut(|providers| { + providers.append(&mut other_providers) + }) + .unwrap(); } Ok(()) @@ -202,9 +219,15 @@ impl_downcast!(sync ProviderRegistryBuilderType); #[cfg(feature = "rc")] impl_downcast!(ProviderRegistryBuilderType); -#[derive(Default)] +impl Debug for Slot> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Slot").field(&self.inner()).finish() + } +} + +#[derive(Debug, Default)] pub(crate) struct InterfaceRegistryBuilder { - providers: HashMap>, + providers: HashMap>>, } impl InterfaceRegistryBuilder { @@ -217,6 +240,8 @@ impl InterfaceRegistryBuilder { #[allow(clippy::missing_panics_doc)] self.providers .get_mut(&ServiceInfo::of::()) + .map(Slot::inner_mut) + .map(Option::unwrap) .map(|providers| { providers .downcast_mut::>() @@ -231,9 +256,13 @@ impl InterfaceRegistryBuilder { #[allow(clippy::missing_panics_doc)] self.providers .entry(ServiceInfo::of::()) - .or_insert_with( - || Box::new(ProviderRegistryBuilder::::default()), - ) + .or_insert_with(|| { + (Box::new(ProviderRegistryBuilder::::default()) + as Box) + .into() + }) + .inner_mut() + .unwrap() .downcast_mut() .unwrap() } @@ -244,17 +273,21 @@ impl InterfaceRegistryBuilder { pub fn merge(&mut self, other: InterfaceRegistryBuilder) { for (service_info, other_providers) in other.providers { + #[allow(clippy::missing_panics_doc)] + let other_providers = other_providers.into_inner().unwrap(); match self.providers.entry(service_info) { Entry::Occupied(entry) => { #[allow(clippy::missing_panics_doc)] entry .into_mut() + .inner_mut() + .unwrap() .merge(other_providers) .map_err(|_| "error merging provider builders") .unwrap(); } Entry::Vacant(entry) => { - entry.insert(other_providers); + entry.insert(other_providers.into()); } } } @@ -265,7 +298,12 @@ impl InterfaceRegistryBuilder { .providers .into_iter() .map(|(service_info, mut providers)| { - (service_info, providers.build()) + ( + service_info, + providers + .with_inner_mut(|providers| providers.build()) + .into(), + ) }) .collect(); InterfaceRegistry::new(providers) diff --git a/crates/runtime_injector/src/docs/getting_started.rs b/crates/runtime_injector/src/docs/getting_started.rs index 84b8a7b..636fccb 100644 --- a/crates/runtime_injector/src/docs/getting_started.rs +++ b/crates/runtime_injector/src/docs/getting_started.rs @@ -1,7 +1,7 @@ //! # Getting started //! -//! Let's start with a simple application. Let's write an application for -//! greeting our users with a nice message. +//! Let's start with a simple application where we greet our users with a nice +//! message: //! //! ``` //! use std::io::stdin; @@ -243,33 +243,14 @@ //! //! // Since we'll be relying on dependency injection, we need to provide our //! // container a little more information by declaring our interfaces. First, -//! // we have three implementations of `OutputWriter` -//! interface! { -//! dyn OutputWriter = [ -//! ConsoleWriter, -//! HttpsWriter, -//! TcpWriter, -//! #[cfg(test)] -//! MockWriter -//! ] -//! } +//! // we will declare `OutputWriter` to be an interface: +//! interface!(OutputWriter); //! -//! // We also have two implementations of `OutputFormatter` -//! interface! { -//! dyn OutputFormatter = [ -//! UserFormatter, -//! DefaultFormatter -//! ] -//! } +//! // We also have the `OutputFormatter` interface: +//! interface!(OutputFormatter); //! -//! // Finally, we have two implementations of `InputReader` as well -//! interface! { -//! dyn InputReader = [ -//! ConsoleReader, -//! #[cfg(test)] -//! MockReader -//! ] -//! } +//! // Finally, we have the `InputReader` interface: +//! interface!(InputReader); //! //! // We'll make a function to help us configure everything based on the //! // configuration settings our user gave us diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index c17ae57..ba34516 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -1,9 +1,7 @@ use crate::{ - FromProvider, InjectError, InjectResult, InjectorBuilder, Interface, - InterfaceRegistry, Provider, Request, RequestInfo, ServiceInfo, Services, - Svc, + FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, Providers, + Request, RequestInfo, ServiceInfo, ServiceType, Services, Svc, }; -use std::ops::{Deref, DerefMut}; pub(crate) trait MapContainerEx { fn new(value: T) -> Self; @@ -112,7 +110,7 @@ pub(crate) use types::*; /// assert_eq!(1.0, value1); /// assert_eq!(2.0, value2); /// ``` -#[derive(Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Injector { interface_registry: MapContainer, root_request_info: Svc, @@ -203,11 +201,12 @@ impl Injector { /// /// ``` /// use runtime_injector::{ - /// interface, Injector, IntoSingleton, Svc, TypedProvider, Service + /// interface, Injector, IntoSingleton, Service, Svc, TypedProvider, + /// WithInterface, /// }; /// /// trait Foo: Service {} - /// interface!(dyn Foo = [Bar]); + /// interface!(Foo); /// /// #[derive(Default)] /// struct Bar; @@ -225,11 +224,12 @@ impl Injector { /// /// ``` /// use runtime_injector::{ - /// interface, Injector, IntoSingleton, Svc, TypedProvider, Service + /// interface, Injector, IntoSingleton, Service, Svc, TypedProvider, + /// WithInterface, /// }; /// /// trait Foo: Service {} - /// interface!(dyn Foo = [Bar, Baz]); + /// interface!(Foo); /// /// #[derive(Default)] /// struct Bar; @@ -296,72 +296,31 @@ impl Injector { &self, request_info: &RequestInfo, ) -> InjectResult> { - let service_info = ServiceInfo::of::(); - let providers = self.interface_registry.with_inner_mut(|registry| { - Ok(ProvidersLease { - parent_registry: self.interface_registry.clone(), - providers: registry.take_providers_for(service_info)?, - }) - })?; - - Ok(Services::new(self.clone(), request_info.clone(), providers)) - } -} - -pub(crate) struct ProvidersLease -where - I: ?Sized + Interface, -{ - parent_registry: MapContainer, - providers: Vec>>, -} - -impl Deref for ProvidersLease -where - I: ?Sized + Interface, -{ - type Target = [Box>]; - - fn deref(&self) -> &Self::Target { - self.providers.as_slice() - } -} - -impl DerefMut for ProvidersLease -where - I: ?Sized + Interface, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.providers.as_mut_slice() - } -} - -impl Drop for ProvidersLease -where - I: ?Sized + Interface, -{ - fn drop(&mut self) { - let providers = std::mem::take(&mut self.providers); - let result = self - .parent_registry - .lock() - .map_err(|_| { - InjectError::InternalError( - "failed to acquire lock for interface registry".into(), + let providers = match S::SERVICE_TYPE { + ServiceType::Service => { + let service_info = ServiceInfo::of::(); + let providers = + self.interface_registry.with_inner_mut(|registry| { + registry.take_providers_for(service_info) + })?; + Providers::services( + self.interface_registry.clone(), + providers, + service_info, ) - }) - .and_then(|mut registry| { - registry - .reclaim_providers_for(ServiceInfo::of::(), providers) - }); + } + ServiceType::Interface => self + .interface_registry + .with_inner_mut(|registry| registry.take()) + .map(|provider_registry| { + Providers::interface( + self.interface_registry.clone(), + provider_registry, + ) + })?, + }; - if let Err(error) = result { - eprintln!( - "An error occurred while releasing providiers for {}: {}", - ServiceInfo::of::().name(), - error - ); - } + Ok(Services::new(self.clone(), request_info.clone(), providers)) } } diff --git a/crates/runtime_injector/src/iter.rs b/crates/runtime_injector/src/iter.rs index fa295d2..bcdea72 100644 --- a/crates/runtime_injector/src/iter.rs +++ b/crates/runtime_injector/src/iter.rs @@ -1,7 +1,7 @@ mod from_provider; mod services; -// mod services_of; +mod providers; pub use from_provider::*; +pub use providers::*; pub use services::*; -// pub use services_of::*; diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs index c84b62e..2eadb03 100644 --- a/crates/runtime_injector/src/iter/from_provider.rs +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -2,9 +2,17 @@ use crate::{ InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, Svc, }; +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum ServiceType { + Service, + Interface, +} + pub trait FromProvider: Service { type Interface: ?Sized + Interface; + const SERVICE_TYPE: ServiceType; + fn should_provide( provider: &dyn Provider, ) -> bool; @@ -20,6 +28,8 @@ pub trait FromProvider: Service { impl FromProvider for S { type Interface = dyn Service; + const SERVICE_TYPE: ServiceType = ServiceType::Service; + #[inline] fn should_provide( provider: &dyn Provider, diff --git a/crates/runtime_injector/src/iter/providers.rs b/crates/runtime_injector/src/iter/providers.rs new file mode 100644 index 0000000..2eda7db --- /dev/null +++ b/crates/runtime_injector/src/iter/providers.rs @@ -0,0 +1,195 @@ +use crate::{ + provider_registry::{ + InterfaceRegistry, ProviderRegistry, ProviderRegistryIterMut, + }, + InjectError, InjectResult, Interface, MapContainer, Provider, ServiceInfo, +}; +use std::slice::IterMut; + +enum ProvidersSource +where + I: ?Sized + Interface, +{ + Services { + providers: Vec>>, + service_info: ServiceInfo, + }, + Interface { + provider_registry: ProviderRegistry, + }, +} + +pub struct Providers +where + I: ?Sized + Interface, +{ + parent_registry: MapContainer, + providers_source: ProvidersSource, +} + +impl Providers +where + I: ?Sized + Interface, +{ + #[inline] + pub(crate) fn services( + parent_registry: MapContainer, + providers: Vec>>, + service_info: ServiceInfo, + ) -> Self { + Providers { + parent_registry, + providers_source: ProvidersSource::Services { + providers, + service_info, + }, + } + } + + #[inline] + pub(crate) fn interface( + parent_registry: MapContainer, + provider_registry: ProviderRegistry, + ) -> Self { + Providers { + parent_registry, + providers_source: ProvidersSource::Interface { provider_registry }, + } + } + + #[inline] + pub fn iter(&mut self) -> ProviderIter<'_, I> { + match self.providers_source { + ProvidersSource::Services { + ref mut providers, + service_info, + } => ProviderIter::Services(ServiceProviderIter { + providers: providers.iter_mut(), + service_info, + }), + ProvidersSource::Interface { + ref mut provider_registry, + } => ProviderIter::Interface(InterfaceProviderIter { + inner: provider_registry.iter_mut(), + }), + } + } +} + +impl Drop for Providers +where + I: ?Sized + Interface, +{ + fn drop(&mut self) { + let result = self + .parent_registry + .lock() + .map_err(|_| { + InjectError::InternalError( + "failed to acquire lock for interface registry".into(), + ) + }) + .and_then(|mut registry| match self.providers_source { + ProvidersSource::Services { + ref mut providers, + service_info, + } => { + let providers = std::mem::take(providers); + registry.reclaim_providers_for(service_info, providers) + } + ProvidersSource::Interface { + ref mut provider_registry, + } => { + let provider_registry = std::mem::take(provider_registry); + registry.reclaim(provider_registry) + } + }); + + if let Err(error) = result { + eprintln!( + "An error occurred while releasing providiers for {}: {:?}", + ServiceInfo::of::().name(), + error + ); + } + } +} + +impl<'a, I> IntoIterator for &'a mut Providers +where + I: ?Sized + Interface, +{ + type Item = InjectResult<&'a mut dyn Provider>; + type IntoIter = ProviderIter<'a, I>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct ServiceProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + providers: IterMut<'a, Box>>, + service_info: ServiceInfo, +} + +impl<'a, I> Iterator for ServiceProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult<&'a mut dyn Provider>; + + fn next(&mut self) -> Option { + self.providers.find_map(|provider| { + // Skip providers that don't match the requested service + if provider.result() != self.service_info { + return None; + } + + // Return the provider + Some(Ok(provider.as_mut())) + }) + } +} + +pub struct InterfaceProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + inner: ProviderRegistryIterMut<'a, I>, +} + +impl<'a, I> Iterator for InterfaceProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult<&'a mut dyn Provider>; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +pub enum ProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + Services(ServiceProviderIter<'a, I>), + Interface(InterfaceProviderIter<'a, I>), +} + +impl<'a, I> Iterator for ProviderIter<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult<&'a mut dyn Provider>; + + fn next(&mut self) -> Option { + match self { + ProviderIter::Services(inner) => inner.next(), + ProviderIter::Interface(inner) => inner.next(), + } + } +} diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs index 7c0cb9f..929cc5e 100644 --- a/crates/runtime_injector/src/iter/services.rs +++ b/crates/runtime_injector/src/iter/services.rs @@ -1,16 +1,59 @@ use crate::{ - FromProvider, InjectError, InjectResult, Injector, Provider, - ProvidersLease, RequestInfo, ServiceInfo, Svc, + FromProvider, InjectError, InjectResult, Injector, ProviderIter, Providers, + RequestInfo, ServiceInfo, Svc, }; -use std::{marker::PhantomData, slice::IterMut}; +use std::marker::PhantomData; +/// A collection of all the providers for a particular service or interface. +/// Each service is activated only during iteration of this collection. +/// +/// If a type only has one implementation registered for it, then it may be +/// easier to request [`Svc`] from the container instead. However, if +/// multiple implementations are registered (or no implementations are +/// registered), then this will allow all of those implementations to be +/// iterated over. +/// +/// ``` +/// use runtime_injector::{ +/// interface, Injector, IntoTransient, Service, Services, Svc, +/// TypedProvider, WithInterface, +/// }; +/// +/// trait Fooable: Service { +/// fn baz(&self) {} +/// } +/// +/// interface!(Fooable); +/// +/// #[derive(Default)] +/// struct Foo; +/// impl Fooable for Foo {} +/// +/// #[derive(Default)] +/// struct Bar; +/// impl Fooable for Bar {} +/// +/// let mut builder = Injector::builder(); +/// builder.provide(Foo::default.transient().with_interface::()); +/// builder.provide(Bar::default.transient().with_interface::()); +/// +/// let injector = builder.build(); +/// let mut counter = 0; +/// let mut fooables: Services = injector.get().unwrap(); +/// for foo in fooables.iter() { +/// counter += 1; +/// foo.unwrap().baz(); +/// } +/// +/// assert_eq!(2, counter); +/// ``` pub struct Services where S: ?Sized + FromProvider, { injector: Injector, request_info: RequestInfo, - providers: ProvidersLease, + providers: Providers, _marker: PhantomData S>, } @@ -22,7 +65,7 @@ where pub(crate) fn new( injector: Injector, request_info: RequestInfo, - providers: ProvidersLease, + providers: Providers, ) -> Self { Services { injector, @@ -39,7 +82,7 @@ where ServicesIter { injector: &self.injector, request_info: &self.request_info, - provider_iter: self.providers.iter_mut(), + provider_iter: self.providers.iter(), _marker: PhantomData, } } @@ -51,35 +94,49 @@ where OwnedServicesIter { injector: &self.injector, request_info: &self.request_info, - provider_iter: self.providers.iter_mut(), + provider_iter: self.providers.iter(), _marker: PhantomData, } } - - /// Gets the number of provided services of the given type registered to - /// this interface. This does not take into account conditional providers - /// which may not return an implementation of the service. - #[inline] - pub fn len(&self) -> usize { - self.providers.len() - } - - /// Returns whether there are no providers for the given service and - /// interface. Conditional providers still may not return an implementation - /// of the service even if this returns `true`. - #[inline] - pub fn is_empty(&self) -> bool { - self.providers.is_empty() - } } +/// An iterator over the provided services of the given type. Each service is +/// activated on demand. +/// +/// ``` +/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; +/// use std::sync::Mutex; +/// +/// struct Foo; +/// +/// fn make_foo(counter: Svc>) -> Foo { +/// // Increment the counter to track how many Foos have been created +/// let mut counter = counter.lock().unwrap(); +/// *counter += 1; +/// Foo +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide(constant(Mutex::new(0usize))); +/// builder.provide(make_foo.transient()); +/// +/// let injector = builder.build(); +/// let counter: Svc> = injector.get().unwrap(); +/// let mut foos: Services = injector.get().unwrap(); +/// +/// let mut iter = foos.iter(); +/// assert_eq!(0, *counter.lock().unwrap()); +/// assert!(iter.next().is_some()); +/// assert_eq!(1, *counter.lock().unwrap()); +/// assert!(iter.next().is_none()); +/// ``` pub struct ServicesIter<'a, S> where S: ?Sized + FromProvider, { injector: &'a Injector, request_info: &'a RequestInfo, - provider_iter: IterMut<'a, Box>>, + provider_iter: ProviderIter<'a, S::Interface>, _marker: PhantomData S>, } @@ -91,8 +148,13 @@ where fn next(&mut self) -> Option { self.provider_iter.find_map(|provider| { + let provider = match provider { + Ok(provider) => provider, + Err(error) => return Some(Err(error)), + }; + // Skip providers that don't match the requested service - if !S::should_provide(provider.as_ref()) { + if !S::should_provide(provider) { return None; } @@ -124,13 +186,47 @@ where } } +/// An iterator over the provided services of the given type. Each service is +/// activated on demand. +/// +/// Not all providers can provide owned pointers to their service. Only owned +/// services are returned, the rest are ignored. +/// +/// ``` +/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; +/// use std::sync::Mutex; +/// +/// #[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// struct Foo(usize); +/// +/// fn make_foo(counter: Svc>) -> Foo { +/// // Increment the counter to track how many Foos have been created +/// let mut counter = counter.lock().unwrap(); +/// *counter += 1; +/// Foo(*counter) +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide(constant(Mutex::new(0usize))); +/// builder.provide(make_foo.transient()); +/// +/// let injector = builder.build(); +/// let counter: Svc> = injector.get().unwrap(); +/// let mut foos: Services = injector.get().unwrap(); +/// +/// let mut iter = foos.iter_owned(); +/// assert_eq!(0, *counter.lock().unwrap()); +/// assert_eq!(Foo(1), *iter.next().unwrap().unwrap()); +/// assert_eq!(1, *counter.lock().unwrap()); +/// assert!(iter.next().is_none()); +/// ``` pub struct OwnedServicesIter<'a, S> where S: ?Sized + FromProvider, { injector: &'a Injector, request_info: &'a RequestInfo, - provider_iter: IterMut<'a, Box>>, + provider_iter: ProviderIter<'a, S::Interface>, _marker: PhantomData S>, } @@ -142,8 +238,13 @@ where fn next(&mut self) -> Option { self.provider_iter.find_map(|provider| { + let provider = match provider { + Ok(provider) => provider, + Err(error) => return Some(Err(error)), + }; + // Skip providers that don't match the requested service - if !S::should_provide(provider.as_ref()) { + if !S::should_provide(provider) { return None; } diff --git a/crates/runtime_injector/src/iter/services_of.rs b/crates/runtime_injector/src/iter/services_of.rs deleted file mode 100644 index 9d7480a..0000000 --- a/crates/runtime_injector/src/iter/services_of.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::{ - InjectError, InjectResult, Injector, Interface, Provider, ProvidersLease, - RequestInfo, Service, ServiceInfo, Services, Svc, -}; -use std::slice::IterMut; - -/// A collection of all the providers for a particular interface. Each service -/// is activated only during iteration of this collection. -/// -/// If an interface will only have one implementation registered for it, then -/// it may be easier to request [`Svc`] from the container instead. However, -/// if multiple implementations are registered (or no implementations are -/// registered), then this will allow all of those implementations to be -/// iterated over. -/// -/// ``` -/// use runtime_injector::{ -/// interface, Injector, IntoTransient, Services, Svc, TypedProvider, Service -/// }; -/// -/// trait Fooable: Service { -/// fn baz(&self) {} -/// } -/// -/// interface!(Fooable); -/// -/// #[derive(Default)] -/// struct Foo; -/// impl Fooable for Foo {} -/// -/// #[derive(Default)] -/// struct Bar; -/// impl Fooable for Bar {} -/// -/// let mut builder = Injector::builder(); -/// builder.provide(Foo::default.transient().with_interface::()); -/// builder.provide(Bar::default.transient().with_interface::()); -/// -/// let injector = builder.build(); -/// let mut counter = 0; -/// let mut fooables: Services = injector.get().unwrap(); -/// for foo in fooables.get_all() { -/// counter += 1; -/// foo.unwrap().baz(); -/// } -/// -/// assert_eq!(2, counter); -/// ``` -pub struct ServicesOf -where - I: ?Sized + Interface, -{ - injector: Injector, - request_info: RequestInfo, - providers: ProvidersLease, -} - -impl ServicesOf -where - I: ?Sized + Interface, -{ - #[inline] - pub(crate) fn new( - injector: Injector, - request_info: RequestInfo, - providers: ProvidersLease, - ) -> Self { - ServicesOf { - injector, - request_info, - providers, - } - } - - /// Lazily gets all the implementations of this interface. Each service - /// will be requested on demand rather than all at once. - #[inline] - pub fn iter(&mut self) -> ServicesOfIter<'_, I> { - ServicesOfIter { - injector: &self.injector, - request_info: &self.request_info, - provider_iter: self.providers.iter_mut(), - } - } - - /// Lazily gets all the implementations of this interface as owned service - /// pointers. Each service will be requested on demand rather than all at - /// once. Not all providers can provide owned service pointers, so some - /// requests may fail. - #[inline] - pub fn iter_owned(&mut self) -> OwnedServicesOfIter<'_, I> { - OwnedServicesOfIter { - injector: &self.injector, - request_info: &self.request_info, - provider_iter: self.providers.iter_mut(), - } - } - - /// Gets the max number of possible implementations of this interface. This - /// does not take into account conditional providers, which may not return - /// an implementation of the service. - #[inline] - pub fn len(&self) -> usize { - self.providers.len() - } - - /// Returns `true` if there are no possible implementations of this - /// interface. This does not take into account conditional providers, which - /// may not return an implementation of the service. - #[inline] - pub fn is_empty(&self) -> bool { - self.providers.is_empty() - } -} - -impl ServicesOf { - pub fn of_service(self) -> Services - where - S: Service, - { - Services::new(self.injector, self.request_info, self.providers) - } -} - -/// An iterator over all the implementations of an interface. Each service is -/// activated on demand. -/// -/// ``` -/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; -/// use std::sync::Mutex; -/// -/// struct Foo; -/// -/// fn make_foo(counter: Svc>) -> Foo { -/// // Increment the counter to track how many Foos have been created -/// let mut counter = counter.lock().unwrap(); -/// *counter += 1; -/// Foo -/// } -/// -/// let mut builder = Injector::builder(); -/// builder.provide(constant(Mutex::new(0usize))); -/// builder.provide(make_foo.transient()); -/// -/// let injector = builder.build(); -/// let counter: Svc> = injector.get().unwrap(); -/// let mut foos: Services = injector.get().unwrap(); -/// -/// let mut iter = foos.get_all(); -/// assert_eq!(0, *counter.lock().unwrap()); -/// assert!(iter.next().is_some()); -/// assert_eq!(1, *counter.lock().unwrap()); -/// assert!(iter.next().is_none()); -/// ``` -pub struct ServicesOfIter<'a, I> -where - I: ?Sized + Interface, -{ - injector: &'a Injector, - request_info: &'a RequestInfo, - provider_iter: IterMut<'a, Box>>, -} - -impl<'a, I> Iterator for ServicesOfIter<'a, I> -where - I: ?Sized + Interface, -{ - type Item = InjectResult>; - - fn next(&mut self) -> Option { - self.provider_iter.find_map(|provider| { - match provider.provide(self.injector, self.request_info) { - Ok(service) => Some(Ok(service)), - Err(InjectError::ConditionsNotMet { .. }) => None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })) - } - Err(error) => Some(Err(error)), - } - }) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.provider_iter.size_hint() - } -} - -/// An iterator over all the implementations of an interface. Each service is -/// activated on demand. -/// -/// ``` -/// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; -/// use std::sync::Mutex; -/// -/// #[derive(Clone, Copy, PartialEq, Eq, Debug)] -/// struct Foo(usize); -/// -/// fn make_foo(counter: Svc>) -> Foo { -/// // Increment the counter to track how many Foos have been created -/// let mut counter = counter.lock().unwrap(); -/// *counter += 1; -/// Foo(*counter) -/// } -/// -/// let mut builder = Injector::builder(); -/// builder.provide(constant(Mutex::new(0usize))); -/// builder.provide(make_foo.transient()); -/// -/// let injector = builder.build(); -/// let counter: Svc> = injector.get().unwrap(); -/// let mut foos: Services = injector.get().unwrap(); -/// -/// let mut iter = foos.get_all_owned(); -/// assert_eq!(0, *counter.lock().unwrap()); -/// assert_eq!(Foo(1), *iter.next().unwrap().unwrap()); -/// assert_eq!(1, *counter.lock().unwrap()); -/// assert!(iter.next().is_none()); -/// ``` -pub struct OwnedServicesOfIter<'a, I> -where - I: ?Sized + Interface, -{ - injector: &'a Injector, - request_info: &'a RequestInfo, - provider_iter: IterMut<'a, Box>>, -} - -impl<'a, I> Iterator for OwnedServicesOfIter<'a, I> -where - I: ?Sized + Interface, -{ - type Item = InjectResult>; - - fn next(&mut self) -> Option { - self.provider_iter.find_map(|provider| { - match provider.provide_owned(self.injector, self.request_info) { - Ok(service) => Some(Ok(service)), - Err(InjectError::ConditionsNotMet { .. }) => None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })) - } - Err(error) => Some(Err(error)), - } - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.provider_iter.size_hint() - } -} diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 6bdf603..a8b30f6 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -1,29 +1,28 @@ //! # Runtime dependency injection. -//! +//! //! By default, services provided by the [`Injector`] use thread-safe pointers. //! This is because [`Arc`](std::sync::Arc) is used to hold instances of the //! services. This can be changed to [`Rc`](std::rc::Rc) by disabling //! default features and enabling the "rc" feature: -//! +//! //! ```text //! [dependencies.runtime_injector] //! version = "*" # Replace with the version you want to use //! default-features = false //! features = ["rc"] //! ``` -//! +//! //! ## Getting started -//! +//! //! If you are unfamiliar with dependency injection, then you may want to check -//! about how a container can help -//! [simplify your application][ioc]. Otherwise, +//! about how a container can help [simplify your application][ioc]. Otherwise, //! check out the [getting started guide][getting-started] -//! +//! //! [ioc]: crate::docs::inversion_of_control //! [getting-started]: crate::docs::getting_started -//! +//! //! ## Dependency injection at runtime (rather than compile-time) -//! +//! //! Runtime dependency injection allows for advanced configuration of services //! during runtime rather than needing to decide what services your application //! will use at compile time. This means you can read a config when your @@ -35,16 +34,15 @@ //! I/O-based applications like a web server, the additional overhead is //! probably insignificant compared to the additional flexibility you get with //! runtime_injector. -//! +//! //! ## Interfaces -//! +//! //! Using interfaces allows you to write your services without worrying about //! how its dependencies are implemented. You can think of them like generic //! type parameters for your service, except rather than needing to add a new //! type parameter, you use a service pointer to the interface for your //! dependency. This makes your code easier to read and faster to write, and //! keeps your services decoupled from their dependencies and dependents. -//! //! Interfaces are implemented as trait objects in runtime_injector. For //! instance, you may define a trait `UserDatabase` and implement it for //! several different types. [`Svc`](crate::Svc) is a @@ -52,13 +50,13 @@ //! Similarly, `dyn UserDatabase` is your interface. You can read more about //! how interfaces work and how they're created in the //! [type-level docs](crate::Interface). -//! +//! //! ## Service lifetimes -//! +//! //! Lifetimes of services created by the [`Injector`] are controlled by the //! [`Provider`] used to construct those lifetimes. Currently, there are three //! built-in service provider types: -//! +//! //! - **[Transient](crate::TransientProvider):** A service is created each time //! it is requested. This will never return the same instance of a service //! more than once. @@ -69,40 +67,38 @@ //! created using a service factory and instead can have their instance //! provided to the container directly. This behaves similar to singleton in //! that the same instance is provided each time the service is requested. -//! +//! //! Custom service providers can also be created by implementing either the //! [`TypedProvider`] or [`Provider`] trait. -//! +//! //! ## Fallible service factories -//! +//! //! Not all types can always be successfully created. Sometimes, creating an //! instance of a service might fail. Rather than panicking on error, it's //! possible to instead return a [`Result`] from your constructors and //! inject the result as a [`Svc`]. Read more in the //! [docs for `IntoFallible`](crate::IntoFallible). -//! +//! //! ## Owned service pointers -//! +//! //! In general, providers need to be able to provide their services via //! reference-counted service pointers, or [`Svc`]. The issue with this is //! that you cannot get mutable or owned access to the contents of those //! pointers since they are shared pointers. As a result, you may need to clone //! some dependencies in your constructors if you want to be able to own them. -//! //! If your dependency is a transient service, then it might make more sense //! to inject it as a [`Box`] than clone it from a reference-counted service //! pointer. In these cases, you can request a [`Box`] directly from the //! injector and avoid needing to clone your dependency entirely! -//! +//! //! ## Custom target-specific arguments -//! +//! //! Sometimes it's useful to be able to pass a specific value into your //! services. For example, if you're writing a database service and you need a //! connection string, you could define a new `ConnectionString` struct as a //! newtype for [`String`], but that would be a bit excessive for passing in a //! single value. If you had several arguments you needed to pass in this way, //! then that would mean you would need a new type for each one. -//! //! Rather than creating a bunch of newtypes, you can use [`Arg`] to pass in //! pre-defined values directly to your services. For example, you can use //! `Arg` to pass in your connection string, plus you can use @@ -110,19 +106,19 @@ //! `Arg` in your logging service to set your logging format without //! needing to worry about accidentally using your connection string as your //! logging format! -//! +//! //! ## Example -//! -//! ``` +//! +//! ```rust //! use runtime_injector::{ //! define_module, Module, interface, Injector, Svc, IntoSingleton, -//! TypedProvider, IntoTransient, constant, Service +//! TypedProvider, IntoTransient, constant, Service, WithInterface, //! }; //! use std::error::Error; -//! +//! //! // Some type that represents a user //! struct User; -//! +//! //! // This is our interface. In practice, multiple structs can implement this //! // trait, and we don't care what the concrete type is most of the time in //! // our other services as long as it implements this trait. Because of this, @@ -138,26 +134,24 @@ //! trait DataService: Service { //! fn get_user(&self, user_id: &str) -> Option; //! } -//! +//! //! // We can use a data service which connects to a SQL database. //! #[derive(Default)] //! struct SqlDataService; //! impl DataService for SqlDataService { //! fn get_user(&self, _user_id: &str) -> Option { todo!() } //! } -//! +//! //! // ... Or we can mock out the data service entirely! //! #[derive(Default)] //! struct MockDataService; //! impl DataService for MockDataService { //! fn get_user(&self, _user_id: &str) -> Option { Some(User) } //! } -//! -//! // Specify which types implement the DataService interface. This does not -//! // determine the actual implementation used. It only registers the types as -//! // possible implementations of the DataService interface. -//! interface!(dyn DataService = [SqlDataService, MockDataService]); -//! +//! +//! // Declare `DataService` as an interface +//! interface!(DataService); +//! //! // Here's another service our application uses. This service depends on our //! // data service, however it doesn't care how that service is actually //! // implemented as long as it works. Because of that, we're using dynamic @@ -165,20 +159,19 @@ //! struct UserService { //! data_service: Svc, //! } -//! +//! //! impl UserService { //! // This is just a normal constructor. The only requirement is that each //! // parameter is a valid injectable dependency. //! pub fn new(data_service: Svc) -> Self { //! UserService { data_service } //! } -//! //! pub fn get_user(&self, user_id: &str) -> Option { //! // UserService doesn't care how the user is actually retrieved //! self.data_service.get_user(user_id) //! } //! } -//! +//! //! fn main() -> Result<(), Box> { //! // This is where we register our services. Each call to `.provide` adds //! // a new service provider to our container, however nothing is actually @@ -186,10 +179,9 @@ //! // types we aren't actually going to use without worrying about //! // constructing instances of those types that we aren't actually using. //! let mut builder = Injector::builder(); -//! +//! //! // We can manually add providers to our builder //! builder.provide(UserService::new.singleton()); -//! //! struct Foo(Svc); //! //! // Alternatively, modules can be used to group providers and @@ -204,11 +196,9 @@ //! // Note that we can register closures as providers as well //! (|_: Svc| "Hello, world!").singleton(), //! (|_: Option>| 120.9).transient(), -//! //! // Since we know our dependency is transient, we can request an //! // owned pointer to it rather than a reference-counted pointer //! (|value: Box| format!("{}", value)).transient(), -//! //! // We can also provide constant values directly to our services //! constant(8usize), //! ], @@ -217,10 +207,10 @@ //! dyn DataService = [MockDataService::default.singleton()], //! }, //! }; -//! +//! //! // You can easily add a module to your builder //! builder.add_module(module); -//! +//! //! // Now that we've registered all our providers and implementations, we //! // can start relying on our container to create our services for us! //! let injector = builder.build(); diff --git a/crates/runtime_injector/src/module.rs b/crates/runtime_injector/src/module.rs index 94771cb..d24214f 100644 --- a/crates/runtime_injector/src/module.rs +++ b/crates/runtime_injector/src/module.rs @@ -67,14 +67,7 @@ impl Module { /// trait Fooable: Service {} /// impl Fooable for Foo {} /// impl Fooable for Bar {} -/// interface! { -/// dyn Fooable = [ -/// Foo, -/// Bar, -/// #[cfg(test)] -/// Quux, -/// ] -/// }; +/// interface!(Fooable); /// /// let module = define_module! { /// services = [ @@ -151,7 +144,7 @@ macro_rules! define_module { } ) => { $( - $($module.provide($crate::TypedProvider::with_interface::<$interface>($implementation));)* + $($module.provide($crate::WithInterface::with_interface::<$interface>($implementation));)* )* }; ( diff --git a/crates/runtime_injector/src/provider_registry.rs b/crates/runtime_injector/src/provider_registry.rs index 66767fa..455401f 100644 --- a/crates/runtime_injector/src/provider_registry.rs +++ b/crates/runtime_injector/src/provider_registry.rs @@ -1,28 +1,100 @@ use crate::{ InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, }; -use std::collections::HashMap; +use std::{ + collections::{hash_map::ValuesMut, HashMap}, + fmt::{Debug, Formatter}, + slice::IterMut, +}; + +pub(crate) struct Slot(Option); + +impl Slot { + pub fn take(&mut self) -> Option { + self.0.take() + } + + pub fn replace(&mut self, value: T) -> Option { + self.0.replace(value) + } + + pub fn inner(&self) -> Option<&T> { + self.0.as_ref() + } + + pub fn inner_mut(&mut self) -> Option<&mut T> { + self.0.as_mut() + } + + pub fn with_inner_mut(&mut self, f: F) -> Option + where + F: FnOnce(&mut T) -> R, + { + self.0.as_mut().map(f) + } + + pub fn into_inner(self) -> Option { + self.0 + } +} + +impl Default for Slot +where + T: Default, +{ + fn default() -> Self { + Self(Some(Default::default())) + } +} + +impl From for Slot { + fn from(value: T) -> Self { + Self(Some(value)) + } +} + +impl From> for Slot { + fn from(value: Option) -> Self { + Self(value) + } +} + +pub(crate) type ProviderSlot = Slot>>>; + +impl Debug for ProviderSlot +where + I: ?Sized + Interface, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.inner() { + Some(providers) => f + .debug_tuple("Slot") + .field(&format_args!("<{} provider(s)>", providers.len())) + .finish(), + None => f.debug_tuple("Slot").field(&"").finish(), + } + } +} + +impl Debug for Slot> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Slot").field(&self.0).finish() + } +} /// Stores providers for a particular interface -#[derive(Default)] pub(crate) struct ProviderRegistry where I: ?Sized + Interface, { - providers: - HashMap>>>>, + providers: HashMap>, } impl ProviderRegistry where I: ?Sized + Interface, { - pub fn new( - providers: HashMap< - ServiceInfo, - Option>>>, - >, - ) -> Self { + pub fn new(providers: HashMap>) -> Self { ProviderRegistry { providers } } @@ -68,10 +140,77 @@ where Ok(()) } } + + pub fn iter_mut(&mut self) -> ProviderRegistryIterMut<'_, I> { + ProviderRegistryIterMut { + values: self.providers.values_mut(), + cur_slot: None, + } + } +} + +impl Debug for ProviderRegistry +where + I: ?Sized + Interface, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProviderRegistry") + .field("providers", &self.providers) + .finish() + } +} + +impl Default for ProviderRegistry +where + I: ?Sized + Interface, +{ + fn default() -> Self { + Self { + providers: Default::default(), + } + } +} + +pub(crate) struct ProviderRegistryIterMut<'a, I> +where + I: ?Sized + Interface, +{ + values: ValuesMut<'a, ServiceInfo, ProviderSlot>, + cur_slot: Option>>>, +} + +impl<'a, I> Iterator for ProviderRegistryIterMut<'a, I> +where + I: ?Sized + Interface, +{ + type Item = InjectResult<&'a mut dyn Provider>; + + fn next(&mut self) -> Option { + loop { + // Try to get next item in current slot + if let Some(next) = self.cur_slot.as_mut().and_then(Iterator::next) + { + return Some(Ok(next.as_mut())); + } + + // Try to go to next slot + let slot = self.values.next()?; + let providers = match slot.inner_mut() { + Some(providers) => providers, + None => { + return Some(Err(InjectError::InternalError(format!( + "providers for {:?} are in use", + ServiceInfo::of::().name() + )))) + } + }; + self.cur_slot = Some(providers.iter_mut()); + } + } } /// Marker trait for provider registries. -pub(crate) trait ProviderRegistryType: Service {} +pub(crate) trait ProviderRegistryType: Service + Debug {} impl ProviderRegistryType for ProviderRegistry where I: ?Sized + Interface {} #[cfg(feature = "arc")] @@ -80,16 +219,17 @@ downcast_rs::impl_downcast!(sync ProviderRegistryType); #[cfg(feature = "rc")] downcast_rs::impl_downcast!(ProviderRegistryType); -#[derive(Default)] +#[derive(Debug, Default)] pub(crate) struct InterfaceRegistry { - provider_registries: HashMap>, + provider_registries: + HashMap>>, } impl InterfaceRegistry { pub fn new( provider_registries: HashMap< ServiceInfo, - Box, + Slot>, >, ) -> Self { InterfaceRegistry { @@ -97,6 +237,63 @@ impl InterfaceRegistry { } } + pub fn take(&mut self) -> InjectResult> + where + I: ?Sized + Interface, + { + let interface_info = ServiceInfo::of::(); + self.provider_registries + .get_mut(&interface_info) + .ok_or_else(|| InjectError::MissingProvider { + service_info: interface_info, + })? + .take() + .ok_or_else(|| InjectError::CycleDetected { + service_info: interface_info, + cycle: vec![interface_info], + })? + .downcast() + .map_err(|_| { + InjectError::InternalError(format!( + "the provider registry for {:?} is an invalid type", + interface_info.name() + )) + }) + .map(|registry| *registry) + } + + pub fn reclaim( + &mut self, + provider_registry: ProviderRegistry, + ) -> InjectResult<()> + where + I: ?Sized + Interface, + { + // Get the provider registry's slot + let interface_info = ServiceInfo::of::(); + let slot = self + .provider_registries + .get_mut(&interface_info) + .ok_or_else(|| { + InjectError::InternalError(format!( + "activated providers for {} are no longer registered", + interface_info.name() + )) + })?; + + // Put the provider registry into the slot + let replaced = slot.replace(Box::new(provider_registry)); + if let Some(replaced) = replaced { + slot.replace(replaced); + return Err(InjectError::InternalError(format!( + "slot for the provider registry for {:?} has already been reclaimed", + interface_info.name() + ))); + } + + Ok(()) + } + pub fn take_providers_for( &mut self, service_info: ServiceInfo, @@ -113,9 +310,17 @@ impl InterfaceRegistry { // Downcast provider list let provider_registry: &mut ProviderRegistry = provider_registry + .inner_mut() + .ok_or_else(|| InjectError::CycleDetected { + service_info, + cycle: vec![service_info], + })? .downcast_mut() - .ok_or_else(|| InjectError::InvalidProvider { - service_info: { interface_info }, + .ok_or_else(|| { + InjectError::InternalError(format!( + "provider registry for interface {:?} is the wrong type", + interface_info.name() + )) })?; // Get providers @@ -132,7 +337,7 @@ impl InterfaceRegistry { { // Get the provider registry let interface_info = ServiceInfo::of::(); - let provider_registry = self + let slot = self .provider_registries .get_mut(&interface_info) .ok_or_else(|| { @@ -141,12 +346,18 @@ impl InterfaceRegistry { interface_info.name() )) })?; + let provider_registry = slot.inner_mut().ok_or_else(|| { + InjectError::InternalError(format!( + "activated provider for {} is in use", + interface_info.name() + )) + })?; // Downcast the provider registry let provider_registry: &mut ProviderRegistry<_> = provider_registry.downcast_mut().ok_or_else(|| { InjectError::InternalError(format!( - "provider for {} is the wrong type", + "provider registry for interface {:?} is the wrong type", interface_info.name() )) })?; diff --git a/crates/runtime_injector/src/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs index fc63b7b..3d33692 100644 --- a/crates/runtime_injector/src/providers/interface.rs +++ b/crates/runtime_injector/src/providers/interface.rs @@ -43,18 +43,21 @@ where /// automatically implemented for all types that implement [`TypedProvider`]. pub trait WithInterface: TypedProvider { /// Provides this service as an implementation of a particular interface. - /// Rather than requesting this service with its concrete type, it can - /// instead be requested by its interface type. + /// Rather than requesting this service with its concrete type, it is + /// instead requested by its interface type. By default, all services are + /// assigned to the [`dyn Service`] interface. Any services assigned to the + /// [`dyn Service`] interface can be requested directly by their concrete + /// type. Other services cannot be requested by their concrete types once + /// they has been assigned another interface. /// - /// *Note: it cannot be requested with its concrete type once it has been - /// assigned an interface.* + /// [`dyn Service`]: crate::Service /// /// ## Example /// /// ``` /// use runtime_injector::{ /// interface, InjectResult, Injector, IntoSingleton, Service, Svc, - /// TypedProvider, + /// TypedProvider, WithInterface, /// }; /// /// trait Fooable: Service { diff --git a/crates/runtime_injector/src/providers/providers.rs b/crates/runtime_injector/src/providers/providers.rs index 87ac627..db1400b 100644 --- a/crates/runtime_injector/src/providers/providers.rs +++ b/crates/runtime_injector/src/providers/providers.rs @@ -35,55 +35,29 @@ pub trait Provider: Service { } } -impl Provider for T -where - T: TypedProvider, -{ - type Interface = ::Interface; - - fn result(&self) -> ServiceInfo { - ServiceInfo::of::() - } - - fn provide( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult> { - let service = self.provide_typed(injector, request_info)?; - Ok(Self::Interface::from_svc(service)) - } - - fn provide_owned( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult> { - let service = self.provide_owned_typed(injector, request_info)?; - Ok(Self::Interface::from_owned_svc(service)) - } -} - /// A strongly-typed service provider. /// -/// Types which implement this trait can provide strongly-typed instances of a -/// particular service type. Examples of typed providers include providers -/// created from service factories or constant providers. This should be -/// preferred over [`Provider`] for custom service providers if possible due to -/// the strong type guarantees this provides. [`Provider`] is automatically +/// Types which implement this trait can provide instances of a particular +/// service type. Examples of typed providers include providers created from +/// service factories or constant providers. This should be preferred over +/// [`Provider`] for custom service providers if possible due to the strong +/// type guarantees this trait provides. [`Provider`] is automatically /// implemented for all types which implement [`TypedProvider`]. /// /// ## Example /// /// ``` /// use runtime_injector::{ -/// InjectResult, Injector, RequestInfo, Svc, TypedProvider, +/// InjectResult, Injector, RequestInfo, Service, Svc, TypedProvider, /// }; /// /// struct Foo; /// /// struct FooProvider; /// impl TypedProvider for FooProvider { +/// // The interface must be `dyn Service` for the service to be requested +/// // directly by its concrete type. +/// type Interface = dyn Service; /// type Result = Foo; /// /// fn provide_typed( @@ -130,3 +104,32 @@ pub trait TypedProvider: }) } } + +impl Provider for T +where + T: TypedProvider, +{ + type Interface = ::Interface; + + fn result(&self) -> ServiceInfo { + ServiceInfo::of::() + } + + fn provide( + &mut self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + let service = self.provide_typed(injector, request_info)?; + Ok(Self::Interface::from_svc(service)) + } + + fn provide_owned( + &mut self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + let service = self.provide_owned_typed(injector, request_info)?; + Ok(Self::Interface::from_owned_svc(service)) + } +} diff --git a/crates/runtime_injector/src/requests.rs b/crates/runtime_injector/src/requests.rs index d6705ef..bfbab42 100644 --- a/crates/runtime_injector/src/requests.rs +++ b/crates/runtime_injector/src/requests.rs @@ -1,10 +1,10 @@ -// mod arg; +mod arg; mod factory; mod info; mod parameter; mod request; -// pub use arg::*; +pub use arg::*; pub use factory::*; pub use info::*; pub use parameter::*; diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index e27c7e4..7aa927f 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -32,8 +32,8 @@ impl Arg { pub(crate) fn param_name(target: ServiceInfo) -> String { format!( "runtime_injector::Arg[target={:?},type={:?}]", - target.name(), - ServiceInfo::of::().name() + target.id(), + ServiceInfo::of::().id() ) } @@ -194,11 +194,8 @@ mod tests { inner, } => { // Check that the service info is correct. - assert_eq!( - ServiceInfo::of::>(), - service_info, - ); - + assert_eq!(ServiceInfo::of::>(), service_info,); + // Check that the inner error is correct. match inner.downcast_ref::() { Some(ArgRequestError::MissingParameter) => (), @@ -280,6 +277,6 @@ mod tests { let injector = builder.build(); let foo = injector.get::>().unwrap(); - assert_eq!(42, foo.0.0); + assert_eq!(42, foo.0 .0); } } diff --git a/crates/runtime_injector/src/requests/request.rs b/crates/runtime_injector/src/requests/request.rs index aba28bc..16f9484 100644 --- a/crates/runtime_injector/src/requests/request.rs +++ b/crates/runtime_injector/src/requests/request.rs @@ -83,20 +83,24 @@ impl Request for Svc { #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { let mut services: Services = injector.get_with(info)?; - if services.len() > 1 { - Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), - providers: services.len(), - }) - } else { - let service = services.iter().next().transpose()?.ok_or( - InjectError::MissingProvider { + let mut services = services.iter(); + + // Try to get first provided service + let first = + services + .next() + .ok_or_else(|| InjectError::MissingProvider { service_info: ServiceInfo::of::(), - }, - )?; + })?; - Ok(service) + // Check if another service is provided + if services.next().is_some() { + return Err(InjectError::MultipleProviders { + service_info: ServiceInfo::of::(), + }); } + + first } } @@ -105,21 +109,26 @@ impl Request for Svc { /// otherwise succeed. impl Request for Box { fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { + // Get service iterator let mut services: Services = injector.get_with(info)?; - if services.len() > 1 { - Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), - providers: services.len(), - }) - } else { - let service = services.iter_owned().next().transpose()?.ok_or( - InjectError::MissingProvider { + let mut services = services.iter_owned(); + + // Try to get first provided service + let first = + services + .next() + .ok_or_else(|| InjectError::MissingProvider { service_info: ServiceInfo::of::(), - }, - )?; + })?; - Ok(service) + // Check if another service is provided + if services.next().is_some() { + return Err(InjectError::MultipleProviders { + service_info: ServiceInfo::of::(), + }); } + + first } } diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index 528b963..d41e94c 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -20,21 +20,23 @@ where fn from_owned_svc(service: Box) -> Box; } -/// Marks a trait as being an interface for many other types. This means that -/// a request for the given trait can resolve to any of the types indicated by -/// this macro invocation. +/// Marks a trait as being an interface. This means that a request for the +/// given trait can resolve to services of any of the types that implement it. +/// Those services must be registered to this interface when building the +/// [`Injector`](crate::Injector). /// -/// With the "arc" feature enabled, the trait must be a subtrait of [`Send`] -/// and [`Sync`]. This is necessary to allow the service pointers to be -/// downcasted. If the "rc" feature is enabled, this is not required. -/// Additionally, instances of the trait must have a `'static` lifetime. This -/// can be done easily by making your interface a subtrait of [`Service`]. +/// The interface trait must be a subtrait of [`Service`]. This means that +/// implementors must have a static lifetime. If the "arc" feature is enabled, +/// they must also be [`Send`] + [`Sync`]. /// /// ## Example /// /// ``` -/// use runtime_injector::{interface, Service}; +/// use runtime_injector::{ +/// interface, Injector, IntoSingleton, Service, Svc, WithInterface, +/// }; /// +/// #[derive(Default)] /// struct Bar; /// #[cfg(test)] /// struct MockBar; @@ -47,6 +49,12 @@ where /// // Requests for `dyn Foo` can resolve to either `Bar` or, in a test run, /// // `MockBar`. /// interface!(Foo); +/// +/// let mut builder = Injector::builder(); +/// builder.provide(Bar::default.singleton().with_interface::()); +/// +/// let injector = builder.build(); +/// let _bar: Svc = injector.get().unwrap(); /// ``` #[macro_export] macro_rules! interface { @@ -68,6 +76,9 @@ macro_rules! interface { impl $crate::FromProvider for dyn $interface { type Interface = Self; + const SERVICE_TYPE: $crate::ServiceType = + $crate::ServiceType::Interface; + fn should_provide( _provider: &dyn $crate::Provider, ) -> bool { @@ -75,15 +86,15 @@ macro_rules! interface { } fn from_provided( - provided: Svc, - ) -> InjectResult> { - Ok(provided) + provided: $crate::Svc, + ) -> $crate::InjectResult<$crate::Svc> { + ::std::result::Result::Ok(provided) } fn from_provided_owned( - provided: Box, - ) -> InjectResult> { - Ok(provided) + provided: ::std::boxed::Box, + ) -> $crate::InjectResult<::std::boxed::Box> { + ::std::result::Result::Ok(provided) } } }; diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index 281254f..c8aef36 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -162,8 +162,6 @@ pub enum InjectError { MultipleProviders { /// The service that was requested. service_info: ServiceInfo, - /// The number of providers registered for that service. - providers: usize, }, /// The registered provider can't provide an owned variant of the requested @@ -241,12 +239,10 @@ impl Display for InjectError { } InjectError::MultipleProviders { service_info, - providers, } => write!( f, - "the requested service {} has {} providers registered (did you mean to request a Services instead?)", + "the requested service {} has multiple providers registered (did you mean to request a Services instead?)", service_info.name(), - providers ), InjectError::OwnedNotSupported { service_info diff --git a/crates/runtime_injector/src/tests.rs b/crates/runtime_injector/src/tests.rs index 9396f68..f69d5af 100644 --- a/crates/runtime_injector/src/tests.rs +++ b/crates/runtime_injector/src/tests.rs @@ -202,10 +202,7 @@ fn interfaces() { #[test] fn multi_injection() { trait Foo: Service {} - impl Foo for Svc1 {} - impl Foo for Svc2 {} - impl Foo for Svc3 {} interface!(Foo); @@ -213,8 +210,9 @@ fn multi_injection() { builder.provide(Svc1::default.transient().with_interface::()); let injector = builder.build(); + dbg!(&injector); let mut foos: Services = injector.get().unwrap(); - assert_eq!(1, foos.len()); + assert_eq!(1, foos.iter().count()); let foos: Vec> = foos.iter().collect::>().unwrap(); From 79f3975a451947b6dc8f88e3863b9ea5c0e1aa78 Mon Sep 17 00:00:00 2001 From: TehPers Date: Wed, 13 Apr 2022 17:32:26 -0700 Subject: [PATCH 11/26] Remove AsAny, fix errors in "rc" --- crates/runtime_injector/src/any.rs | 27 -------- crates/runtime_injector/src/iter.rs | 2 +- .../src/iter/from_provider.rs | 2 +- crates/runtime_injector/src/iter/providers.rs | 43 ++++++------- crates/runtime_injector/src/lib.rs | 62 +++++++++---------- crates/runtime_injector/src/requests/arg.rs | 12 ++-- .../runtime_injector/src/services/service.rs | 10 +-- 7 files changed, 62 insertions(+), 96 deletions(-) delete mode 100644 crates/runtime_injector/src/any.rs diff --git a/crates/runtime_injector/src/any.rs b/crates/runtime_injector/src/any.rs deleted file mode 100644 index 13853af..0000000 --- a/crates/runtime_injector/src/any.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::any::Any; - -/// Defines a conversion for a type into an [`dyn Any`](Any) trait object. -pub trait AsAny: Any { - /// Converts `self` into a trait object. - fn as_any(&self) -> &dyn Any; - - /// Converts `self` into a mutable trait object. - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Converts `Box` into a trait object. - fn into_any(self: Box) -> Box; -} - -impl AsAny for T { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_any(self: Box) -> Box { - self - } -} diff --git a/crates/runtime_injector/src/iter.rs b/crates/runtime_injector/src/iter.rs index bcdea72..c19dc4a 100644 --- a/crates/runtime_injector/src/iter.rs +++ b/crates/runtime_injector/src/iter.rs @@ -1,6 +1,6 @@ mod from_provider; -mod services; mod providers; +mod services; pub use from_provider::*; pub use providers::*; diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs index 2eadb03..607f62c 100644 --- a/crates/runtime_injector/src/iter/from_provider.rs +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -48,7 +48,7 @@ impl FromProvider for S { } })?; #[cfg(feature = "rc")] - let provided = service.downcast_rc().map_err(|_| { + let provided = provided.downcast_rc().map_err(|_| { InjectError::InvalidProvider { service_info: ServiceInfo::of::(), } diff --git a/crates/runtime_injector/src/iter/providers.rs b/crates/runtime_injector/src/iter/providers.rs index 2eda7db..f2294a7 100644 --- a/crates/runtime_injector/src/iter/providers.rs +++ b/crates/runtime_injector/src/iter/providers.rs @@ -2,7 +2,8 @@ use crate::{ provider_registry::{ InterfaceRegistry, ProviderRegistry, ProviderRegistryIterMut, }, - InjectError, InjectResult, Interface, MapContainer, Provider, ServiceInfo, + InjectResult, Interface, MapContainer, MapContainerEx, Provider, + ServiceInfo, }; use std::slice::IterMut; @@ -81,29 +82,23 @@ where I: ?Sized + Interface, { fn drop(&mut self) { - let result = self - .parent_registry - .lock() - .map_err(|_| { - InjectError::InternalError( - "failed to acquire lock for interface registry".into(), - ) - }) - .and_then(|mut registry| match self.providers_source { - ProvidersSource::Services { - ref mut providers, - service_info, - } => { - let providers = std::mem::take(providers); - registry.reclaim_providers_for(service_info, providers) - } - ProvidersSource::Interface { - ref mut provider_registry, - } => { - let provider_registry = std::mem::take(provider_registry); - registry.reclaim(provider_registry) - } - }); + let result = self.parent_registry.with_inner_mut(|registry| match self + .providers_source + { + ProvidersSource::Services { + ref mut providers, + service_info, + } => { + let providers = std::mem::take(providers); + registry.reclaim_providers_for(service_info, providers) + } + ProvidersSource::Interface { + ref mut provider_registry, + } => { + let provider_registry = std::mem::take(provider_registry); + registry.reclaim(provider_registry) + } + }); if let Err(error) = result { eprintln!( diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index a8b30f6..7845add 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -1,28 +1,28 @@ //! # Runtime dependency injection. -//! +//! //! By default, services provided by the [`Injector`] use thread-safe pointers. //! This is because [`Arc`](std::sync::Arc) is used to hold instances of the //! services. This can be changed to [`Rc`](std::rc::Rc) by disabling //! default features and enabling the "rc" feature: -//! +//! //! ```text //! [dependencies.runtime_injector] //! version = "*" # Replace with the version you want to use //! default-features = false //! features = ["rc"] //! ``` -//! +//! //! ## Getting started -//! +//! //! If you are unfamiliar with dependency injection, then you may want to check //! about how a container can help [simplify your application][ioc]. Otherwise, //! check out the [getting started guide][getting-started] -//! +//! //! [ioc]: crate::docs::inversion_of_control //! [getting-started]: crate::docs::getting_started -//! +//! //! ## Dependency injection at runtime (rather than compile-time) -//! +//! //! Runtime dependency injection allows for advanced configuration of services //! during runtime rather than needing to decide what services your application //! will use at compile time. This means you can read a config when your @@ -34,9 +34,9 @@ //! I/O-based applications like a web server, the additional overhead is //! probably insignificant compared to the additional flexibility you get with //! runtime_injector. -//! +//! //! ## Interfaces -//! +//! //! Using interfaces allows you to write your services without worrying about //! how its dependencies are implemented. You can think of them like generic //! type parameters for your service, except rather than needing to add a new @@ -50,13 +50,13 @@ //! Similarly, `dyn UserDatabase` is your interface. You can read more about //! how interfaces work and how they're created in the //! [type-level docs](crate::Interface). -//! +//! //! ## Service lifetimes -//! +//! //! Lifetimes of services created by the [`Injector`] are controlled by the //! [`Provider`] used to construct those lifetimes. Currently, there are three //! built-in service provider types: -//! +//! //! - **[Transient](crate::TransientProvider):** A service is created each time //! it is requested. This will never return the same instance of a service //! more than once. @@ -67,20 +67,20 @@ //! created using a service factory and instead can have their instance //! provided to the container directly. This behaves similar to singleton in //! that the same instance is provided each time the service is requested. -//! +//! //! Custom service providers can also be created by implementing either the //! [`TypedProvider`] or [`Provider`] trait. -//! +//! //! ## Fallible service factories -//! +//! //! Not all types can always be successfully created. Sometimes, creating an //! instance of a service might fail. Rather than panicking on error, it's //! possible to instead return a [`Result`] from your constructors and //! inject the result as a [`Svc`]. Read more in the //! [docs for `IntoFallible`](crate::IntoFallible). -//! +//! //! ## Owned service pointers -//! +//! //! In general, providers need to be able to provide their services via //! reference-counted service pointers, or [`Svc`]. The issue with this is //! that you cannot get mutable or owned access to the contents of those @@ -90,9 +90,9 @@ //! to inject it as a [`Box`] than clone it from a reference-counted service //! pointer. In these cases, you can request a [`Box`] directly from the //! injector and avoid needing to clone your dependency entirely! -//! +//! //! ## Custom target-specific arguments -//! +//! //! Sometimes it's useful to be able to pass a specific value into your //! services. For example, if you're writing a database service and you need a //! connection string, you could define a new `ConnectionString` struct as a @@ -106,19 +106,19 @@ //! `Arg` in your logging service to set your logging format without //! needing to worry about accidentally using your connection string as your //! logging format! -//! +//! //! ## Example -//! +//! //! ```rust //! use runtime_injector::{ //! define_module, Module, interface, Injector, Svc, IntoSingleton, //! TypedProvider, IntoTransient, constant, Service, WithInterface, //! }; //! use std::error::Error; -//! +//! //! // Some type that represents a user //! struct User; -//! +//! //! // This is our interface. In practice, multiple structs can implement this //! // trait, and we don't care what the concrete type is most of the time in //! // our other services as long as it implements this trait. Because of this, @@ -134,24 +134,24 @@ //! trait DataService: Service { //! fn get_user(&self, user_id: &str) -> Option; //! } -//! +//! //! // We can use a data service which connects to a SQL database. //! #[derive(Default)] //! struct SqlDataService; //! impl DataService for SqlDataService { //! fn get_user(&self, _user_id: &str) -> Option { todo!() } //! } -//! +//! //! // ... Or we can mock out the data service entirely! //! #[derive(Default)] //! struct MockDataService; //! impl DataService for MockDataService { //! fn get_user(&self, _user_id: &str) -> Option { Some(User) } //! } -//! +//! //! // Declare `DataService` as an interface //! interface!(DataService); -//! +//! //! // Here's another service our application uses. This service depends on our //! // data service, however it doesn't care how that service is actually //! // implemented as long as it works. Because of that, we're using dynamic @@ -159,7 +159,7 @@ //! struct UserService { //! data_service: Svc, //! } -//! +//! //! impl UserService { //! // This is just a normal constructor. The only requirement is that each //! // parameter is a valid injectable dependency. @@ -171,7 +171,7 @@ //! self.data_service.get_user(user_id) //! } //! } -//! +//! //! fn main() -> Result<(), Box> { //! // This is where we register our services. Each call to `.provide` adds //! // a new service provider to our container, however nothing is actually @@ -179,7 +179,7 @@ //! // types we aren't actually going to use without worrying about //! // constructing instances of those types that we aren't actually using. //! let mut builder = Injector::builder(); -//! +//! //! // We can manually add providers to our builder //! builder.provide(UserService::new.singleton()); //! struct Foo(Svc); @@ -242,7 +242,6 @@ compile_error!( "The 'arc' and 'rc' features are mutually exclusive and cannot be enabled together." ); -mod any; mod builder; mod injector; mod iter; @@ -252,7 +251,6 @@ mod providers; mod requests; mod services; -pub use any::*; pub use builder::*; pub use injector::*; pub use iter::*; diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/requests/arg.rs index 7aa927f..87fd876 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/requests/arg.rs @@ -1,6 +1,6 @@ use crate::{ - AsAny, InjectError, InjectResult, Injector, InjectorBuilder, Module, - Request, RequestInfo, RequestParameter, Service, ServiceInfo, + InjectError, InjectResult, Injector, InjectorBuilder, Module, Request, + RequestInfo, RequestParameter, Service, ServiceInfo, }; use std::{ error::Error, @@ -125,14 +125,14 @@ impl Display for ArgRequestError { /// Allows defining pre-defined arguments to services. pub trait WithArg { /// Adds an argument for a service. See the docs for [`Arg`]. - fn with_arg( + fn with_arg( &mut self, value: T, ) -> Option>; } impl WithArg for RequestInfo { - fn with_arg( + fn with_arg( &mut self, value: T, ) -> Option> { @@ -144,7 +144,7 @@ impl WithArg for RequestInfo { } impl WithArg for InjectorBuilder { - fn with_arg( + fn with_arg( &mut self, value: T, ) -> Option> { @@ -153,7 +153,7 @@ impl WithArg for InjectorBuilder { } impl WithArg for Module { - fn with_arg( + fn with_arg( &mut self, value: T, ) -> Option> { diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index c8aef36..6196e24 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -1,5 +1,5 @@ use crate::interface; -use downcast_rs::{impl_downcast, DowncastSync}; +use downcast_rs::impl_downcast; use std::{ any::{Any, TypeId}, error::Error, @@ -60,12 +60,12 @@ feature_unique!( /// service. }, { - pub trait Service: Downcast {} - impl Service for T {} + pub trait Service: downcast_rs::Downcast {} + impl Service for T {} }, { - pub trait Service: DowncastSync {} - impl Service for T {} + pub trait Service: downcast_rs::DowncastSync {} + impl Service for T {} } ); From 2ef6f1b8cb628c92f2ca130c97c506be9daec26b Mon Sep 17 00:00:00 2001 From: TehPers Date: Sat, 16 Apr 2022 16:13:03 -0700 Subject: [PATCH 12/26] Update actix-web v3->v4, add docs and tests --- crates/runtime_injector/src/injector.rs | 10 ++--- .../src/iter/from_provider.rs | 13 ++++-- crates/runtime_injector/src/iter/providers.rs | 40 +++++++++++++++++++ crates/runtime_injector/src/iter/services.rs | 34 +++++++++++++++- .../src/services/interface.rs | 4 +- crates/runtime_injector_actix/Cargo.toml | 2 +- crates/runtime_injector_actix/src/service.rs | 31 +++++++++++--- 7 files changed, 115 insertions(+), 19 deletions(-) diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index ba34516..62ed905 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -1,6 +1,6 @@ use crate::{ - FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, Providers, - Request, RequestInfo, ServiceInfo, ServiceType, Services, Svc, + FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, + ProviderType, Providers, Request, RequestInfo, ServiceInfo, Services, Svc, }; pub(crate) trait MapContainerEx { @@ -296,8 +296,8 @@ impl Injector { &self, request_info: &RequestInfo, ) -> InjectResult> { - let providers = match S::SERVICE_TYPE { - ServiceType::Service => { + let providers = match S::PROVIDER_TYPE { + ProviderType::Service => { let service_info = ServiceInfo::of::(); let providers = self.interface_registry.with_inner_mut(|registry| { @@ -309,7 +309,7 @@ impl Injector { service_info, ) } - ServiceType::Interface => self + ProviderType::Interface => self .interface_registry .with_inner_mut(|registry| registry.take()) .map(|provider_registry| { diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs index 607f62c..7c6c332 100644 --- a/crates/runtime_injector/src/iter/from_provider.rs +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -2,16 +2,23 @@ use crate::{ InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, Svc, }; +/// The type of provider that should be requested. #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub enum ServiceType { +pub enum ProviderType { + /// Providers for implementations of services should be requested. Service, + /// Providers for implementations of an interface should be requested. Interface, } +/// A type that can be requested from a provider. pub trait FromProvider: Service { + /// The interface to request providers for. type Interface: ?Sized + Interface; - const SERVICE_TYPE: ServiceType; + /// The type of providers to request. Providers can either provide + /// implementations of a service type or of an interface type. + const PROVIDER_TYPE: ProviderType; fn should_provide( provider: &dyn Provider, @@ -28,7 +35,7 @@ pub trait FromProvider: Service { impl FromProvider for S { type Interface = dyn Service; - const SERVICE_TYPE: ServiceType = ServiceType::Service; + const PROVIDER_TYPE: ProviderType = ProviderType::Service; #[inline] fn should_provide( diff --git a/crates/runtime_injector/src/iter/providers.rs b/crates/runtime_injector/src/iter/providers.rs index f2294a7..627cc49 100644 --- a/crates/runtime_injector/src/iter/providers.rs +++ b/crates/runtime_injector/src/iter/providers.rs @@ -20,6 +20,37 @@ where }, } +/// A collection of all the providers for a particular service or interface. No +/// services are activated during iteration of this collection. +/// +/// ``` +/// use runtime_injector::{ +/// interface, Injector, IntoTransient, Providers, Service, Svc, +/// TypedProvider, WithInterface, +/// }; +/// +/// trait Fooable: Service { +/// fn baz(&self) {} +/// } +/// +/// interface!(Fooable); +/// +/// #[derive(Default)] +/// struct Foo; +/// impl Fooable for Foo {} +/// +/// #[derive(Default)] +/// struct Bar; +/// impl Fooable for Bar {} +/// +/// let mut builder = Injector::builder(); +/// builder.provide(Foo::default.transient().with_interface::()); +/// builder.provide(Bar::default.transient().with_interface::()); +/// +/// let injector = builder.build(); +/// let mut fooables: Providers = injector.get().unwrap(); +/// assert_eq!(2, fooables.iter().count()); +/// ``` pub struct Providers where I: ?Sized + Interface, @@ -58,6 +89,8 @@ where } } + /// Gets all the providers for the given type. No services are activated + /// during iteration of this collection. #[inline] pub fn iter(&mut self) -> ProviderIter<'_, I> { match self.providers_source { @@ -122,6 +155,8 @@ where } } +/// An iterator over the providers for services of the given type. No services +/// are activated during iteration of this collection. pub struct ServiceProviderIter<'a, I> where I: ?Sized + Interface, @@ -149,6 +184,8 @@ where } } +/// An iterator over the providers for the given interface type. No services +/// are activated during iteration of this collection. pub struct InterfaceProviderIter<'a, I> where I: ?Sized + Interface, @@ -167,11 +204,14 @@ where } } +/// An iterator over the providers for the given service or interface type. pub enum ProviderIter<'a, I> where I: ?Sized + Interface, { + /// Iterator over providers for a service type. Services(ServiceProviderIter<'a, I>), + /// Iterator over providers for an interface type. Interface(InterfaceProviderIter<'a, I>), } diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs index 929cc5e..6d3e453 100644 --- a/crates/runtime_injector/src/iter/services.rs +++ b/crates/runtime_injector/src/iter/services.rs @@ -4,8 +4,9 @@ use crate::{ }; use std::marker::PhantomData; -/// A collection of all the providers for a particular service or interface. -/// Each service is activated only during iteration of this collection. +/// A collection of all the implementations for a particular service or +/// interface. Each service is activated only during iteration of this +/// collection. /// /// If a type only has one implementation registered for it, then it may be /// easier to request [`Svc`] from the container instead. However, if @@ -280,4 +281,33 @@ where #[cfg(test)] mod tests { use super::*; + use crate::{constant, IntoSingleton}; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[test] + fn service_initialized_only_on_iteration() { + struct Counter; + impl Counter { + fn new(flag: Svc) -> Self { + flag.store(true, Ordering::Relaxed); + Counter + } + } + + // Setup injector + let mut builder = Injector::builder(); + builder.provide(Counter::new.singleton()); + builder.provide(constant(AtomicBool::new(false))); + + let injector = builder.build(); + let mut services: Services = injector.get().unwrap(); + let initialized: Svc = injector.get().unwrap(); + + // Check that it isn't initialized yet + assert!(!initialized.load(Ordering::Relaxed)); + + // Check that it is initialized after iteration + let _: Svc = services.iter().next().unwrap().unwrap(); + assert!(initialized.load(Ordering::Relaxed)); + } } diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index d41e94c..44871c9 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -76,8 +76,8 @@ macro_rules! interface { impl $crate::FromProvider for dyn $interface { type Interface = Self; - const SERVICE_TYPE: $crate::ServiceType = - $crate::ServiceType::Interface; + const PROVIDER_TYPE: $crate::ProviderType = + $crate::ProviderType::Interface; fn should_provide( _provider: &dyn $crate::Provider, diff --git a/crates/runtime_injector_actix/Cargo.toml b/crates/runtime_injector_actix/Cargo.toml index 61d5075..3fa6d08 100644 --- a/crates/runtime_injector_actix/Cargo.toml +++ b/crates/runtime_injector_actix/Cargo.toml @@ -16,7 +16,7 @@ default = [] arc = [] # Ignored, just used for CI [dependencies] -actix-web = "3" +actix-web = "4" futures-util = "0.3" [dependencies.runtime_injector] diff --git a/crates/runtime_injector_actix/src/service.rs b/crates/runtime_injector_actix/src/service.rs index 3f2ba12..aa67a2f 100644 --- a/crates/runtime_injector_actix/src/service.rs +++ b/crates/runtime_injector_actix/src/service.rs @@ -3,7 +3,7 @@ use actix_web::{ }; use futures_util::future::{err, ok, Ready}; use runtime_injector::{Injector, Request}; -use std::ops::Deref; +use std::{fmt::Display, ops::Deref}; /// An injected request. Any request to the [`Injector`] can be injected by /// wrapping it in this type and providing it as a parameter to your request @@ -39,16 +39,24 @@ use std::ops::Deref; /// } /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Injected(R); +pub struct Injected(R) +where + R: Request; -impl Injected { +impl Injected +where + R: Request, +{ /// Converts an [`Injected`] to its inner value. pub fn into_inner(value: Injected) -> R { value.0 } } -impl Deref for Injected { +impl Deref for Injected +where + R: Request, +{ type Target = R; fn deref(&self) -> &Self::Target { @@ -56,10 +64,21 @@ impl Deref for Injected { } } -impl FromRequest for Injected { +impl Display for Injected +where + R: Request + Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl FromRequest for Injected +where + R: Request, +{ type Error = actix_web::Error; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { let injector: &Injector = match req.app_data() { From e5451af8567db9ff14b452ee80b6a0aec1b3ecec Mon Sep 17 00:00:00 2001 From: TehPers Date: Sun, 17 Apr 2022 15:11:37 -0700 Subject: [PATCH 13/26] Update cycle detection, providers, and builders Cycle detection now occurs when creating a child RequestInfo to ensure it is always checked when a child request is performed. Child requests are now created in providers instead: - Child requests should not be made in ServiceFactory. A ServiceFactory can be used to create another ServiceFactory (like FallibleServiceFactory), and the ServiceInfo used by the child RequestInfo will be wrong (it'll be for a Result instead of a T even though FallibleServiceFactory makes the actual provided service a T) - Child requests should not be made in Services. Providers can now be requested directly, which would bypass the entire Services type. - Child requests cannot be made in Providers because it only returns the providers. The RequestInfo is passed directly into the provider later, and Providers has no control over that. - Providers know what type they're providing, so it makes sense to force them to add the type to the child request before invoking their inner service factory. Unfortunately, this means that users creating custom providers will have to remember to create the child requests themselves in their providers. There may be a way to force child requests to be created though using generics? Providers now take &self instead of &mut self. It greatly simplifies the logic for iterating over providers because the iterator no longer needs to take ownership of the provider to be able to use it. This means that SingletonProvider needs to have a lock in it, but this is still cheaper than requiring a lock on all providers. Due to the above changes, a large part of InjectorBuilder and Module needed to be rewritten. The above changes also opened up an opportunity to simplify how the builder types are implemented. --- crates/runtime_injector/src/builder.rs | 233 +++--------- crates/runtime_injector/src/injector.rs | 96 +---- .../src/iter/from_provider.rs | 21 +- crates/runtime_injector/src/iter/providers.rs | 184 ++-------- crates/runtime_injector/src/iter/services.rs | 53 +-- crates/runtime_injector/src/module.rs | 8 +- .../runtime_injector/src/provider_registry.rs | 345 ++++-------------- .../src/providers/conditional.rs | 4 +- .../src/providers/constant.rs | 2 +- crates/runtime_injector/src/providers/func.rs | 5 +- .../src/providers/interface.rs | 4 +- .../src/providers/providers.rs | 14 +- .../src/providers/singleton.rs | 54 ++- .../src/providers/transient.rs | 16 +- crates/runtime_injector/src/requests/info.rs | 28 +- .../runtime_injector/src/requests/request.rs | 12 +- .../src/services/interface.rs | 3 - .../runtime_injector/src/services/service.rs | 11 +- 18 files changed, 279 insertions(+), 814 deletions(-) diff --git a/crates/runtime_injector/src/builder.rs b/crates/runtime_injector/src/builder.rs index 153b6df..bb047d0 100644 --- a/crates/runtime_injector/src/builder.rs +++ b/crates/runtime_injector/src/builder.rs @@ -1,20 +1,17 @@ use crate::{ - provider_registry::{ - ProviderRegistry, ProviderRegistryType, ProviderSlot, Slot, - }, + provider_registry::{ProviderRegistry, ProviderRegistryType}, Injector, Interface, InterfaceRegistry, Module, Provider, RequestInfo, - Service, ServiceInfo, + Service, ServiceInfo, Svc, }; -use downcast_rs::impl_downcast; use std::{ collections::{hash_map::Entry, HashMap}, - fmt::{Debug, Formatter}, + fmt::Debug, }; /// A builder for an [`Injector`]. #[derive(Debug, Default)] pub struct InjectorBuilder { - registry_builder: InterfaceRegistryBuilder, + registry: InterfaceRegistryBuilder, root_info: RequestInfo, } @@ -25,18 +22,17 @@ impl InjectorBuilder { where P: Provider, { - self.add_provider(Box::new(provider)) + self.add_provider(Svc::new(provider)) } /// Adds a provider to the injector. - #[allow(clippy::missing_panics_doc)] pub fn add_provider( &mut self, - provider: Box>, + provider: Svc>, ) where I: ?Sized + Interface, { - self.registry_builder + self.registry .ensure_providers_mut() .add_provider_for(provider.result(), provider); } @@ -45,7 +41,7 @@ impl InjectorBuilder { pub fn remove_providers( &mut self, service_info: ServiceInfo, - ) -> Vec>> { + ) -> Vec>> { self.remove_providers_for::(service_info) } @@ -53,24 +49,18 @@ impl InjectorBuilder { pub fn remove_providers_for( &mut self, service_info: ServiceInfo, - ) -> Vec>> + ) -> Vec>> where I: ?Sized + Interface, { - self.registry_builder - .providers_mut::() - .map(|providers| { - providers - .remove_providers_for(service_info) - .into_inner() - .unwrap() - }) + self.registry + .remove_providers_for::(service_info) .unwrap_or_default() } /// Clears all providers. pub fn clear_providers(&mut self) { - self.registry_builder.clear(); + self.registry.clear(); } /// Clears all providers for an interface. @@ -78,9 +68,7 @@ impl InjectorBuilder { where I: ?Sized + Interface, { - self.registry_builder - .providers - .remove(&ServiceInfo::of::()); + self.registry.remove_providers::(); } /// Borrows the root [`RequestInfo`] that will be used by calls to @@ -105,7 +93,7 @@ impl InjectorBuilder { #[allow(clippy::missing_panics_doc)] pub fn add_module(&mut self, module: Module) { // Merge providers - self.registry_builder.merge(module.registry_builder); + self.registry.merge(module.registry); // Merge parameters for (key, value) in module.parameters { @@ -116,175 +104,63 @@ impl InjectorBuilder { /// Builds the injector. #[must_use] pub fn build(self) -> Injector { - Injector::new_from_parts(self.registry_builder.build(), self.root_info) - } -} - -pub(crate) struct ProviderRegistryBuilder -where - I: ?Sized + Interface, -{ - providers: HashMap>, -} - -impl ProviderRegistryBuilder -where - I: ?Sized + Interface, -{ - pub fn add_provider_for( - &mut self, - service_info: ServiceInfo, - provider: Box>, - ) { - #[allow(clippy::missing_panics_doc)] - self.providers - .entry(service_info) - .or_default() - .with_inner_mut(|providers| providers.push(provider)) - .unwrap(); - } - - pub fn remove_providers_for( - &mut self, - service_info: ServiceInfo, - ) -> ProviderSlot { - #[allow(clippy::missing_panics_doc)] - self.providers.remove(&service_info).unwrap_or_default() - } -} - -impl Default for ProviderRegistryBuilder -where - I: ?Sized + Interface, -{ - fn default() -> Self { - Self { - providers: HashMap::new(), - } - } -} - -impl Debug for ProviderRegistryBuilder -where - I: ?Sized + Interface, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ProviderRegistryBuilder") - .field("providers", &self.providers) - .finish() - } -} - -pub(crate) trait ProviderRegistryBuilderType: Service + Debug { - fn merge( - &mut self, - other: Box, - ) -> Result<(), Box>; - - fn build(&mut self) -> Box; -} - -impl ProviderRegistryBuilderType for ProviderRegistryBuilder -where - I: ?Sized + Interface, -{ - fn merge( - &mut self, - other: Box, - ) -> Result<(), Box> { - let other: Box = other.downcast()?; - for (service_info, other_providers) in other.providers { - #[allow(clippy::missing_panics_doc)] - let mut other_providers = other_providers.into_inner().unwrap(); - self.providers - .entry(service_info) - .or_default() - .with_inner_mut(|providers| { - providers.append(&mut other_providers) - }) - .unwrap(); - } - - Ok(()) - } - - fn build(&mut self) -> Box { - Box::new(ProviderRegistry::new(std::mem::take(&mut self.providers))) - } -} - -#[cfg(feature = "arc")] -impl_downcast!(sync ProviderRegistryBuilderType); - -#[cfg(feature = "rc")] -impl_downcast!(ProviderRegistryBuilderType); - -impl Debug for Slot> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Slot").field(&self.inner()).finish() + Injector::new_from_parts(self.registry.build(), self.root_info) } } #[derive(Debug, Default)] pub(crate) struct InterfaceRegistryBuilder { - providers: HashMap>>, + registries: HashMap>, } impl InterfaceRegistryBuilder { - pub fn providers_mut( - &mut self, - ) -> Option<&mut ProviderRegistryBuilder> + pub fn ensure_providers_mut(&mut self) -> &mut ProviderRegistry where I: ?Sized + Interface, { - #[allow(clippy::missing_panics_doc)] - self.providers - .get_mut(&ServiceInfo::of::()) - .map(Slot::inner_mut) - .map(Option::unwrap) - .map(|providers| { - providers - .downcast_mut::>() - .unwrap() - }) - } - - pub fn ensure_providers_mut(&mut self) -> &mut ProviderRegistryBuilder - where - I: ?Sized + Interface, - { - #[allow(clippy::missing_panics_doc)] - self.providers + self.registries .entry(ServiceInfo::of::()) .or_insert_with(|| { - (Box::new(ProviderRegistryBuilder::::default()) - as Box) + (Box::new(ProviderRegistry::::default()) + as Box) .into() }) - .inner_mut() - .unwrap() .downcast_mut() .unwrap() } + pub fn remove_providers(&mut self) -> Option> + where + I: ?Sized + Interface, + { + let interface_info = ServiceInfo::of::(); + let registry = self.registries.remove(&interface_info)?; + let registry = registry.downcast().unwrap(); + Some(*registry) + } + + pub fn remove_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> Option>>> + where + I: ?Sized + Interface, + { + let registry = self.registries.get_mut(&service_info)?; + let registry: &mut ProviderRegistry = + registry.downcast_mut().unwrap(); + registry.remove_providers_for(service_info) + } + pub fn clear(&mut self) { - self.providers.clear(); + self.registries.clear(); } pub fn merge(&mut self, other: InterfaceRegistryBuilder) { - for (service_info, other_providers) in other.providers { - #[allow(clippy::missing_panics_doc)] - let other_providers = other_providers.into_inner().unwrap(); - match self.providers.entry(service_info) { + for (interface_info, other_providers) in other.registries { + match self.registries.entry(interface_info) { Entry::Occupied(entry) => { - #[allow(clippy::missing_panics_doc)] - entry - .into_mut() - .inner_mut() - .unwrap() - .merge(other_providers) - .map_err(|_| "error merging provider builders") - .unwrap(); + entry.into_mut().merge(other_providers).unwrap(); } Entry::Vacant(entry) => { entry.insert(other_providers.into()); @@ -294,18 +170,11 @@ impl InterfaceRegistryBuilder { } pub fn build(self) -> InterfaceRegistry { - let providers = self - .providers + let registries = self + .registries .into_iter() - .map(|(service_info, mut providers)| { - ( - service_info, - providers - .with_inner_mut(|providers| providers.build()) - .into(), - ) - }) + .map(|(k, v)| (k, v.into())) .collect(); - InterfaceRegistry::new(providers) + InterfaceRegistry::new(registries) } } diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index 62ed905..469fcc8 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -1,61 +1,8 @@ use crate::{ - FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, - ProviderType, Providers, Request, RequestInfo, ServiceInfo, Services, Svc, + FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, Providers, + Request, RequestInfo, Svc, }; -pub(crate) trait MapContainerEx { - fn new(value: T) -> Self; - fn with_inner R>(&self, f: F) -> R; - fn with_inner_mut R>(&self, f: F) -> R; -} - -#[cfg(feature = "rc")] -mod types { - use super::MapContainerEx; - use std::{cell::RefCell, rc::Rc}; - - pub type MapContainer = Rc>; - - impl MapContainerEx for MapContainer { - fn new(value: T) -> Self { - Rc::new(RefCell::new(value)) - } - - fn with_inner R>(&self, f: F) -> R { - f(&*self.borrow()) - } - - fn with_inner_mut R>(&self, f: F) -> R { - f(&mut *self.borrow_mut()) - } - } -} - -#[cfg(feature = "arc")] -mod types { - use super::MapContainerEx; - use std::sync::{Arc, Mutex}; - - pub type MapContainer = Arc>; - - impl MapContainerEx for MapContainer { - fn new(value: T) -> Self { - Arc::new(Mutex::new(value)) - } - - fn with_inner R>(&self, f: F) -> R { - f(&*self.lock().unwrap()) - } - - fn with_inner_mut R>(&self, f: F) -> R { - f(&mut *self.lock().unwrap()) - } - } -} - -#[allow(clippy::wildcard_imports)] -pub(crate) use types::*; - /// A runtime dependency injection container. This holds all the bindings /// between service types and their providers, as well as all the mappings from /// interfaces to their implementations (if they differ). @@ -112,7 +59,7 @@ pub(crate) use types::*; /// ``` #[derive(Clone, Debug, Default)] pub struct Injector { - interface_registry: MapContainer, + interface_registry: Svc, root_request_info: Svc, } @@ -129,7 +76,7 @@ impl Injector { request_info: RequestInfo, ) -> Self { Injector { - interface_registry: MapContainerEx::new(interface_registry), + interface_registry: Svc::new(interface_registry), root_request_info: Svc::new(request_info), } } @@ -289,38 +236,9 @@ impl Injector { R::request(self, request_info) } - /// Gets implementations of a service from the container. This is - /// equivalent to requesting [`Services`] from [`Injector::get()`]. #[doc(hidden)] - pub fn get_all( - &self, - request_info: &RequestInfo, - ) -> InjectResult> { - let providers = match S::PROVIDER_TYPE { - ProviderType::Service => { - let service_info = ServiceInfo::of::(); - let providers = - self.interface_registry.with_inner_mut(|registry| { - registry.take_providers_for(service_info) - })?; - Providers::services( - self.interface_registry.clone(), - providers, - service_info, - ) - } - ProviderType::Interface => self - .interface_registry - .with_inner_mut(|registry| registry.take()) - .map(|provider_registry| { - Providers::interface( - self.interface_registry.clone(), - provider_registry, - ) - })?, - }; - - Ok(Services::new(self.clone(), request_info.clone(), providers)) + pub fn get_providers(&self) -> Providers { + Providers::new(self.interface_registry.get_providers()) } } @@ -343,7 +261,7 @@ mod tests { } fn provide( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult { diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs index 7c6c332..5edbd9f 100644 --- a/crates/runtime_injector/src/iter/from_provider.rs +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -2,31 +2,21 @@ use crate::{ InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, Svc, }; -/// The type of provider that should be requested. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub enum ProviderType { - /// Providers for implementations of services should be requested. - Service, - /// Providers for implementations of an interface should be requested. - Interface, -} - /// A type that can be requested from a provider. pub trait FromProvider: Service { /// The interface to request providers for. type Interface: ?Sized + Interface; - /// The type of providers to request. Providers can either provide - /// implementations of a service type or of an interface type. - const PROVIDER_TYPE: ProviderType; - + /// Whether the given provider is valid for this type. fn should_provide( provider: &dyn Provider, ) -> bool; + /// Converts a provided service into a service pointer of this type. fn from_provided(provided: Svc) -> InjectResult>; + /// Converts a provided service into an owned service pointer of this type. fn from_provided_owned( provided: Box, ) -> InjectResult>; @@ -35,13 +25,10 @@ pub trait FromProvider: Service { impl FromProvider for S { type Interface = dyn Service; - const PROVIDER_TYPE: ProviderType = ProviderType::Service; - - #[inline] fn should_provide( provider: &dyn Provider, ) -> bool { - provider.result() == ServiceInfo::of::() + provider.result() == ServiceInfo::of::() } #[inline] diff --git a/crates/runtime_injector/src/iter/providers.rs b/crates/runtime_injector/src/iter/providers.rs index 627cc49..ecf6b67 100644 --- a/crates/runtime_injector/src/iter/providers.rs +++ b/crates/runtime_injector/src/iter/providers.rs @@ -1,24 +1,6 @@ use crate::{ - provider_registry::{ - InterfaceRegistry, ProviderRegistry, ProviderRegistryIterMut, - }, - InjectResult, Interface, MapContainer, MapContainerEx, Provider, - ServiceInfo, + FromProvider, Provider, ProviderRegistry, ProviderRegistryIter, Svc, }; -use std::slice::IterMut; - -enum ProvidersSource -where - I: ?Sized + Interface, -{ - Services { - providers: Vec>>, - service_info: ServiceInfo, - }, - Interface { - provider_registry: ProviderRegistry, - }, -} /// A collection of all the providers for a particular service or interface. No /// services are activated during iteration of this collection. @@ -51,180 +33,68 @@ where /// let mut fooables: Providers = injector.get().unwrap(); /// assert_eq!(2, fooables.iter().count()); /// ``` -pub struct Providers +pub struct Providers where - I: ?Sized + Interface, + S: ?Sized + FromProvider, { - parent_registry: MapContainer, - providers_source: ProvidersSource, + parent_registry: Svc>, } -impl Providers +impl Providers where - I: ?Sized + Interface, + S: ?Sized + FromProvider, { - #[inline] - pub(crate) fn services( - parent_registry: MapContainer, - providers: Vec>>, - service_info: ServiceInfo, - ) -> Self { - Providers { - parent_registry, - providers_source: ProvidersSource::Services { - providers, - service_info, - }, - } - } - - #[inline] - pub(crate) fn interface( - parent_registry: MapContainer, - provider_registry: ProviderRegistry, + pub(crate) fn new( + parent_registry: Svc>, ) -> Self { - Providers { - parent_registry, - providers_source: ProvidersSource::Interface { provider_registry }, - } + Self { parent_registry } } /// Gets all the providers for the given type. No services are activated /// during iteration of this collection. #[inline] - pub fn iter(&mut self) -> ProviderIter<'_, I> { - match self.providers_source { - ProvidersSource::Services { - ref mut providers, - service_info, - } => ProviderIter::Services(ServiceProviderIter { - providers: providers.iter_mut(), - service_info, - }), - ProvidersSource::Interface { - ref mut provider_registry, - } => ProviderIter::Interface(InterfaceProviderIter { - inner: provider_registry.iter_mut(), - }), + pub fn iter(&mut self) -> ProviderIter<'_, S> { + ProviderIter { + inner: self.parent_registry.iter(), } } } -impl Drop for Providers +impl<'a, S> IntoIterator for &'a mut Providers where - I: ?Sized + Interface, + S: ?Sized + FromProvider, { - fn drop(&mut self) { - let result = self.parent_registry.with_inner_mut(|registry| match self - .providers_source - { - ProvidersSource::Services { - ref mut providers, - service_info, - } => { - let providers = std::mem::take(providers); - registry.reclaim_providers_for(service_info, providers) - } - ProvidersSource::Interface { - ref mut provider_registry, - } => { - let provider_registry = std::mem::take(provider_registry); - registry.reclaim(provider_registry) - } - }); - - if let Err(error) = result { - eprintln!( - "An error occurred while releasing providiers for {}: {:?}", - ServiceInfo::of::().name(), - error - ); - } - } -} - -impl<'a, I> IntoIterator for &'a mut Providers -where - I: ?Sized + Interface, -{ - type Item = InjectResult<&'a mut dyn Provider>; - type IntoIter = ProviderIter<'a, I>; + type Item = &'a dyn Provider; + type IntoIter = ProviderIter<'a, S>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -/// An iterator over the providers for services of the given type. No services -/// are activated during iteration of this collection. -pub struct ServiceProviderIter<'a, I> +/// An iterator over the providers for the given service or interface type. +pub struct ProviderIter<'a, S> where - I: ?Sized + Interface, + S: ?Sized + FromProvider, { - providers: IterMut<'a, Box>>, - service_info: ServiceInfo, + inner: ProviderRegistryIter<'a, S::Interface>, } -impl<'a, I> Iterator for ServiceProviderIter<'a, I> +impl<'a, S> Iterator for ProviderIter<'a, S> where - I: ?Sized + Interface, + S: ?Sized + FromProvider, { - type Item = InjectResult<&'a mut dyn Provider>; + type Item = &'a dyn Provider; fn next(&mut self) -> Option { - self.providers.find_map(|provider| { - // Skip providers that don't match the requested service - if provider.result() != self.service_info { + self.inner.find_map(|provider| { + // Skip providers that don't match the filter + if !S::should_provide(provider) { return None; } // Return the provider - Some(Ok(provider.as_mut())) + Some(provider) }) } } - -/// An iterator over the providers for the given interface type. No services -/// are activated during iteration of this collection. -pub struct InterfaceProviderIter<'a, I> -where - I: ?Sized + Interface, -{ - inner: ProviderRegistryIterMut<'a, I>, -} - -impl<'a, I> Iterator for InterfaceProviderIter<'a, I> -where - I: ?Sized + Interface, -{ - type Item = InjectResult<&'a mut dyn Provider>; - - fn next(&mut self) -> Option { - self.inner.next() - } -} - -/// An iterator over the providers for the given service or interface type. -pub enum ProviderIter<'a, I> -where - I: ?Sized + Interface, -{ - /// Iterator over providers for a service type. - Services(ServiceProviderIter<'a, I>), - /// Iterator over providers for an interface type. - Interface(InterfaceProviderIter<'a, I>), -} - -impl<'a, I> Iterator for ProviderIter<'a, I> -where - I: ?Sized + Interface, -{ - type Item = InjectResult<&'a mut dyn Provider>; - - fn next(&mut self) -> Option { - match self { - ProviderIter::Services(inner) => inner.next(), - ProviderIter::Interface(inner) => inner.next(), - } - } -} diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs index 6d3e453..aaea2ed 100644 --- a/crates/runtime_injector/src/iter/services.rs +++ b/crates/runtime_injector/src/iter/services.rs @@ -1,8 +1,7 @@ use crate::{ FromProvider, InjectError, InjectResult, Injector, ProviderIter, Providers, - RequestInfo, ServiceInfo, Svc, + RequestInfo, Svc, }; -use std::marker::PhantomData; /// A collection of all the implementations for a particular service or /// interface. Each service is activated only during iteration of this @@ -54,8 +53,7 @@ where { injector: Injector, request_info: RequestInfo, - providers: Providers, - _marker: PhantomData S>, + providers: Providers, } impl Services @@ -66,13 +64,12 @@ where pub(crate) fn new( injector: Injector, request_info: RequestInfo, - providers: Providers, + providers: Providers, ) -> Self { Services { injector, request_info, providers, - _marker: PhantomData, } } @@ -84,7 +81,6 @@ where injector: &self.injector, request_info: &self.request_info, provider_iter: self.providers.iter(), - _marker: PhantomData, } } @@ -96,7 +92,6 @@ where injector: &self.injector, request_info: &self.request_info, provider_iter: self.providers.iter(), - _marker: PhantomData, } } } @@ -137,8 +132,7 @@ where { injector: &'a Injector, request_info: &'a RequestInfo, - provider_iter: ProviderIter<'a, S::Interface>, - _marker: PhantomData S>, + provider_iter: ProviderIter<'a, S>, } impl<'a, S> Iterator for ServicesIter<'a, S> @@ -149,29 +143,11 @@ where fn next(&mut self) -> Option { self.provider_iter.find_map(|provider| { - let provider = match provider { - Ok(provider) => provider, - Err(error) => return Some(Err(error)), - }; - - // Skip providers that don't match the requested service - if !S::should_provide(provider) { - return None; - } - // Provide the service let service = match provider.provide(self.injector, self.request_info) { Ok(service) => service, Err(InjectError::ConditionsNotMet { .. }) => return None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - return Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })); - } Err(error) => return Some(Err(error)), }; @@ -227,8 +203,7 @@ where { injector: &'a Injector, request_info: &'a RequestInfo, - provider_iter: ProviderIter<'a, S::Interface>, - _marker: PhantomData S>, + provider_iter: ProviderIter<'a, S>, } impl<'a, S> Iterator for OwnedServicesIter<'a, S> @@ -239,30 +214,12 @@ where fn next(&mut self) -> Option { self.provider_iter.find_map(|provider| { - let provider = match provider { - Ok(provider) => provider, - Err(error) => return Some(Err(error)), - }; - - // Skip providers that don't match the requested service - if !S::should_provide(provider) { - return None; - } - // Provide the service let service = match provider .provide_owned(self.injector, self.request_info) { Ok(service) => service, Err(InjectError::ConditionsNotMet { .. }) => return None, - Err(InjectError::CycleDetected { mut cycle, .. }) => { - let service_info = ServiceInfo::of::(); - cycle.push(service_info); - return Some(Err(InjectError::CycleDetected { - service_info, - cycle, - })); - } Err(error) => return Some(Err(error)), }; diff --git a/crates/runtime_injector/src/module.rs b/crates/runtime_injector/src/module.rs index d24214f..b96afec 100644 --- a/crates/runtime_injector/src/module.rs +++ b/crates/runtime_injector/src/module.rs @@ -1,4 +1,4 @@ -use crate::{InterfaceRegistryBuilder, Provider, RequestParameter}; +use crate::{InterfaceRegistryBuilder, Provider, RequestParameter, Svc}; use std::collections::HashMap; /// A collection of providers that can be added all at once to an @@ -10,7 +10,7 @@ use std::collections::HashMap; /// [`define_module!`]. #[derive(Default)] pub struct Module { - pub(crate) registry_builder: InterfaceRegistryBuilder, + pub(crate) registry: InterfaceRegistryBuilder, pub(crate) parameters: HashMap>, } @@ -23,9 +23,9 @@ impl Module { P: Provider, { // Should never panic - self.registry_builder + self.registry .ensure_providers_mut() - .add_provider_for(provider.result(), Box::new(provider)); + .add_provider_for(provider.result(), Svc::new(provider)); } /// Sets the of a value request parameter for requests made by the injector diff --git a/crates/runtime_injector/src/provider_registry.rs b/crates/runtime_injector/src/provider_registry.rs index 455401f..82669e0 100644 --- a/crates/runtime_injector/src/provider_registry.rs +++ b/crates/runtime_injector/src/provider_registry.rs @@ -1,149 +1,43 @@ -use crate::{ - InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, -}; +use crate::{Interface, Provider, Service, ServiceInfo, Svc}; use std::{ - collections::{hash_map::ValuesMut, HashMap}, + collections::{hash_map::Values, HashMap}, fmt::{Debug, Formatter}, - slice::IterMut, + slice::Iter, }; -pub(crate) struct Slot(Option); - -impl Slot { - pub fn take(&mut self) -> Option { - self.0.take() - } - - pub fn replace(&mut self, value: T) -> Option { - self.0.replace(value) - } - - pub fn inner(&self) -> Option<&T> { - self.0.as_ref() - } - - pub fn inner_mut(&mut self) -> Option<&mut T> { - self.0.as_mut() - } - - pub fn with_inner_mut(&mut self, f: F) -> Option - where - F: FnOnce(&mut T) -> R, - { - self.0.as_mut().map(f) - } - - pub fn into_inner(self) -> Option { - self.0 - } -} - -impl Default for Slot -where - T: Default, -{ - fn default() -> Self { - Self(Some(Default::default())) - } -} - -impl From for Slot { - fn from(value: T) -> Self { - Self(Some(value)) - } -} - -impl From> for Slot { - fn from(value: Option) -> Self { - Self(value) - } -} - -pub(crate) type ProviderSlot = Slot>>>; - -impl Debug for ProviderSlot -where - I: ?Sized + Interface, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.inner() { - Some(providers) => f - .debug_tuple("Slot") - .field(&format_args!("<{} provider(s)>", providers.len())) - .finish(), - None => f.debug_tuple("Slot").field(&"").finish(), - } - } -} - -impl Debug for Slot> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Slot").field(&self.0).finish() - } -} - /// Stores providers for a particular interface pub(crate) struct ProviderRegistry where I: ?Sized + Interface, { - providers: HashMap>, + providers: HashMap>>>, } impl ProviderRegistry where I: ?Sized + Interface, { - pub fn new(providers: HashMap>) -> Self { - ProviderRegistry { providers } - } - - /// Gets the providers for a particular service type. - pub fn take_providers_for( + pub fn add_provider_for( &mut self, service_info: ServiceInfo, - ) -> InjectResult>>> { - // Get the provider list slot - let slot = self - .providers - .get_mut(&service_info) - .ok_or_else(|| InjectError::MissingProvider { service_info })?; - - // Ensure the providers are not in use - slot.take().ok_or_else(|| InjectError::CycleDetected { - service_info, - cycle: vec![service_info], - }) + provider: Svc>, + ) { + self.providers + .entry(service_info) + .or_default() + .push(provider); } - /// Reclaims the providers for a particular service type. - pub fn reclaim_providers_for( + pub fn remove_providers_for( &mut self, service_info: ServiceInfo, - providers: Vec>>, - ) -> InjectResult<()> { - // Get the provider list slot - let slot = self.providers.get_mut(&service_info).ok_or_else(|| { - InjectError::InternalError(format!( - "activated provider for {} is no longer registered", - service_info.name() - )) - })?; - - // Insert the providers back into the list, ensuring the list is in use - if slot.replace(providers).is_some() { - Err(InjectError::InternalError(format!( - "another provider for {} was added during its activation", - service_info.name() - ))) - } else { - Ok(()) - } + ) -> Option>>> { + self.providers.remove(&service_info) } - pub fn iter_mut(&mut self) -> ProviderRegistryIterMut<'_, I> { - ProviderRegistryIterMut { - values: self.providers.values_mut(), + pub fn iter(&self) -> ProviderRegistryIter<'_, I> { + ProviderRegistryIter { + values: self.providers.values(), cur_slot: None, } } @@ -154,8 +48,12 @@ where I: ?Sized + Interface, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ProviderRegistry") - .field("providers", &self.providers) + f.debug_map() + .entries( + self.providers.iter().filter(|(_, v)| !v.is_empty()).map( + |(k, v)| (k.name(), format!("<{} providers>", v.len())), + ), + ) .finish() } } @@ -171,47 +69,62 @@ where } } -pub(crate) struct ProviderRegistryIterMut<'a, I> +pub(crate) struct ProviderRegistryIter<'a, I> where I: ?Sized + Interface, { - values: ValuesMut<'a, ServiceInfo, ProviderSlot>, - cur_slot: Option>>>, + values: Values<'a, ServiceInfo, Vec>>>, + cur_slot: Option>>>, } -impl<'a, I> Iterator for ProviderRegistryIterMut<'a, I> +impl<'a, I> Iterator for ProviderRegistryIter<'a, I> where I: ?Sized + Interface, { - type Item = InjectResult<&'a mut dyn Provider>; + type Item = &'a dyn Provider; fn next(&mut self) -> Option { loop { // Try to get next item in current slot if let Some(next) = self.cur_slot.as_mut().and_then(Iterator::next) { - return Some(Ok(next.as_mut())); + return Some(next.as_ref()); } // Try to go to next slot - let slot = self.values.next()?; - let providers = match slot.inner_mut() { - Some(providers) => providers, - None => { - return Some(Err(InjectError::InternalError(format!( - "providers for {:?} are in use", - ServiceInfo::of::().name() - )))) - } - }; - self.cur_slot = Some(providers.iter_mut()); + let next_slot = self.values.next()?; + self.cur_slot = Some(next_slot.iter()); } } } /// Marker trait for provider registries. -pub(crate) trait ProviderRegistryType: Service + Debug {} -impl ProviderRegistryType for ProviderRegistry where I: ?Sized + Interface {} +pub(crate) trait ProviderRegistryType: Service + Debug { + fn merge( + &mut self, + other: Box, + ) -> Result<(), Box>; +} + +impl ProviderRegistryType for ProviderRegistry +where + I: ?Sized + Interface, +{ + fn merge( + &mut self, + other: Box, + ) -> Result<(), Box> { + let other: Box = other.downcast()?; + for (service_info, mut other_providers) in other.providers { + self.providers + .entry(service_info) + .or_default() + .append(&mut other_providers); + } + + Ok(()) + } +} #[cfg(feature = "arc")] downcast_rs::impl_downcast!(sync ProviderRegistryType); @@ -221,148 +134,38 @@ downcast_rs::impl_downcast!(ProviderRegistryType); #[derive(Debug, Default)] pub(crate) struct InterfaceRegistry { - provider_registries: - HashMap>>, + registries: HashMap>, } impl InterfaceRegistry { pub fn new( provider_registries: HashMap< ServiceInfo, - Slot>, + Svc, >, ) -> Self { InterfaceRegistry { - provider_registries, + registries: provider_registries, } } - pub fn take(&mut self) -> InjectResult> + pub fn get_providers(&self) -> Svc> where I: ?Sized + Interface, { + // If a provider registry for the given interface is not found, an + // empty one is returned. This allows requests for interfaces that have + // no providers registered to still work without returning an error. let interface_info = ServiceInfo::of::(); - self.provider_registries - .get_mut(&interface_info) - .ok_or_else(|| InjectError::MissingProvider { - service_info: interface_info, - })? - .take() - .ok_or_else(|| InjectError::CycleDetected { - service_info: interface_info, - cycle: vec![interface_info], - })? - .downcast() - .map_err(|_| { - InjectError::InternalError(format!( - "the provider registry for {:?} is an invalid type", - interface_info.name() - )) - }) - .map(|registry| *registry) - } - - pub fn reclaim( - &mut self, - provider_registry: ProviderRegistry, - ) -> InjectResult<()> - where - I: ?Sized + Interface, - { - // Get the provider registry's slot - let interface_info = ServiceInfo::of::(); - let slot = self - .provider_registries - .get_mut(&interface_info) - .ok_or_else(|| { - InjectError::InternalError(format!( - "activated providers for {} are no longer registered", - interface_info.name() - )) - })?; - - // Put the provider registry into the slot - let replaced = slot.replace(Box::new(provider_registry)); - if let Some(replaced) = replaced { - slot.replace(replaced); - return Err(InjectError::InternalError(format!( - "slot for the provider registry for {:?} has already been reclaimed", - interface_info.name() - ))); - } - - Ok(()) - } - - pub fn take_providers_for( - &mut self, - service_info: ServiceInfo, - ) -> InjectResult>>> - where - I: ?Sized + Interface, - { - // Get provider registry - let interface_info = ServiceInfo::of::(); - let provider_registry = self - .provider_registries - .get_mut(&interface_info) - .ok_or_else(|| InjectError::MissingProvider { service_info })?; - - // Downcast provider list - let provider_registry: &mut ProviderRegistry = provider_registry - .inner_mut() - .ok_or_else(|| InjectError::CycleDetected { - service_info, - cycle: vec![service_info], - })? - .downcast_mut() - .ok_or_else(|| { - InjectError::InternalError(format!( - "provider registry for interface {:?} is the wrong type", - interface_info.name() - )) - })?; - - // Get providers - provider_registry.take_providers_for(service_info) - } - - pub fn reclaim_providers_for( - &mut self, - service_info: ServiceInfo, - providers: Vec>>, - ) -> InjectResult<()> - where - I: ?Sized + Interface, - { - // Get the provider registry - let interface_info = ServiceInfo::of::(); - let slot = self - .provider_registries - .get_mut(&interface_info) - .ok_or_else(|| { - InjectError::InternalError(format!( - "activated provider for {} is no longer registered", - interface_info.name() - )) - })?; - let provider_registry = slot.inner_mut().ok_or_else(|| { - InjectError::InternalError(format!( - "activated provider for {} is in use", - interface_info.name() - )) - })?; - - // Downcast the provider registry - let provider_registry: &mut ProviderRegistry<_> = - provider_registry.downcast_mut().ok_or_else(|| { - InjectError::InternalError(format!( - "provider registry for interface {:?} is the wrong type", - interface_info.name() - )) - })?; - - // Reclaim the providers - provider_registry.reclaim_providers_for(service_info, providers) + let registry = self.registries.get(&interface_info).cloned(); + #[cfg(feature = "arc")] + let registry = registry.map(|registry| { + registry.downcast_arc::>().unwrap() + }); + #[cfg(feature = "rc")] + let registry = registry.map(|registry| { + registry.downcast_rc::>().unwrap() + }); + registry.unwrap_or_default() } } diff --git a/crates/runtime_injector/src/providers/conditional.rs b/crates/runtime_injector/src/providers/conditional.rs index 86dbd70..3e6eeb9 100644 --- a/crates/runtime_injector/src/providers/conditional.rs +++ b/crates/runtime_injector/src/providers/conditional.rs @@ -27,7 +27,7 @@ where #[inline] fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { @@ -42,7 +42,7 @@ where #[inline] fn provide_owned_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { diff --git a/crates/runtime_injector/src/providers/constant.rs b/crates/runtime_injector/src/providers/constant.rs index a3d10f3..ea1f052 100644 --- a/crates/runtime_injector/src/providers/constant.rs +++ b/crates/runtime_injector/src/providers/constant.rs @@ -31,7 +31,7 @@ where type Result = R; fn provide_typed( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult> { diff --git a/crates/runtime_injector/src/providers/func.rs b/crates/runtime_injector/src/providers/func.rs index fd86673..61f75b1 100644 --- a/crates/runtime_injector/src/providers/func.rs +++ b/crates/runtime_injector/src/providers/func.rs @@ -1,4 +1,4 @@ -use crate::{InjectResult, Injector, Request, RequestInfo, ServiceInfo}; +use crate::{InjectResult, Injector, Request, RequestInfo}; use std::any::Any; /// A factory for creating instances of a service. All functions of arity 12 or @@ -59,9 +59,8 @@ macro_rules! impl_provider_function { injector: &Injector, request_info: &RequestInfo ) -> InjectResult { - let request_info = request_info.with_request(ServiceInfo::of::()); let result = self($( - match <$type_name as Request>::request(&injector, &request_info) { + match <$type_name as Request>::request(&injector, request_info) { Ok(dependency) => dependency, Err($crate::InjectError::MissingProvider { service_info }) => { return Err($crate::InjectError::MissingDependency { diff --git a/crates/runtime_injector/src/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs index 3d33692..44d0f13 100644 --- a/crates/runtime_injector/src/providers/interface.rs +++ b/crates/runtime_injector/src/providers/interface.rs @@ -23,7 +23,7 @@ where type Result = P::Result; fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { @@ -31,7 +31,7 @@ where } fn provide_owned_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { diff --git a/crates/runtime_injector/src/providers/providers.rs b/crates/runtime_injector/src/providers/providers.rs index db1400b..672d5ec 100644 --- a/crates/runtime_injector/src/providers/providers.rs +++ b/crates/runtime_injector/src/providers/providers.rs @@ -18,14 +18,14 @@ pub trait Provider: Service { /// Provides an instance of the service. fn provide( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult>; /// Provides an owned instance of the service. fn provide_owned( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult> { @@ -61,7 +61,7 @@ pub trait Provider: Service { /// type Result = Foo; /// /// fn provide_typed( -/// &mut self, +/// &self, /// _injector: &Injector, /// _request_info: &RequestInfo, /// ) -> InjectResult> { @@ -87,7 +87,7 @@ pub trait TypedProvider: /// Provides an instance of the service. The [`Injector`] passed in can be /// used to retrieve instances of any dependencies this service has. fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult>; @@ -95,7 +95,7 @@ pub trait TypedProvider: /// Provides an owned instance of the service. Not all providers can /// provide an owned variant of the service. fn provide_owned_typed( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult> { @@ -116,7 +116,7 @@ where } fn provide( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { @@ -125,7 +125,7 @@ where } fn provide_owned( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { diff --git a/crates/runtime_injector/src/providers/singleton.rs b/crates/runtime_injector/src/providers/singleton.rs index 6aff9a7..bcbe033 100644 --- a/crates/runtime_injector/src/providers/singleton.rs +++ b/crates/runtime_injector/src/providers/singleton.rs @@ -1,6 +1,6 @@ use crate::{ - InjectResult, Injector, RequestInfo, Service, ServiceFactory, Svc, - TypedProvider, + InjectResult, Injector, RequestInfo, Service, ServiceFactory, ServiceInfo, + Svc, TypedProvider, }; use std::marker::PhantomData; @@ -13,7 +13,10 @@ where F: ServiceFactory, { factory: F, - result: Option>, + #[cfg(feature = "arc")] + result: std::sync::RwLock>>, + #[cfg(feature = "rc")] + result: std::cell::RefCell>>, marker: PhantomData R>, } @@ -27,7 +30,7 @@ where pub fn new(func: F) -> Self { SingletonProvider { factory: func, - result: None, + result: Default::default(), marker: PhantomData, } } @@ -43,17 +46,46 @@ where type Result = R; fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { - if let Some(ref service) = self.result { - return Ok(service.clone()); - } + let request_info = + request_info.with_request(ServiceInfo::of::())?; + + #[cfg(feature = "arc")] + let result = { + // Check if already stored - fast path + let stored = self.result.read().unwrap(); + if let Some(result) = stored.as_ref() { + return Ok(result.clone()); + } + drop(stored); - let result = self.factory.invoke(injector, request_info)?; - let result = Svc::new(result); - self.result = Some(result.clone()); + // Create new service if needed - slow path + let mut stored = self.result.write().unwrap(); + match &mut *stored { + Some(stored) => return Ok(stored.clone()), + stored @ None => { + let result = + self.factory.invoke(injector, &request_info)?; + stored.insert(Svc::new(result)).clone() + } + } + }; + #[cfg(feature = "rc")] + let result = { + // Create new service if needed + let mut stored = self.result.borrow_mut(); + match &mut *stored { + Some(stored) => return Ok(stored.clone()), + stored @ None => { + let result = + self.factory.invoke(injector, &request_info)?; + stored.insert(Svc::new(result)).clone() + } + } + }; Ok(result) } } diff --git a/crates/runtime_injector/src/providers/transient.rs b/crates/runtime_injector/src/providers/transient.rs index bb37f57..622f8c3 100644 --- a/crates/runtime_injector/src/providers/transient.rs +++ b/crates/runtime_injector/src/providers/transient.rs @@ -1,6 +1,6 @@ use crate::{ - InjectResult, Injector, RequestInfo, Service, ServiceFactory, Svc, - TypedProvider, + InjectResult, Injector, RequestInfo, Service, ServiceFactory, ServiceInfo, + Svc, TypedProvider, }; use std::marker::PhantomData; @@ -41,20 +41,24 @@ where type Result = R; fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { - let result = self.factory.invoke(injector, request_info)?; + let request_info = + request_info.with_request(ServiceInfo::of::())?; + let result = self.factory.invoke(injector, &request_info)?; Ok(Svc::new(result)) } fn provide_owned_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { - let result = self.factory.invoke(injector, request_info)?; + let request_info = + request_info.with_request(ServiceInfo::of::())?; + let result = self.factory.invoke(injector, &request_info)?; Ok(Box::new(result)) } } diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index ebf5f27..75efed0 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -1,4 +1,4 @@ -use crate::{RequestParameter, ServiceInfo}; +use crate::{InjectError, InjectResult, RequestParameter, ServiceInfo}; use std::{ collections::HashMap, fmt::{Debug, Formatter}, @@ -13,7 +13,6 @@ pub struct RequestInfo { impl RequestInfo { /// Creates a new, empty instance of [`RequestInfo`]. - #[must_use] pub fn new() -> Self { RequestInfo { service_path: Vec::new(), @@ -22,12 +21,26 @@ impl RequestInfo { } /// Creates a new child instance of [`RequestInfo`] with the given service - /// appended to the end of the request path. - #[must_use] - pub fn with_request(&self, service: ServiceInfo) -> Self { + /// appended to the end of the request path. If a cycle is detected, an + /// error is returned instead. + pub fn with_request( + &self, + service_info: ServiceInfo, + ) -> InjectResult { + // Check for cycles + if self.service_path.contains(&service_info) { + let mut cycle = self.service_path.clone(); + cycle.push(service_info); + return Err(InjectError::CycleDetected { + service_info, + cycle, + }); + } + + // Create child request info let mut child = self.clone(); - child.service_path.push(service); - child + child.service_path.push(service_info); + Ok(child) } /// Gets the current request path. This can be used to configure a service @@ -66,6 +79,7 @@ impl RequestInfo { /// let foo: Svc = injector.get().unwrap(); /// let bar: Svc = injector.get().unwrap(); /// let baz: Svc = injector.get().unwrap(); + // rustfmt doesn't properly format `x.0.0` in doc comments #[rustfmt::skip] /// assert_eq!(1, foo.0.0); /// assert_eq!(2, bar.0.0); diff --git a/crates/runtime_injector/src/requests/request.rs b/crates/runtime_injector/src/requests/request.rs index 16f9484..b3b4971 100644 --- a/crates/runtime_injector/src/requests/request.rs +++ b/crates/runtime_injector/src/requests/request.rs @@ -1,5 +1,5 @@ use crate::{ - FromProvider, InjectError, InjectResult, Injector, RequestInfo, + FromProvider, InjectError, InjectResult, Injector, Providers, RequestInfo, ServiceInfo, Services, Svc, }; @@ -132,11 +132,19 @@ impl Request for Box { } } +/// Requests all the providers of a service. +impl Request for Providers { + fn request(injector: &Injector, _info: &RequestInfo) -> InjectResult { + Ok(injector.get_providers()) + } +} + /// Lazily requests all the implementations of a service. impl Request for Services { #[inline] fn request(injector: &Injector, info: &RequestInfo) -> InjectResult { - injector.get_all(info) + let providers: Providers = injector.get_with(info)?; + Ok(Services::new(injector.clone(), info.clone(), providers)) } } diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index 44871c9..0a5e766 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -76,9 +76,6 @@ macro_rules! interface { impl $crate::FromProvider for dyn $interface { type Interface = Self; - const PROVIDER_TYPE: $crate::ProviderType = - $crate::ProviderType::Interface; - fn should_provide( _provider: &dyn $crate::Provider, ) -> bool { diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index 6196e24..20a890f 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -214,8 +214,15 @@ impl Display for InjectError { } InjectError::MissingDependency { service_info, - .. - } => write!(f, "{} is missing a dependency", service_info.name()), + dependency_info, + } => { + write!( + f, + "{} has no provider (required by {})", + dependency_info.name(), + service_info.name() + ) + }, InjectError::CycleDetected { service_info, cycle, From ffd01c296170c8ec4ec53359468fe33bee83fa22 Mon Sep 17 00:00:00 2001 From: TehPers Date: Sun, 17 Apr 2022 15:37:14 -0700 Subject: [PATCH 14/26] Fix clippy lints --- crates/runtime_injector/src/builder.rs | 10 +++------- crates/runtime_injector/src/injector.rs | 1 + crates/runtime_injector/src/iter/providers.rs | 10 +--------- crates/runtime_injector/src/lib.rs | 1 + crates/runtime_injector/src/provider_registry.rs | 2 +- crates/runtime_injector/src/providers/singleton.rs | 5 ++++- crates/runtime_injector/src/requests/info.rs | 1 + 7 files changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/runtime_injector/src/builder.rs b/crates/runtime_injector/src/builder.rs index bb047d0..c8886ea 100644 --- a/crates/runtime_injector/src/builder.rs +++ b/crates/runtime_injector/src/builder.rs @@ -22,7 +22,7 @@ impl InjectorBuilder { where P: Provider, { - self.add_provider(Svc::new(provider)) + self.add_provider(Svc::new(provider)); } /// Adds a provider to the injector. @@ -120,11 +120,7 @@ impl InterfaceRegistryBuilder { { self.registries .entry(ServiceInfo::of::()) - .or_insert_with(|| { - (Box::new(ProviderRegistry::::default()) - as Box) - .into() - }) + .or_insert_with(|| Box::new(ProviderRegistry::::default())) .downcast_mut() .unwrap() } @@ -163,7 +159,7 @@ impl InterfaceRegistryBuilder { entry.into_mut().merge(other_providers).unwrap(); } Entry::Vacant(entry) => { - entry.insert(other_providers.into()); + entry.insert(other_providers); } } } diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index 469fcc8..6f1eb28 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -237,6 +237,7 @@ impl Injector { } #[doc(hidden)] + #[must_use] pub fn get_providers(&self) -> Providers { Providers::new(self.interface_registry.get_providers()) } diff --git a/crates/runtime_injector/src/iter/providers.rs b/crates/runtime_injector/src/iter/providers.rs index ecf6b67..d15656d 100644 --- a/crates/runtime_injector/src/iter/providers.rs +++ b/crates/runtime_injector/src/iter/providers.rs @@ -87,14 +87,6 @@ where type Item = &'a dyn Provider; fn next(&mut self) -> Option { - self.inner.find_map(|provider| { - // Skip providers that don't match the filter - if !S::should_provide(provider) { - return None; - } - - // Return the provider - Some(provider) - }) + self.inner.find(|&provider| S::should_provide(provider)) } } diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 7845add..d9bb77b 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -226,6 +226,7 @@ #![warn(missing_docs)] #![allow( clippy::module_name_repetitions, + clippy::module_inception, clippy::missing_errors_doc, clippy::doc_markdown, clippy::needless_doctest_main, diff --git a/crates/runtime_injector/src/provider_registry.rs b/crates/runtime_injector/src/provider_registry.rs index 82669e0..c67f20b 100644 --- a/crates/runtime_injector/src/provider_registry.rs +++ b/crates/runtime_injector/src/provider_registry.rs @@ -64,7 +64,7 @@ where { fn default() -> Self { Self { - providers: Default::default(), + providers: HashMap::default(), } } } diff --git a/crates/runtime_injector/src/providers/singleton.rs b/crates/runtime_injector/src/providers/singleton.rs index bcbe033..a4b8562 100644 --- a/crates/runtime_injector/src/providers/singleton.rs +++ b/crates/runtime_injector/src/providers/singleton.rs @@ -30,7 +30,10 @@ where pub fn new(func: F) -> Self { SingletonProvider { factory: func, - result: Default::default(), + #[cfg(feature = "arc")] + result: std::sync::RwLock::default(), + #[cfg(feature = "rc")] + result: std::cell::RefCell::default(), marker: PhantomData, } } diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index 75efed0..173b4f4 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -13,6 +13,7 @@ pub struct RequestInfo { impl RequestInfo { /// Creates a new, empty instance of [`RequestInfo`]. + #[must_use] pub fn new() -> Self { RequestInfo { service_path: Vec::new(), From d6f82cc773702bdb50e1113042dcd03f14336282 Mon Sep 17 00:00:00 2001 From: TehPers Date: Sun, 17 Apr 2022 19:11:57 -0700 Subject: [PATCH 15/26] Fix more clippy errors --- crates/runtime_injector/src/iter/services.rs | 5 +++-- crates/runtime_injector/src/providers/fallible.rs | 2 +- crates/runtime_injector/src/requests/info.rs | 6 ++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs index aaea2ed..d148bf0 100644 --- a/crates/runtime_injector/src/iter/services.rs +++ b/crates/runtime_injector/src/iter/services.rs @@ -97,7 +97,8 @@ where } /// An iterator over the provided services of the given type. Each service is -/// activated on demand. +/// activated on demand. Because activation of a service may fail, this +/// iterator returns [`InjectResult`]. /// /// ``` /// use runtime_injector::{constant, Injector, IntoTransient, Services, Svc}; @@ -264,7 +265,7 @@ mod tests { assert!(!initialized.load(Ordering::Relaxed)); // Check that it is initialized after iteration - let _: Svc = services.iter().next().unwrap().unwrap(); + let _next = services.iter().next().unwrap().unwrap(); assert!(initialized.load(Ordering::Relaxed)); } } diff --git a/crates/runtime_injector/src/providers/fallible.rs b/crates/runtime_injector/src/providers/fallible.rs index d934836..660a8b4 100644 --- a/crates/runtime_injector/src/providers/fallible.rs +++ b/crates/runtime_injector/src/providers/fallible.rs @@ -154,7 +154,7 @@ mod tests { builder.provide(constant(true)); let injector = builder.build(); - let _: Svc = injector.get().unwrap(); + let _foo: Svc = injector.get().unwrap(); } /// A value is not returned if the service factory fails. diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index 173b4f4..b7d4950 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -152,14 +152,12 @@ mod tests { assert_eq!( Some(&"bar".to_string()), info.get_parameter("foo") - .map(|p| p.downcast_ref::()) - .flatten() + .and_then(::downcast_ref) ); assert_eq!( Some(Box::new("bar".to_string())), info.remove_parameter("foo") - .map(|p| p.downcast::().ok()) - .flatten() + .and_then(|p| p.downcast::().ok()) ); assert!(info.get_parameter("foo").is_none()); } From 7b36c2bfd3cede1defae88d2b4eca3ea708349cb Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 26 Apr 2022 00:38:25 -0700 Subject: [PATCH 16/26] Allow TypedProvider to have interface result type --- .../runtime_injector/src/iter/from_provider.rs | 16 +++++++++------- crates/runtime_injector/src/iter/services.rs | 4 ++-- .../src/providers/interface.rs | 12 ++++++++---- .../src/providers/providers.rs | 2 +- .../runtime_injector/src/services/interface.rs | 18 +++++++++++++++--- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/crates/runtime_injector/src/iter/from_provider.rs b/crates/runtime_injector/src/iter/from_provider.rs index 5edbd9f..205a287 100644 --- a/crates/runtime_injector/src/iter/from_provider.rs +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -1,11 +1,12 @@ use crate::{ - InjectError, InjectResult, Interface, Provider, Service, ServiceInfo, Svc, + InjectError, InjectResult, InterfaceFor, Provider, Service, ServiceInfo, + Svc, }; /// A type that can be requested from a provider. pub trait FromProvider: Service { /// The interface to request providers for. - type Interface: ?Sized + Interface; + type Interface: ?Sized + InterfaceFor; /// Whether the given provider is valid for this type. fn should_provide( @@ -13,11 +14,12 @@ pub trait FromProvider: Service { ) -> bool; /// Converts a provided service into a service pointer of this type. - fn from_provided(provided: Svc) - -> InjectResult>; + fn from_interface( + provided: Svc, + ) -> InjectResult>; /// Converts a provided service into an owned service pointer of this type. - fn from_provided_owned( + fn from_interface_owned( provided: Box, ) -> InjectResult>; } @@ -32,7 +34,7 @@ impl FromProvider for S { } #[inline] - fn from_provided( + fn from_interface( provided: Svc, ) -> InjectResult> { #[cfg(feature = "arc")] @@ -51,7 +53,7 @@ impl FromProvider for S { } #[inline] - fn from_provided_owned( + fn from_interface_owned( provided: Box, ) -> InjectResult> { provided diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs index d148bf0..d148af0 100644 --- a/crates/runtime_injector/src/iter/services.rs +++ b/crates/runtime_injector/src/iter/services.rs @@ -153,7 +153,7 @@ where }; // Downcast the service - let service = S::from_provided(service); + let service = S::from_interface(service); Some(service) }) } @@ -225,7 +225,7 @@ where }; // Downcast the service - let service = S::from_provided_owned(service); + let service = S::from_interface_owned(service); Some(service) }) } diff --git a/crates/runtime_injector/src/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs index 44d0f13..8546b1e 100644 --- a/crates/runtime_injector/src/providers/interface.rs +++ b/crates/runtime_injector/src/providers/interface.rs @@ -17,17 +17,19 @@ where impl TypedProvider for InterfaceProvider where P: TypedProvider, - I: ?Sized + InterfaceFor, + I: ?Sized + InterfaceFor + InterfaceFor, { type Interface = I; - type Result = P::Result; + type Result = I; fn provide_typed( &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { - self.inner.provide_typed(injector, request_info) + self.inner + .provide_typed(injector, request_info) + .map(I::from_svc) } fn provide_owned_typed( @@ -35,7 +37,9 @@ where injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { - self.inner.provide_owned_typed(injector, request_info) + self.inner + .provide_owned_typed(injector, request_info) + .map(I::from_owned_svc) } } diff --git a/crates/runtime_injector/src/providers/providers.rs b/crates/runtime_injector/src/providers/providers.rs index 672d5ec..ff1644d 100644 --- a/crates/runtime_injector/src/providers/providers.rs +++ b/crates/runtime_injector/src/providers/providers.rs @@ -82,7 +82,7 @@ pub trait TypedProvider: type Interface: ?Sized + InterfaceFor; /// The type of service this can provide. - type Result: Service; + type Result: ?Sized + Service; /// Provides an instance of the service. The [`Injector`] passed in can be /// used to retrieve instances of any dependencies this service has. diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index 0a5e766..aa4b1d1 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -11,7 +11,7 @@ pub trait Interface: Service {} /// implementations for interfaces. pub trait InterfaceFor: Interface where - S: Service, + S: ?Sized + Service, { #[doc(hidden)] fn from_svc(service: Svc) -> Svc; @@ -61,6 +61,18 @@ macro_rules! interface { ($interface:tt) => { impl $crate::Interface for dyn $interface {} + impl $crate::InterfaceFor for dyn $interface { + fn from_svc(service: $crate::Svc) -> $crate::Svc { + service + } + + fn from_owned_svc( + service: ::std::boxed::Box, + ) -> ::std::boxed::Box { + service + } + } + impl $crate::InterfaceFor for dyn $interface { fn from_svc(service: $crate::Svc) -> $crate::Svc { service @@ -82,13 +94,13 @@ macro_rules! interface { true } - fn from_provided( + fn from_interface( provided: $crate::Svc, ) -> $crate::InjectResult<$crate::Svc> { ::std::result::Result::Ok(provided) } - fn from_provided_owned( + fn from_interface_owned( provided: ::std::boxed::Box, ) -> $crate::InjectResult<::std::boxed::Box> { ::std::result::Result::Ok(provided) From ce96d1a60729c3cb944b7095ad0f7fb56bb8a3f0 Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 26 Apr 2022 00:39:08 -0700 Subject: [PATCH 17/26] Update CI to actions/checkout@v3 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 358c77f..b38821e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup toolchain uses: dtolnay/rust-toolchain@stable with: @@ -41,7 +41,7 @@ jobs: features: rc steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup toolchain uses: dtolnay/rust-toolchain@stable with: @@ -66,7 +66,7 @@ jobs: - package: runtime_injector_actix features: rc steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@clippy - run: >- cargo clippy From 696f6b21684425b3a13d96b6c9d645988bb19797 Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 26 Apr 2022 12:06:10 -0700 Subject: [PATCH 18/26] Update with_arg() to be on provider/factory --- crates/runtime_injector/src/module.rs | 21 +- crates/runtime_injector/src/providers.rs | 2 + .../src/{requests => providers}/arg.rs | 229 ++++++++++++------ .../src/providers/fallible.rs | 6 +- crates/runtime_injector/src/requests.rs | 2 - .../runtime_injector/src/requests/factory.rs | 63 +++-- crates/runtime_injector/src/requests/info.rs | 2 - .../runtime_injector/src/services/service.rs | 31 ++- crates/runtime_injector/src/tests.rs | 2 +- crates/runtime_injector_actix/src/service.rs | 2 +- rustfmt.toml | 1 + 11 files changed, 227 insertions(+), 134 deletions(-) rename crates/runtime_injector/src/{requests => providers}/arg.rs (55%) diff --git a/crates/runtime_injector/src/module.rs b/crates/runtime_injector/src/module.rs index b96afec..5286c5d 100644 --- a/crates/runtime_injector/src/module.rs +++ b/crates/runtime_injector/src/module.rs @@ -55,7 +55,7 @@ impl Module { /// ``` /// use runtime_injector::{ /// define_module, interface, Arg, Injector, IntoSingleton, IntoTransient, -/// Service, Svc, +/// Service, Svc, WithArg, /// }; /// /// struct Foo(Arg); @@ -75,13 +75,10 @@ impl Module { /// ], /// interfaces = { /// dyn Fooable = [ -/// Foo.singleton(), +/// Foo.singleton().with_arg(12i32), /// Bar.singleton(), /// ], /// }, -/// arguments = { -/// Foo = [12i32], -/// }, /// /// // If there are multiple interface or service definitions, they are /// // merged together. This means we can have providers registered only in @@ -147,18 +144,4 @@ macro_rules! define_module { $($module.provide($crate::WithInterface::with_interface::<$interface>($implementation));)* )* }; - ( - @provide $module:expr, - arguments = { - $($service:ty = [ - $($arg:expr),* - $(,)? - ]),* - $(,)? - } - ) => { - $( - $($crate::WithArg::with_arg::<$service, _>($module, $arg);)* - )* - }; } diff --git a/crates/runtime_injector/src/providers.rs b/crates/runtime_injector/src/providers.rs index 522390d..71a3b17 100644 --- a/crates/runtime_injector/src/providers.rs +++ b/crates/runtime_injector/src/providers.rs @@ -1,3 +1,4 @@ +mod arg; mod conditional; mod constant; mod fallible; @@ -7,6 +8,7 @@ mod providers; mod singleton; mod transient; +pub use arg::*; pub use conditional::*; pub use constant::*; pub use fallible::*; diff --git a/crates/runtime_injector/src/requests/arg.rs b/crates/runtime_injector/src/providers/arg.rs similarity index 55% rename from crates/runtime_injector/src/requests/arg.rs rename to crates/runtime_injector/src/providers/arg.rs index 87fd876..66427e1 100644 --- a/crates/runtime_injector/src/requests/arg.rs +++ b/crates/runtime_injector/src/providers/arg.rs @@ -1,35 +1,23 @@ use crate::{ - InjectError, InjectResult, Injector, InjectorBuilder, Module, Request, - RequestInfo, RequestParameter, Service, ServiceInfo, + InjectError, InjectResult, Injector, Request, RequestInfo, Service, + ServiceInfo, TypedProvider, }; use std::{ error::Error, - fmt::{Debug, Display, Formatter}, + fmt::{Display, Formatter}, ops::{Deref, DerefMut}, }; /// Allows custom pre-defined values to be passed as arguments to services. /// -/// ## Example -/// -/// ``` -/// use runtime_injector::{Arg, Injector, IntoTransient, WithArg}; -/// -/// struct Foo(Arg); -/// -/// let mut builder = Injector::builder(); -/// builder.provide(Foo.transient()); -/// builder.with_arg::(12); -/// -/// let injector = builder.build(); -/// let foo: Box = injector.get().unwrap(); -/// assert_eq!(12, *foo.0); -/// ``` -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +/// See [WithArg::with_arg()]. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] pub struct Arg(T); impl Arg { - pub(crate) fn param_name(target: ServiceInfo) -> String { + /// Gets the parameter name of an [`Arg`] requested by a particular + /// service. + pub fn param_name(target: ServiceInfo) -> String { format!( "runtime_injector::Arg[target={:?},type={:?}]", target.id(), @@ -122,58 +110,127 @@ impl Display for ArgRequestError { } } -/// Allows defining pre-defined arguments to services. -pub trait WithArg { - /// Adds an argument for a service. See the docs for [`Arg`]. - fn with_arg( - &mut self, - value: T, - ) -> Option>; +/// A service provider that attaches an argument to a service. This can be used +/// to pass custom values to service factories. See [`WithArg::with_arg()`]. +pub struct ArgProvider +where + P: TypedProvider, + P::Result: Sized, + T: Service + Clone, +{ + inner: P, + arg: T, } -impl WithArg for RequestInfo { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.insert_parameter( - &Arg::::param_name(ServiceInfo::of::()), - value, - ) +impl TypedProvider for ArgProvider +where + P: TypedProvider, + P::Result: Sized, + T: Service + Clone, +{ + type Interface =

::Interface; + type Result = P::Result; + + fn provide_typed( + &self, + injector: &crate::Injector, + request_info: &crate::RequestInfo, + ) -> crate::InjectResult> { + let mut request_info = request_info.clone(); + let _ = request_info.insert_parameter( + &Arg::::param_name(ServiceInfo::of::()), + self.arg.clone(), + ); + self.inner.provide_typed(injector, &request_info) } -} -impl WithArg for InjectorBuilder { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.root_info_mut().with_arg::(value) + fn provide_owned_typed( + &self, + injector: &crate::Injector, + request_info: &crate::RequestInfo, + ) -> crate::InjectResult> { + let mut request_info = request_info.clone(); + let _ = request_info.insert_parameter( + &Arg::::param_name(ServiceInfo::of::()), + self.arg.clone(), + ); + self.inner.provide_owned_typed(injector, &request_info) } } -impl WithArg for Module { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.insert_parameter( - &Arg::::param_name(ServiceInfo::of::()), - value, - ) +/// Allows defining pre-defined arguments to services. +pub trait WithArg: TypedProvider +where + Self::Result: Sized, +{ + /// Adds an argument for a service. + /// + /// ## Example + /// + /// ``` + /// use runtime_injector::{Arg, Injector, IntoSingleton, Svc, WithArg}; + /// + /// struct DatabaseConnection { + /// connection_string: String, + /// } + /// + /// impl DatabaseConnection { + /// fn new(connection_string: Arg) -> Self { + /// Self { + /// connection_string: Arg::into_inner(connection_string), + /// } + /// } + /// } + /// + /// let mut builder = Injector::builder(); + /// builder.provide( + /// DatabaseConnection::new + /// .singleton() + /// .with_arg("".to_string()), + /// ); + /// + /// let injector = builder.build(); + /// let db: Svc = injector.get().unwrap(); + /// assert_eq!("", db.connection_string); + /// ``` + fn with_arg(self, arg: T) -> ArgProvider + where + T: Service + Clone; +} + +impl

WithArg for P +where + P: TypedProvider, + P::Result: Sized, +{ + fn with_arg(self, arg: T) -> ArgProvider + where + T: Service + Clone, + { + ArgProvider { inner: self, arg } } } #[cfg(test)] mod tests { use crate::{ - define_module, Arg, ArgRequestError, InjectError, Injector, - IntoSingleton, ServiceInfo, Svc, + define_module, interface, Arg, ArgRequestError, InjectError, Injector, + IntoSingleton, Service, ServiceInfo, Svc, WithArg, WithInterface, }; - #[derive(Debug)] + #[derive(Debug, Default)] struct Foo(Arg); + trait Fooable: Service { + fn value(&self) -> i32; + } + interface!(Fooable); + impl Fooable for Foo { + fn value(&self) -> i32 { + self.0.0 + } + } + #[test] fn request_fails_if_missing_arg() { // Create a module with a single service. @@ -230,17 +287,8 @@ mod tests { #[test] fn request_fails_if_arg_is_wrong_type() { - struct Foo(Arg); - - let module = define_module! { - services = [Foo.singleton()], - arguments = { - Foo = [42u32], - }, - }; - let mut builder = Injector::builder(); - builder.add_module(module); + builder.provide(Foo.singleton().with_arg(42u32)); let injector = builder.build(); match injector.get::>() { @@ -263,20 +311,51 @@ mod tests { #[test] fn request_succeeds_if_arg_is_correct_type() { - struct Foo(Arg); + let mut builder = Injector::builder(); + builder.provide(Foo.singleton().with_arg(42i32)); - let module = define_module! { - services = [Foo.singleton()], - arguments = { - Foo = [42i32], - }, - }; + let injector = builder.build(); + let foo = injector.get::>().unwrap(); + assert_eq!(42, foo.value()); + } + #[test] + fn request_succeeds_with_interface_provider() { let mut builder = Injector::builder(); - builder.add_module(module); + builder.provide( + Foo.singleton() + .with_arg(42i32) + .with_interface::(), + ); let injector = builder.build(); - let foo = injector.get::>().unwrap(); - assert_eq!(42, foo.0 .0); + let foo = injector.get::>().unwrap(); + assert_eq!(42, foo.value()); + } + + #[test] + fn request_succeeds_with_multiple_providers() { + let mut builder = Injector::builder(); + builder.provide(Foo.singleton().with_arg(1i32)); + builder.provide(Foo.singleton().with_arg(2i32)); + + let injector = builder.build(); + let foos = injector.get::>>().unwrap(); + assert_eq!(2, foos.len()); + assert!(foos.iter().any(|foo| foo.value() == 1)); + assert!(foos.iter().any(|foo| foo.value() == 2)); + } + + #[test] + fn request_succeeds_with_multiple_args() { + struct Bar(Arg, Arg<&'static str>); + + let mut builder = Injector::builder(); + builder.provide(Bar.singleton().with_arg(1i32).with_arg("foo")); + + let injector = builder.build(); + let bar = injector.get::>().unwrap(); + assert_eq!(1, bar.0.0); + assert_eq!("foo", bar.1.0); } } diff --git a/crates/runtime_injector/src/providers/fallible.rs b/crates/runtime_injector/src/providers/fallible.rs index 660a8b4..73a9a51 100644 --- a/crates/runtime_injector/src/providers/fallible.rs +++ b/crates/runtime_injector/src/providers/fallible.rs @@ -139,11 +139,7 @@ mod tests { struct Foo; fn make_foo(succeed: Svc) -> Result { - if *succeed { - Ok(Foo) - } else { - Err(FooError) - } + if *succeed { Ok(Foo) } else { Err(FooError) } } /// A value is returned if the service factory succeeds. diff --git a/crates/runtime_injector/src/requests.rs b/crates/runtime_injector/src/requests.rs index bfbab42..cf4e5f9 100644 --- a/crates/runtime_injector/src/requests.rs +++ b/crates/runtime_injector/src/requests.rs @@ -1,10 +1,8 @@ -mod arg; mod factory; mod info; mod parameter; mod request; -pub use arg::*; pub use factory::*; pub use info::*; pub use parameter::*; diff --git a/crates/runtime_injector/src/requests/factory.rs b/crates/runtime_injector/src/requests/factory.rs index eb90dfd..f6286cc 100644 --- a/crates/runtime_injector/src/requests/factory.rs +++ b/crates/runtime_injector/src/requests/factory.rs @@ -1,4 +1,6 @@ -use crate::{InjectResult, Injector, Request, RequestInfo}; +use crate::{ + Arg, InjectResult, Injector, Request, RequestInfo, Service, ServiceInfo, +}; use std::marker::PhantomData; /// Lazy request factory allowing requests to be made outside of service @@ -49,7 +51,10 @@ impl Clone for Factory { } } -impl Factory { +impl Factory +where + R: Request, +{ /// Performs the factory's inner request. pub fn get(&self) -> InjectResult { R::request(&self.injector, &self.request_info) @@ -65,11 +70,17 @@ impl Factory { /// Mutably gets this factory's inner [`RequestInfo`]. This request info is /// used by all requests the factory makes. /// - /// Modifying this request info affects future requests the factory makes, - /// meaning additional arguments can be added to requests prior to them - /// being executed. Since the factory can be cloned, requests can be - /// specialized by first cloning the factory, then modifying the - /// [`RequestInfo`] on the clone and using it to make the request instead. + /// Modifying this request info affects future requests the factory makes. + /// Since the factory can be cloned, requests can be specialized by first + /// cloning the factory, then modifying the [`RequestInfo`] on the clone + /// and using it to make the request instead. + #[must_use] + pub fn request_info_mut(&mut self) -> &mut RequestInfo { + &mut self.request_info + } + + /// Creates a new [`Factory`] that injects an [`Arg`] for a particular + /// service. /// /// ## Example /// @@ -81,30 +92,48 @@ impl Factory { /// /// struct Foo(Arg); /// - /// struct Bar(Factory>); - /// impl Bar { + /// struct FooFactory { + /// factory: Factory>, + /// } + /// + /// impl FooFactory { + /// fn new(factory: Factory>) -> Self { + /// FooFactory { factory } + /// } + /// /// fn get_foo(&self, arg: i32) -> InjectResult> { - /// let mut factory = self.0.clone(); - /// factory.request_info_mut().with_arg::(arg); - /// factory.get() + /// self.factory.with_arg::(arg).get() /// } /// } /// /// let mut builder = Injector::builder(); /// builder.provide(Foo.transient()); - /// builder.provide(Bar.singleton()); + /// builder.provide(FooFactory::new.singleton()); /// /// let injector = builder.build(); - /// let bar: Svc = injector.get().unwrap(); + /// let bar: Svc = injector.get().unwrap(); /// let foo1 = bar.get_foo(1).unwrap(); /// let foo2 = bar.get_foo(2).unwrap(); /// /// assert_eq!(1, *foo1.0); /// assert_eq!(2, *foo2.0); /// ``` - #[must_use] - pub fn request_info_mut(&mut self) -> &mut RequestInfo { - &mut self.request_info + pub fn with_arg(&self, value: T) -> Factory + where + S: Service, + T: Service + Clone, + { + let mut request_info = self.request_info.clone(); + let _ = request_info.insert_parameter( + &Arg::::param_name(ServiceInfo::of::()), + value, + ); + + Factory { + injector: self.injector.clone(), + request_info, + marker: PhantomData, + } } } diff --git a/crates/runtime_injector/src/requests/info.rs b/crates/runtime_injector/src/requests/info.rs index b7d4950..16cbd85 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -80,8 +80,6 @@ impl RequestInfo { /// let foo: Svc = injector.get().unwrap(); /// let bar: Svc = injector.get().unwrap(); /// let baz: Svc = injector.get().unwrap(); - // rustfmt doesn't properly format `x.0.0` in doc comments - #[rustfmt::skip] /// assert_eq!(1, foo.0.0); /// assert_eq!(2, bar.0.0); /// assert_eq!(0, baz.0); diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index 20a890f..c64dd40 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -222,7 +222,7 @@ impl Display for InjectError { dependency_info.name(), service_info.name() ) - }, + } InjectError::CycleDetected { service_info, cycle, @@ -242,18 +242,18 @@ impl Display for InjectError { service_info.name() ), InjectError::InvalidProvider { service_info } => { - write!(f, "the registered provider for {} returned the wrong type", service_info.name()) + write!( + f, + "the registered provider for {} returned the wrong type", + service_info.name() + ) } - InjectError::MultipleProviders { - service_info, - } => write!( + InjectError::MultipleProviders { service_info } => write!( f, "the requested service {} has multiple providers registered (did you mean to request a Services instead?)", service_info.name(), ), - InjectError::OwnedNotSupported { - service_info - } => write!( + InjectError::OwnedNotSupported { service_info } => write!( f, "the registered provider can't provide an owned variant of {}", service_info.name() @@ -266,11 +266,18 @@ impl Display for InjectError { ) } InjectError::ActivationFailed { service_info, .. } => { - write!(f, "an error occurred during activation of {}", service_info.name()) - }, + write!( + f, + "an error occurred during activation of {}", + service_info.name() + ) + } InjectError::InternalError(message) => { - write!(f, "an unexpected error occurred (please report this to https://github.com/TehPers/runtime_injector/issues): {message}") - }, + write!( + f, + "an unexpected error occurred (please report this to https://github.com/TehPers/runtime_injector/issues): {message}" + ) + } } } } diff --git a/crates/runtime_injector/src/tests.rs b/crates/runtime_injector/src/tests.rs index f69d5af..bf886ff 100644 --- a/crates/runtime_injector/src/tests.rs +++ b/crates/runtime_injector/src/tests.rs @@ -261,6 +261,6 @@ fn request_info_has_correct_path() { assert_eq!(&[ServiceInfo::of::()], foo.1.service_path()); assert_eq!( &[ServiceInfo::of::(), ServiceInfo::of::()], - foo.0 .0.service_path() + foo.0.0.service_path() ); } diff --git a/crates/runtime_injector_actix/src/service.rs b/crates/runtime_injector_actix/src/service.rs index aa67a2f..aa63653 100644 --- a/crates/runtime_injector_actix/src/service.rs +++ b/crates/runtime_injector_actix/src/service.rs @@ -86,7 +86,7 @@ where None => { return err(ErrorInternalServerError( "no injector is present in app_data", - )) + )); } }; diff --git a/rustfmt.toml b/rustfmt.toml index e423b68..97c7ae6 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -4,5 +4,6 @@ max_width = 80 # Unstable features unstable_features = true +version = "Two" format_code_in_doc_comments = true imports_granularity = "Crate" From 5556ffa5417b87679be2365e14512f5fdd449b45 Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 26 Apr 2022 12:35:43 -0700 Subject: [PATCH 19/26] Update some docs --- .../src/docs/getting_started.rs | 182 ++++++++++++------ crates/runtime_injector/src/injector.rs | 11 +- crates/runtime_injector/src/lib.rs | 2 +- .../src/providers/interface.rs | 9 +- 4 files changed, 131 insertions(+), 73 deletions(-) diff --git a/crates/runtime_injector/src/docs/getting_started.rs b/crates/runtime_injector/src/docs/getting_started.rs index 636fccb..a7067a9 100644 --- a/crates/runtime_injector/src/docs/getting_started.rs +++ b/crates/runtime_injector/src/docs/getting_started.rs @@ -47,9 +47,6 @@ //! abstraction for our output so we can check it it in our unit tests. //! //! ``` -//! #[cfg(test)] -//! # mod _ignored0 {} -//! use std::fmt::Write; //! use std::{env::args, io::stdin}; //! //! trait OutputWriter { @@ -59,19 +56,7 @@ //! struct ConsoleWriter; //! impl OutputWriter for ConsoleWriter { //! fn write_output(&mut self, message: &str) { -//! println!("{}", message); -//! } -//! } -//! -//! // Our mock writer so we can observe the output in tests -//! #[cfg(test)] -//! # mod _ignored1 {} -//! struct MockWriter(pub String); -//! #[cfg(test)] -//! # mod _ignored2 {} -//! impl OutputWriter for MockWriter { -//! fn write_output(&mut self, message: &str) { -//! writeln!(self.0, "{}", message).unwrap(); +//! println!("{message}"); //! } //! } //! @@ -88,18 +73,6 @@ //! } //! } //! -//! // Our mock reader for testing our application! -//! #[cfg(test)] -//! # mod _ignored3 {} -//! struct MockReader(pub Option); -//! #[cfg(test)] -//! # mod _ignored4 {} -//! impl InputReader for MockReader { -//! fn read_line(&mut self) -> String { -//! self.0.take().unwrap() -//! } -//! } -//! //! fn get_name( //! args: &[String], //! reader: &mut R, @@ -131,13 +104,30 @@ //! //! // Verify our program works like we want it to //! #[cfg(test)] -//! # mod _ignored5 {} +//! # mod _ignored_tests {} //! mod tests { //! use super::*; +//! use std::fmt::Write; +//! +//! // Our mock writer so we can observe the output in tests +//! struct MockWriter(pub String); +//! impl OutputWriter for MockWriter { +//! fn write_output(&mut self, message: &str) { +//! writeln!(self.0, "{message}").unwrap(); +//! } +//! } +//! +//! // Our mock reader for ensuring anything that depends on it works +//! struct MockReader(pub Option); +//! impl InputReader for MockReader { +//! fn read_line(&mut self) -> String { +//! self.0.take().unwrap() +//! } +//! } //! //! // Let's make sure we're getting the correct name from the user //! #[test] -//! # fn _ignored() {} +//! # fn _ignored_test() {} //! # pub //! fn name_is_correct() { //! // Setup our mocked reader and writer @@ -179,59 +169,82 @@ //! ``` //! use runtime_injector::{ //! interface, Arg, Injector, InjectorBuilder, IntoSingleton, Service, Svc, -//! TypedProvider, WithArg, +//! TypedProvider, WithArg, WithInterface, //! }; +//! use std::io::stdin; //! -//! // Let's leave out the functions from our traits, we don't really need them -//! // for this example. We need our trait to be a subtrait of `Service` so -//! // that we can use type erasure in our container later on. Also, if our -//! // services need to be thread-safe and we're using the "arc" feature for -//! // runtime_injector, then `Service` will automatically require `Send` + -//! // `Sync` for us -//! trait OutputWriter: Service {} +//! // We need our trait to be a subtrait of `Service` so that we can use type +//! // erasure in our container later on. Also, if our services need to be +//! // thread-safe and we're using the "arc" feature for `runtime_injector`, +//! // then `Service` will automatically make `Send` + `Sync` required for us. +//! trait OutputWriter: Service { +//! fn write_output(&mut self, message: &str); +//! } //! //! // We still want to be able to write to the console, but we need our output //! // formatter to make sure we correctly format our output //! struct ConsoleWriter(Svc); -//! impl OutputWriter for ConsoleWriter {} +//! impl OutputWriter for ConsoleWriter { +//! fn write_output(&mut self, message: &str) { +//! let message = self.0.fmt_message(message); +//! println!("{message}"); +//! } +//! } //! -//! // We also want to be able to send greetings across HTTPS -//! struct HttpsWriter(Svc); -//! impl OutputWriter for HttpsWriter {} +//! // We also want to be able to send greetings via HTTP to a web service +//! struct HttpWriter(Svc); +//! impl OutputWriter for HttpWriter { +//! fn write_output(&mut self, message: &str) { +//! let _message = self.0.fmt_message(message); +//! // ... +//! } +//! } //! //! // Finally, we need to support TCP as well //! struct TcpWriter(Svc); -//! impl OutputWriter for TcpWriter {} -//! -//! // Also, we need to be able to mock this for testing -//! #[cfg(test)] -//! struct MockWriter(pub Arg, Svc); -//! #[cfg(test)] -//! impl OutputWriter for MockWriter {} +//! impl OutputWriter for TcpWriter { +//! fn write_output(&mut self, message: &str) { +//! let _message = self.0.fmt_message(message); +//! // ... +//! } +//! } //! //! // We also need a way to format the messages -//! trait OutputFormatter: Service {} +//! trait OutputFormatter: Service { +//! fn fmt_message(&self, message: &str) -> String; +//! } //! //! // Our users want to be able to format them with custom formats! //! struct UserFormatter(pub Arg); -//! impl OutputFormatter for UserFormatter {} +//! impl OutputFormatter for UserFormatter { +//! fn fmt_message(&self, message: &str) -> String { +//! format!("{message} formatted with {fmt}", fmt = self.0) +//! } +//! } //! //! // Not all users want to use a custom format though, so we need a default //! #[derive(Default)] //! struct DefaultFormatter; -//! impl OutputFormatter for DefaultFormatter {} +//! impl OutputFormatter for DefaultFormatter { +//! fn fmt_message(&self, message: &str) -> String { +//! message.into() +//! } +//! } //! //! // Now let's bring over our reader implementations -//! trait InputReader: Service {} +//! trait InputReader: Service { +//! fn read_line(&mut self) -> String; +//! } //! //! #[derive(Default)] //! struct ConsoleReader; -//! impl InputReader for ConsoleReader {} -//! -//! #[cfg(test)] -//! struct MockReader(pub Arg>); -//! #[cfg(test)] -//! impl InputReader for MockReader {} +//! impl InputReader for ConsoleReader { +//! fn read_line(&mut self) -> String { +//! let mut input = String::new(); +//! stdin().read_line(&mut input).unwrap(); +//! input +//! } +//! } //! //! // Let's create an enum to help us configure our output writer too //! #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] @@ -279,11 +292,13 @@ //! builder.provide( //! UserFormatter //! .singleton() +//! // We want to also pass the user's custom format to our +//! // service +//! .with_arg(user_format) +//! // We want our formatter to be requested through its +//! // interface //! .with_interface::(), //! ); -//! -//! // We want to also pass the user's custom format to our service -//! builder.with_arg::(user_format); //! } else { //! // The user didn't give us a custom format, so we'll use the //! // default output formatter instead @@ -306,7 +321,7 @@ //! } //! OutputType::Https => { //! builder.provide( -//! HttpsWriter +//! HttpWriter //! .singleton() //! .with_interface::(), //! ); @@ -323,6 +338,9 @@ //! } //! //! fn main() { +//! # // Let's actually verify the test passes +//! # tests::console_output_is_formatted_before_being_written(); +//! # //! // We want the user to be able to configure the application here. //! // Normally, we'd use something like clap for this, but for the sake of //! // the example, we'll just hardcode the config @@ -343,11 +361,49 @@ //! //! // Let's not forget about unit tests! //! #[cfg(test)] +//! # mod _ignored_tests {} //! mod tests { //! use super::*; //! use runtime_injector::{define_module, Injector, IntoSingleton, Svc}; +#![cfg_attr(feature = "arc", doc = " use std::sync::Mutex;")] +#![cfg_attr(feature = "rc", doc = " use std::cell::RefCell;")] +//! +//! // We may need to mock the output writer for testing to make sure we +//! // are writing the correct message +#![cfg_attr( + feature = "arc", + doc = " struct MockWriter(Svc>);" +)] +#![cfg_attr( + feature = "rc", + doc = " struct MockWriter(Svc>);" +)] +//! impl OutputWriter for MockWriter { +//! fn write_output(&mut self, message: &str) { +//! // We'll just track the message that was written +#![cfg_attr( + feature = "arc", + doc = " let mut inner = self.0.lock().unwrap();" +)] +#![cfg_attr( + feature = "rc", + doc = " let mut inner = self.0.borrow_mut().unwrap();" +)] +//! *inner = message.to_string(); +//! } +//! } +//! +//! struct MockReader(pub Arg>); +//! impl InputReader for MockReader { +//! fn read_line(&mut self) -> String { +//! // We'll just return the message that was given to us +//! self.0.take().unwrap() +//! } +//! } //! //! #[test] +//! # fn _ignored_test() {} +//! # pub //! fn console_output_is_formatted_before_being_written() { //! // Let's make a custom module for testing just the console writer //! let module = define_module! { diff --git a/crates/runtime_injector/src/injector.rs b/crates/runtime_injector/src/injector.rs index 6f1eb28..14f642e 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -84,10 +84,10 @@ impl Injector { /// Performs a request for a service. There are several types of requests /// that can be made to the service container by default: /// - /// - [`Svc`](crate::Svc): Requests a service pointer to the given - /// interface and creates an instance of the service if needed. If - /// multiple service providers are registered for that interface, then - /// returns an error instead. + /// - [`Svc`]: Requests a service pointer to the given interface and + /// creates an instance of the service if needed. If multiple service + /// providers are registered for that interface, then returns an error + /// instead. /// - [`Box`]: Requests an owned service pointer to the given interface /// and creates an instance of the service. Not all service providers can /// provide owned versions of their services, so this may fail for some @@ -105,7 +105,7 @@ impl Injector { /// can't provide owned pointers, then returns an error instead. /// - [`Services`]: Requests all the implementations of an interface. /// This will lazily create the services on demand. See the - /// [documentation for `Services`](Services) for more details. + /// [documentation for `Services`][`Services`] for more details. /// - [`Injector`]: Requests a clone of the injector. While it doesn't make /// much sense to request this directly from the injector itself, this /// allows the injector to be requested as a dependency inside of @@ -115,6 +115,7 @@ impl Injector { /// - [`Factory`]: Lazily performs requests on demand. /// /// [`Factory`]: crate::Factory + /// [`Services`]: crate::Services /// /// See the [documentation for `Request`](Request) for more information on /// what can be requested. Custom request types can also be created by diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index d9bb77b..6eb353d 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -55,7 +55,7 @@ //! //! Lifetimes of services created by the [`Injector`] are controlled by the //! [`Provider`] used to construct those lifetimes. Currently, there are three -//! built-in service provider types: +//! primary built-in service provider types: //! //! - **[Transient](crate::TransientProvider):** A service is created each time //! it is requested. This will never return the same instance of a service diff --git a/crates/runtime_injector/src/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs index 8546b1e..cf15950 100644 --- a/crates/runtime_injector/src/providers/interface.rs +++ b/crates/runtime_injector/src/providers/interface.rs @@ -4,7 +4,7 @@ use crate::{ use std::marker::PhantomData; /// Provides a service as an implementation of an interface. See -/// [`TypedProvider::with_interface()`] for more information. +/// [`WithInterface::with_interface()`]. pub struct InterfaceProvider where P: TypedProvider, @@ -52,7 +52,7 @@ pub trait WithInterface: TypedProvider { /// assigned to the [`dyn Service`] interface. Any services assigned to the /// [`dyn Service`] interface can be requested directly by their concrete /// type. Other services cannot be requested by their concrete types once - /// they has been assigned another interface. + /// they have been assigned another interface. /// /// [`dyn Service`]: crate::Service /// @@ -74,15 +74,16 @@ pub trait WithInterface: TypedProvider { /// struct Foo; /// impl Fooable for Foo {} /// + /// // First, register Foo with the interface `dyn Fooable` /// let mut builder = Injector::builder(); /// builder.provide(Foo::default.singleton().with_interface::()); /// - /// // Foo can now be requested through its interface of `dyn Fooable`. + /// // Foo can now be requested through its interface /// let injector = builder.build(); /// let fooable: Svc = injector.get().unwrap(); /// fooable.bar(); /// - /// // It can't be requested through its original type + /// // It can't be requested through its original type anymore /// assert!(injector.get::>().is_err()); /// ``` fn with_interface>( From f4494491fb299cde47b0141dfa49410340ffd548 Mon Sep 17 00:00:00 2001 From: TehPers Date: Wed, 27 Apr 2022 02:22:18 -0700 Subject: [PATCH 20/26] Add generics/assoc types/where in interfaces --- crates/runtime_injector/src/lib.rs | 29 +- crates/runtime_injector/src/providers/func.rs | 8 +- .../src/services/interface.rs | 249 +++++++++++++++++- 3 files changed, 260 insertions(+), 26 deletions(-) diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 6eb353d..2ba7a13 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -111,8 +111,8 @@ //! //! ```rust //! use runtime_injector::{ -//! define_module, Module, interface, Injector, Svc, IntoSingleton, -//! TypedProvider, IntoTransient, constant, Service, WithInterface, +//! constant, define_module, interface, Injector, IntoSingleton, +//! IntoTransient, Module, Service, Svc, TypedProvider, WithInterface, //! }; //! use std::error::Error; //! @@ -123,8 +123,7 @@ //! // trait, and we don't care what the concrete type is most of the time in //! // our other services as long as it implements this trait. Because of this, //! // we're going to use dynamic dispatch later so that we can determine the -//! // concrete type at runtime (vs. generics, which are determined instead at -//! // compile time). +//! // concrete type at runtime. //! // //! // Since all implementations of this interface must be services for them to //! // be injected, we'll add that as a supertrait of `DataService`. With the @@ -139,14 +138,18 @@ //! #[derive(Default)] //! struct SqlDataService; //! impl DataService for SqlDataService { -//! fn get_user(&self, _user_id: &str) -> Option { todo!() } +//! fn get_user(&self, _user_id: &str) -> Option { +//! todo!() +//! } //! } //! //! // ... Or we can mock out the data service entirely! //! #[derive(Default)] //! struct MockDataService; //! impl DataService for MockDataService { -//! fn get_user(&self, _user_id: &str) -> Option { Some(User) } +//! fn get_user(&self, _user_id: &str) -> Option { +//! Some(User) +//! } //! } //! //! // Declare `DataService` as an interface @@ -183,16 +186,16 @@ //! // We can manually add providers to our builder //! builder.provide(UserService::new.singleton()); //! struct Foo(Svc); -//! +//! //! // Alternatively, modules can be used to group providers and //! // configurations together, and can be defined via the //! // define_module! macro //! let module = define_module! { //! services = [ -//! // Simple tuple structs can be registered as services directly without -//! // defining any additional constructors +//! // Simple tuple structs can be registered as services directly +//! // without defining any additional constructors //! Foo.singleton(), -//! +//! //! // Note that we can register closures as providers as well //! (|_: Svc| "Hello, world!").singleton(), //! (|_: Option>| 120.9).transient(), @@ -207,16 +210,16 @@ //! dyn DataService = [MockDataService::default.singleton()], //! }, //! }; -//! +//! //! // You can easily add a module to your builder //! builder.add_module(module); -//! +//! //! // Now that we've registered all our providers and implementations, we //! // can start relying on our container to create our services for us! //! let injector = builder.build(); //! let user_service: Svc = injector.get()?; //! let _user = user_service.get_user("john"); -//! +//! //! Ok(()) //! } //! ``` diff --git a/crates/runtime_injector/src/providers/func.rs b/crates/runtime_injector/src/providers/func.rs index 61f75b1..a73942c 100644 --- a/crates/runtime_injector/src/providers/func.rs +++ b/crates/runtime_injector/src/providers/func.rs @@ -14,15 +14,13 @@ use std::any::Any; /// use runtime_injector::{Injector, RequestInfo, ServiceFactory, Svc}; /// /// struct Foo; -/// struct Bar; +/// struct Bar(Svc); /// -/// # fn _no_run() { /// fn factory(foo: Svc) -> Bar { -/// todo!() +/// Bar(foo) /// } -/// let injector: Injector = todo!(); +/// let injector = Injector::default(); /// factory.invoke(&injector, &RequestInfo::new()); -/// # } /// ``` pub trait ServiceFactory { /// The resulting service from invoking this service factory. diff --git a/crates/runtime_injector/src/services/interface.rs b/crates/runtime_injector/src/services/interface.rs index aa4b1d1..077ec91 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -56,12 +56,116 @@ where /// let injector = builder.build(); /// let _bar: Svc = injector.get().unwrap(); /// ``` +/// +/// ## Generic interfaces +/// +/// Traits with generic type parameters, associated types, or `where` clauses +/// can be marked as interfaces as well. For generic type parameters, just use +/// the familiar `` syntax. Then for associated types, use `assoc +/// T1, T2`. Finally, for `where` clauses, use `where T1: Trait, T2: Trait`. +/// The order is important (type parameters, then associated types, then +/// `where` clauses). +/// +/// *This syntax is based on the syntax used by +/// [downcast_rs](https://docs.rs/downcast_rs).* +/// +/// ``` +/// use runtime_injector::{ +/// constant, interface, Injector, Service, Svc, WithInterface, +/// }; +/// use std::fmt::Debug; +/// +/// trait DataSource: Service +/// where +/// T: Debug, +/// { +/// type Id; +/// fn get(&self, id: Self::Id) -> Option; +/// } +/// interface!(DataSource assoc Id where T: Debug); +/// +/// #[derive(Debug)] +/// struct User { +/// id: u32, +/// name: String, +/// } +/// +/// struct UserDataSource; +/// impl DataSource for UserDataSource { +/// type Id = u32; +/// +/// fn get(&self, id: Self::Id) -> Option { +/// if id == 1 { +/// Some(User { +/// id, +/// name: "example".to_string(), +/// }) +/// } else { +/// None +/// } +/// } +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide( +/// constant(UserDataSource) +/// .with_interface::>(), +/// ); +/// +/// let injector = builder.build(); +/// let user_data_source: Svc> = +/// injector.get().unwrap(); +/// assert!(user_data_source.get(0).is_none()); +/// let user = user_data_source.get(1).unwrap(); +/// assert_eq!("example", user.name); +/// ``` #[macro_export] macro_rules! interface { - ($interface:tt) => { - impl $crate::Interface for dyn $interface {} + ( + $interface:ident + $(<$($types:ident),* $(,)?>)? + $(assoc $($assoc:ident),* $(,)?)? + $(where $($where:tt)*)? + ) => { + $crate::interface! { + @impl + interface = $interface, + types = [$($($types),*)?], + assoc = [$($($assoc),*)?], + where = [$($($where)*)?], + unused_name = __RUNTIME_INJECTOR_T, + } + }; + { + @impl + interface = $interface:ident, + types = [$($types:ident),*], + assoc = [$($assoc:ident),*], + where = [$($where:tt)*], + unused_name = $unused_name:ident, + } => { + impl< + $($types,)* $($assoc,)* + > $crate::Interface for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Service, + $($where)* + {} - impl $crate::InterfaceFor for dyn $interface { + impl< + $($types,)* + $($assoc,)* + > $crate::InterfaceFor for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Interface, + $($where)* + { fn from_svc(service: $crate::Svc) -> $crate::Svc { service } @@ -73,19 +177,45 @@ macro_rules! interface { } } - impl $crate::InterfaceFor for dyn $interface { - fn from_svc(service: $crate::Svc) -> $crate::Svc { + #[allow(non_camel_case_types)] + impl< + $($types,)* + $($assoc,)* + $unused_name, + > $crate::InterfaceFor<$unused_name> for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Interface, + $unused_name: $interface< + $($types,)* + $($assoc = $assoc,)* + >, + $($where)* + { + fn from_svc(service: $crate::Svc<$unused_name>) -> $crate::Svc { service } fn from_owned_svc( - service: ::std::boxed::Box, + service: ::std::boxed::Box<$unused_name>, ) -> ::std::boxed::Box { service } } - impl $crate::FromProvider for dyn $interface { + impl< + $($types,)* + $($assoc,)* + > $crate::FromProvider for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Interface, + $($where)* + { type Interface = Self; fn should_provide( @@ -106,5 +236,108 @@ macro_rules! interface { ::std::result::Result::Ok(provided) } } - }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{constant, Injector, WithInterface}; + use std::marker::PhantomData; + + #[test] + fn a_impl_can_be_provided_as_a() { + trait A: Service {} + interface!(A); + + struct AImpl; + impl A for AImpl {} + + let mut builder = Injector::builder(); + builder.provide(constant(AImpl).with_interface::()); + + let injector = builder.build(); + let _a: Svc = injector.get().unwrap(); + } + + #[test] + fn b_impl_can_be_provided_as_b() { + trait B: Service { + fn get(&self) -> &T; + } + interface!(B); + + struct BImpl(T) + where + T: Service; + impl B for BImpl + where + T: Service, + { + fn get(&self) -> &T { + &self.0 + } + } + + let mut builder = Injector::builder(); + builder.provide(constant(BImpl(42)).with_interface::>()); + builder.provide( + constant(BImpl("hello, world!")) + .with_interface::>(), + ); + + let injector = builder.build(); + let b: Svc> = injector.get().unwrap(); + assert_eq!(42, *b.get()); + + let b: Svc> = injector.get().unwrap(); + assert_eq!("hello, world!", *b.get()); + } + + #[test] + fn c_impl_can_be_provided_as_c() { + trait C: Service + where + Self::Out: Service, + { + type Out; + fn get(&self, value: In) -> Self::Out; + } + interface!(C assoc Out where Out: Service); + + struct CImpl(F, PhantomData Out>) + where + In: Service, + Out: Service, + F: Service + Fn(In) -> Out; + impl C for CImpl + where + In: Service, + Out: Service, + F: Service + Fn(In) -> Out, + { + type Out = Out; + + fn get(&self, value: In) -> Self::Out { + (self.0)(value) + } + } + + let mut builder = Injector::builder(); + builder.provide( + constant(CImpl(|x| x + 1, PhantomData)) + .with_interface::>(), + ); + builder.provide( + constant(CImpl(|x| format!("input: {x}"), PhantomData)) + .with_interface::>(), + ); + + let injector = builder.build(); + let c: Svc> = injector.get().unwrap(); + assert_eq!(43, c.get(42)); + + let c: Svc> = injector.get().unwrap(); + assert_eq!("input: 42", c.get("42")); + } } From 1b4e9a9272bc2efcfb9d96de99954beb9b48e71c Mon Sep 17 00:00:00 2001 From: TehPers Date: Wed, 27 Apr 2022 02:26:20 -0700 Subject: [PATCH 21/26] Move service factories to separate directory --- crates/runtime_injector/src/lib.rs | 2 ++ crates/runtime_injector/src/providers.rs | 4 ---- crates/runtime_injector/src/service_factories.rs | 5 +++++ .../src/{providers/func.rs => service_factories/factory.rs} | 0 .../src/{providers => service_factories}/fallible.rs | 0 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 crates/runtime_injector/src/service_factories.rs rename crates/runtime_injector/src/{providers/func.rs => service_factories/factory.rs} (100%) rename crates/runtime_injector/src/{providers => service_factories}/fallible.rs (100%) diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 2ba7a13..46cdfd3 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -253,6 +253,7 @@ mod module; mod provider_registry; mod providers; mod requests; +mod service_factories; mod services; pub use builder::*; @@ -262,6 +263,7 @@ pub use module::*; pub(crate) use provider_registry::*; pub use providers::*; pub use requests::*; +pub use service_factories::*; pub use services::*; pub mod docs; diff --git a/crates/runtime_injector/src/providers.rs b/crates/runtime_injector/src/providers.rs index 71a3b17..bd57fd1 100644 --- a/crates/runtime_injector/src/providers.rs +++ b/crates/runtime_injector/src/providers.rs @@ -1,8 +1,6 @@ mod arg; mod conditional; mod constant; -mod fallible; -mod func; mod interface; mod providers; mod singleton; @@ -11,8 +9,6 @@ mod transient; pub use arg::*; pub use conditional::*; pub use constant::*; -pub use fallible::*; -pub use func::*; pub use interface::*; pub use providers::*; pub use singleton::*; diff --git a/crates/runtime_injector/src/service_factories.rs b/crates/runtime_injector/src/service_factories.rs new file mode 100644 index 0000000..9616110 --- /dev/null +++ b/crates/runtime_injector/src/service_factories.rs @@ -0,0 +1,5 @@ +mod fallible; +mod factory; + +pub use fallible::*; +pub use factory::*; diff --git a/crates/runtime_injector/src/providers/func.rs b/crates/runtime_injector/src/service_factories/factory.rs similarity index 100% rename from crates/runtime_injector/src/providers/func.rs rename to crates/runtime_injector/src/service_factories/factory.rs diff --git a/crates/runtime_injector/src/providers/fallible.rs b/crates/runtime_injector/src/service_factories/fallible.rs similarity index 100% rename from crates/runtime_injector/src/providers/fallible.rs rename to crates/runtime_injector/src/service_factories/fallible.rs From a68d70fecb9921bcb5960c18e9cae1c5fbc927c2 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 28 Apr 2022 00:10:41 -0700 Subject: [PATCH 22/26] Fix IoC docs and module macro --- .../src/docs/inversion_of_control.rs | 71 ++++++++----------- crates/runtime_injector/src/module.rs | 41 ++++++++--- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/crates/runtime_injector/src/docs/inversion_of_control.rs b/crates/runtime_injector/src/docs/inversion_of_control.rs index b6a6a98..db471bb 100644 --- a/crates/runtime_injector/src/docs/inversion_of_control.rs +++ b/crates/runtime_injector/src/docs/inversion_of_control.rs @@ -157,8 +157,8 @@ //! //! ``` //! use runtime_injector::{ -//! constant, define_module, interface, Injector, IntoSingleton, -//! IntoTransient, Service, Svc, +//! Arg, constant, define_module, interface, Injector, IntoSingleton, +//! IntoTransient, Service, Svc, WithArg, //! }; //! //! #[derive(Clone, Debug)] @@ -182,15 +182,15 @@ //! struct MockUserDatabase; //! impl UserDatabase for MockUserDatabase {} //! -//! // Since we're having our connection string injected, we need to put it -//! // behind some type that our container can inject -//! struct SqlUserDatabase(Svc); +//! // Let's pass our connection string as an argument so we don't need to +//! // hardcode it. +//! struct SqlUserDatabase(Arg); //! impl UserDatabase for SqlUserDatabase {} //! //! # #[derive(Default)] //! # struct IntegrationTestParameters; //! // We'll also inject our connection string and test parameters here -//! struct IntegrationUserDatabase(Svc, Svc); +//! struct IntegrationUserDatabase(Arg, Svc); //! impl UserDatabase for IntegrationUserDatabase {} //! //! trait UserAuthenticator: Service { @@ -206,8 +206,8 @@ //! } //! //! // We're switching to dynamic dispatch here which is marginally slower than -//! // static dispatch, but we're going to lose most of our performance to I/O -//! // anyway so the additional v-table lookup is hardly relevant here +//! // static dispatch, but we're going to lose most of our performance to +//! // network I/O anyway when making database requests. //! struct DatabaseUserAuthenticator(Svc); //! impl UserAuthenticator for DatabaseUserAuthenticator { //! fn has_access(&self, user_id: u32, scope: &str) -> bool { @@ -216,41 +216,33 @@ //! } //! } //! -//! // Now we need to declare what services we can use. -//! interface! { -//! // Since we have three implementations of a user database, we'll -//! // declare `UserDatabase` as being an interface that supports those -//! // three types -//! dyn UserDatabase = [ -//! MockUserDatabase, -//! SqlUserDatabase, -//! IntegrationUserDatabase, -//! ], -//! -//! // Similarly, we'll declare `UserAuthenticator` as being an interface -//! // that supports both our database-backed authenticator and mock one -//! dyn UserAuthenticator = [ -//! MockUserAuthenticator, -//! DatabaseUserAuthenticator, -//! ], -//! } +//! // Now we need to declare our interfaces. These are the traits we want to +//! // have abstracted away. +//! interface!(UserDatabase); +//! interface!(UserAuthenticator); //! //! fn main() { //! // We can easily determine which implementations we will use in one //! // place by creating a module. If we add more implementations later, we //! // only need to change a few lines of code in one place rather than //! // adding #[cfg] attributes all over our code +//! let connection_string = "our_secret_connection_string".to_string(); //! let module = define_module! { //! interfaces = { -//! dyn UserAuthenticator = [DatabaseUserAuthenticator.singleton()] -//! }, -//! #[cfg(feature = "integration")] -//! interfaces = { -//! dyn UserDatabase = [IntegrationUserDatabase.singleton()] -//! }, -//! #[cfg(not(feature = "integration"))] -//! interfaces = { -//! dyn UserDatabase = [SqlUserDatabase.singleton()] +//! dyn UserAuthenticator = [ +//! DatabaseUserAuthenticator.singleton() +//! ], +//! #[cfg(feature = "integration")] +//! dyn UserDatabase = [ +//! IntegrationUserDatabase +//! .singleton() +//! .with_arg(connection_string) +//! .with_arg(IntegrationTestParameters::default()) +//! ], +//! #[cfg(not(feature = "integration"))] +//! dyn UserDatabase = [ +//! SqlUserDatabase.singleton().with_arg(connection_string) +//! ], //! }, //! }; //! @@ -260,11 +252,6 @@ //! let mut builder = Injector::builder(); //! builder.add_module(module); //! -//! // Let's add our connection string and integration parameters now -//! builder.provide(constant("our_secret_connection_string".to_string())); -//! #[cfg(feature = "integration")] -//! builder.provide(constant(IntegrationTestParameters::default())); -//! //! // Now we're ready to start creating our services! We have one single //! // way of creating each of our services, regardless of what the actual //! // implementation of that service is. Let's create our container now @@ -285,7 +272,9 @@ //! // create a module which provides the mock implementation instead //! let _module = define_module! { //! interfaces = { -//! dyn UserAuthenticator = [DatabaseUserAuthenticator.singleton()], +//! dyn UserAuthenticator = [ +//! DatabaseUserAuthenticator.singleton() +//! ], //! dyn UserDatabase = [MockUserDatabase::default.singleton()], //! }, //! }; diff --git a/crates/runtime_injector/src/module.rs b/crates/runtime_injector/src/module.rs index 5286c5d..1e4ebf7 100644 --- a/crates/runtime_injector/src/module.rs +++ b/crates/runtime_injector/src/module.rs @@ -106,16 +106,17 @@ impl Module { macro_rules! define_module { { $( - $(#[$($attr:meta),*])* + $(#[$attr:meta])* $key:ident = $value:tt ),* $(,)? } => { { #[allow(unused_mut)] - let mut module = <$crate::Module as ::std::default::Default>::default(); + let mut module = + <$crate::Module as ::std::default::Default>::default(); $( - $(#[$($attr),*])* + $(#[$attr])* $crate::define_module!(@provide &mut module, $key = $value); )* module @@ -124,24 +125,44 @@ macro_rules! define_module { ( @provide $module:expr, services = [ - $($service:expr),* + $( + $(#[$attr:meta])* + $service:expr + ),* $(,)? ] ) => { - $($module.provide($service);)* + $( + $(#[$attr])* + $module.provide($service); + )* }; ( @provide $module:expr, interfaces = { - $($interface:ty = [ - $($implementation:expr),* - $(,)? - ]),* + $( + $(#[$attr1:meta])* + $interface:ty = [ + $( + $(#[$attr2:meta])* + $implementation:expr + ),* + $(,)? + ] + ),* $(,)? } ) => { $( - $($module.provide($crate::WithInterface::with_interface::<$interface>($implementation));)* + $(#[$attr1])* + $( + $(#[$attr2])* + $module.provide( + $crate::WithInterface::with_interface::<$interface>( + $implementation + ) + ); + )* )* }; } From dd02dd17f4da1a5268f9db336064a9456f2f8174 Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 28 Apr 2022 00:27:09 -0700 Subject: [PATCH 23/26] Reformat code --- crates/runtime_injector/src/docs/inversion_of_control.rs | 2 +- crates/runtime_injector/src/service_factories.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/runtime_injector/src/docs/inversion_of_control.rs b/crates/runtime_injector/src/docs/inversion_of_control.rs index db471bb..53d6318 100644 --- a/crates/runtime_injector/src/docs/inversion_of_control.rs +++ b/crates/runtime_injector/src/docs/inversion_of_control.rs @@ -157,7 +157,7 @@ //! //! ``` //! use runtime_injector::{ -//! Arg, constant, define_module, interface, Injector, IntoSingleton, +//! constant, define_module, interface, Arg, Injector, IntoSingleton, //! IntoTransient, Service, Svc, WithArg, //! }; //! diff --git a/crates/runtime_injector/src/service_factories.rs b/crates/runtime_injector/src/service_factories.rs index 9616110..55859fe 100644 --- a/crates/runtime_injector/src/service_factories.rs +++ b/crates/runtime_injector/src/service_factories.rs @@ -1,5 +1,5 @@ -mod fallible; mod factory; +mod fallible; -pub use fallible::*; pub use factory::*; +pub use fallible::*; From 66e23e9d3c95e7efec7528d3cb1607a6773dcd2f Mon Sep 17 00:00:00 2001 From: TehPers Date: Thu, 28 Apr 2022 00:32:24 -0700 Subject: [PATCH 24/26] Fix failed checks --- crates/runtime_injector/src/docs/getting_started.rs | 2 +- crates/runtime_injector/src/lib.rs | 3 ++- crates/runtime_injector/src/providers/arg.rs | 9 +++++---- crates/runtime_injector/src/requests/factory.rs | 7 ++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/runtime_injector/src/docs/getting_started.rs b/crates/runtime_injector/src/docs/getting_started.rs index a7067a9..507198e 100644 --- a/crates/runtime_injector/src/docs/getting_started.rs +++ b/crates/runtime_injector/src/docs/getting_started.rs @@ -387,7 +387,7 @@ )] #![cfg_attr( feature = "rc", - doc = " let mut inner = self.0.borrow_mut().unwrap();" + doc = " let mut inner = self.0.borrow_mut();" )] //! *inner = message.to_string(); //! } diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 46cdfd3..4ea14c1 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -233,7 +233,8 @@ clippy::missing_errors_doc, clippy::doc_markdown, clippy::needless_doctest_main, - clippy::needless_pass_by_value + clippy::needless_pass_by_value, + clippy::wildcard_imports )] #[cfg(not(any(feature = "arc", feature = "rc")))] diff --git a/crates/runtime_injector/src/providers/arg.rs b/crates/runtime_injector/src/providers/arg.rs index 66427e1..bfa36c9 100644 --- a/crates/runtime_injector/src/providers/arg.rs +++ b/crates/runtime_injector/src/providers/arg.rs @@ -17,6 +17,7 @@ pub struct Arg(T); impl Arg { /// Gets the parameter name of an [`Arg`] requested by a particular /// service. + #[must_use] pub fn param_name(target: ServiceInfo) -> String { format!( "runtime_injector::Arg[target={:?},type={:?}]", @@ -137,10 +138,10 @@ where request_info: &crate::RequestInfo, ) -> crate::InjectResult> { let mut request_info = request_info.clone(); - let _ = request_info.insert_parameter( + drop(request_info.insert_parameter( &Arg::::param_name(ServiceInfo::of::()), self.arg.clone(), - ); + )); self.inner.provide_typed(injector, &request_info) } @@ -150,10 +151,10 @@ where request_info: &crate::RequestInfo, ) -> crate::InjectResult> { let mut request_info = request_info.clone(); - let _ = request_info.insert_parameter( + drop(request_info.insert_parameter( &Arg::::param_name(ServiceInfo::of::()), self.arg.clone(), - ); + )); self.inner.provide_owned_typed(injector, &request_info) } } diff --git a/crates/runtime_injector/src/requests/factory.rs b/crates/runtime_injector/src/requests/factory.rs index f6286cc..cdcfc55 100644 --- a/crates/runtime_injector/src/requests/factory.rs +++ b/crates/runtime_injector/src/requests/factory.rs @@ -118,16 +118,17 @@ where /// assert_eq!(1, *foo1.0); /// assert_eq!(2, *foo2.0); /// ``` - pub fn with_arg(&self, value: T) -> Factory + #[must_use] + pub fn with_arg(&self, value: T) -> Self where S: Service, T: Service + Clone, { let mut request_info = self.request_info.clone(); - let _ = request_info.insert_parameter( + drop(request_info.insert_parameter( &Arg::::param_name(ServiceInfo::of::()), value, - ); + )); Factory { injector: self.injector.clone(), From 900600517ee43c867f7ff09e1abe276f8c5cb20b Mon Sep 17 00:00:00 2001 From: TehPers Date: Fri, 29 Apr 2022 13:24:06 -0700 Subject: [PATCH 25/26] Update InjectError to implement Service --- crates/runtime_injector/src/services/service.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/runtime_injector/src/services/service.rs b/crates/runtime_injector/src/services/service.rs index c64dd40..497bb7d 100644 --- a/crates/runtime_injector/src/services/service.rs +++ b/crates/runtime_injector/src/services/service.rs @@ -188,6 +188,10 @@ pub enum InjectError { /// The service that was requested. service_info: ServiceInfo, /// The error that was thrown during service initialization. + #[cfg(feature = "arc")] + inner: Box, + /// The error that was thrown during service initialization. + #[cfg(feature = "rc")] inner: Box, }, @@ -196,6 +200,12 @@ pub enum InjectError { InternalError(String), } +#[cfg(feature = "arc")] +const _: () = { + const fn assert_send_sync() {} + assert_send_sync::(); +}; + impl Error for InjectError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { From bba43f142c4c93e15f531d411ecef262bd3c8535 Mon Sep 17 00:00:00 2001 From: TehPers Date: Fri, 29 Apr 2022 13:24:23 -0700 Subject: [PATCH 26/26] Simplify macro --- .../runtime_injector/src/service_factories/factory.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/runtime_injector/src/service_factories/factory.rs b/crates/runtime_injector/src/service_factories/factory.rs index a73942c..71a3f24 100644 --- a/crates/runtime_injector/src/service_factories/factory.rs +++ b/crates/runtime_injector/src/service_factories/factory.rs @@ -1,4 +1,6 @@ -use crate::{InjectResult, Injector, Request, RequestInfo}; +use crate::{ + InjectError, InjectResult, Injector, Request, RequestInfo, ServiceInfo, +}; use std::any::Any; /// A factory for creating instances of a service. All functions of arity 12 or @@ -60,10 +62,10 @@ macro_rules! impl_provider_function { let result = self($( match <$type_name as Request>::request(&injector, request_info) { Ok(dependency) => dependency, - Err($crate::InjectError::MissingProvider { service_info }) => { - return Err($crate::InjectError::MissingDependency { + Err(InjectError::MissingProvider { service_info }) => { + return Err(InjectError::MissingDependency { dependency_info: service_info, - service_info: $crate::ServiceInfo::of::(), + service_info: ServiceInfo::of::(), }) }, Err(error) => return Err(error),