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 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..09aefc8 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" @@ -15,3 +15,6 @@ exclude = [] default = ["arc"] arc = [] # Svc = Arc rc = [] # Svc = Rc + +[dependencies] +downcast-rs = "1" diff --git a/crates/runtime_injector/src/any.rs b/crates/runtime_injector/src/any.rs deleted file mode 100644 index 3407cd5..0000000 --- a/crates/runtime_injector/src/any.rs +++ /dev/null @@ -1,20 +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; -} - -impl AsAny for T { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} diff --git a/crates/runtime_injector/src/builder.rs b/crates/runtime_injector/src/builder.rs index 8995ef8..c8886ea 100644 --- a/crates/runtime_injector/src/builder.rs +++ b/crates/runtime_injector/src/builder.rs @@ -1,39 +1,74 @@ use crate::{ - Injector, Module, Provider, ProviderMap, RequestInfo, ServiceInfo, + provider_registry::{ProviderRegistry, ProviderRegistryType}, + Injector, Interface, InterfaceRegistry, Module, Provider, RequestInfo, + Service, ServiceInfo, Svc, +}; +use std::{ + collections::{hash_map::Entry, HashMap}, + fmt::Debug, }; /// A builder for an [`Injector`]. -#[derive(Default)] +#[derive(Debug, Default)] pub struct InjectorBuilder { - providers: ProviderMap, + registry: 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) { - self.add_provider(Box::new(provider)) + pub fn provide

(&mut self, provider: P) + where + P: 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) { - // 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: Svc>, + ) where + I: ?Sized + Interface, + { + self.registry + .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 + .remove_providers_for::(service_info) + .unwrap_or_default() + } + + /// Clears all providers. + pub fn clear_providers(&mut self) { + self.registry.clear(); + } + + /// Clears all providers for an interface. + pub fn clear_providers_for(&mut self) + where + I: ?Sized + Interface, + { + self.registry.remove_providers::(); } /// Borrows the root [`RequestInfo`] that will be used by calls to @@ -57,26 +92,85 @@ 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.merge(module.registry); + // 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.build(), self.root_info) + } +} + +#[derive(Debug, Default)] +pub(crate) struct InterfaceRegistryBuilder { + registries: HashMap>, +} + +impl InterfaceRegistryBuilder { + pub fn ensure_providers_mut(&mut self) -> &mut ProviderRegistry + where + I: ?Sized + Interface, + { + self.registries + .entry(ServiceInfo::of::()) + .or_insert_with(|| Box::new(ProviderRegistry::::default())) + .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.registries.clear(); + } + + pub fn merge(&mut self, other: InterfaceRegistryBuilder) { + for (interface_info, other_providers) in other.registries { + match self.registries.entry(interface_info) { + Entry::Occupied(entry) => { + entry.into_mut().merge(other_providers).unwrap(); + } + Entry::Vacant(entry) => { + entry.insert(other_providers); + } + } + } + } + + pub fn build(self) -> InterfaceRegistry { + let registries = self + .registries + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(); + InterfaceRegistry::new(registries) } } diff --git a/crates/runtime_injector/src/docs/getting_started.rs b/crates/runtime_injector/src/docs/getting_started.rs index 84b8a7b..507198e 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; @@ -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)] @@ -243,33 +256,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 @@ -298,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 @@ -325,7 +321,7 @@ //! } //! OutputType::Https => { //! builder.provide( -//! HttpsWriter +//! HttpWriter //! .singleton() //! .with_interface::(), //! ); @@ -342,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 @@ -362,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();" +)] +//! *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/docs/inversion_of_control.rs b/crates/runtime_injector/src/docs/inversion_of_control.rs index b6a6a98..53d6318 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, +//! constant, define_module, interface, Arg, 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/injector.rs b/crates/runtime_injector/src/injector.rs index 6351479..14f642e 100644 --- a/crates/runtime_injector/src/injector.rs +++ b/crates/runtime_injector/src/injector.rs @@ -1,64 +1,7 @@ use crate::{ - InjectResult, InjectorBuilder, Interface, Provider, Request, RequestInfo, - ServiceInfo, Services, Svc, + FromProvider, InjectResult, InjectorBuilder, InterfaceRegistry, Providers, + Request, RequestInfo, Svc, }; -use std::collections::HashMap; - -pub(crate) type ProviderMap = - HashMap>>>; - -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 @@ -114,9 +57,9 @@ pub(crate) use types::*; /// assert_eq!(1.0, value1); /// assert_eq!(2.0, value2); /// ``` -#[derive(Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Injector { - provider_map: MapContainer, + interface_registry: Svc, root_request_info: Svc, } @@ -128,26 +71,12 @@ 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, + interface_registry: InterfaceRegistry, request_info: RequestInfo, ) -> Self { Injector { - provider_map: MapContainerEx::new(providers), + interface_registry: Svc::new(interface_registry), root_request_info: Svc::new(request_info), } } @@ -155,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 @@ -176,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 @@ -186,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 @@ -219,11 +149,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; @@ -241,11 +172,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; @@ -305,17 +237,10 @@ impl Injector { R::request(self, request_info) } - /// Gets implementations of a service from the container. This is - /// equivalent to requesting [`Services`] from [`Injector::get()`]. - pub(crate) fn get_service( - &self, - request_info: &RequestInfo, - ) -> InjectResult> { - Services::new( - self.clone(), - self.provider_map.clone(), - request_info.clone(), - ) + #[doc(hidden)] + #[must_use] + pub fn get_providers(&self) -> Providers { + Providers::new(self.interface_registry.get_providers()) } } @@ -323,7 +248,7 @@ impl Injector { mod tests { use crate::{ DynSvc, InjectError, InjectResult, Injector, Provider, RequestInfo, - ServiceInfo, Svc, + Service, ServiceInfo, Svc, }; use core::panic; @@ -331,12 +256,14 @@ 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::() } fn provide( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult { diff --git a/crates/runtime_injector/src/iter.rs b/crates/runtime_injector/src/iter.rs index f954983..c19dc4a 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 providers; +mod services; -/// 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 providers::*; +pub use services::*; 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..205a287 --- /dev/null +++ b/crates/runtime_injector/src/iter/from_provider.rs @@ -0,0 +1,65 @@ +use crate::{ + 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 + InterfaceFor; + + /// 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_interface( + provided: Svc, + ) -> InjectResult>; + + /// Converts a provided service into an owned service pointer of this type. + fn from_interface_owned( + provided: Box, + ) -> InjectResult>; +} + +impl FromProvider for S { + type Interface = dyn Service; + + fn should_provide( + provider: &dyn Provider, + ) -> bool { + provider.result() == ServiceInfo::of::() + } + + #[inline] + fn from_interface( + provided: Svc, + ) -> InjectResult> { + #[cfg(feature = "arc")] + let provided = provided.downcast_arc().map_err(|_| { + InjectError::InvalidProvider { + service_info: ServiceInfo::of::(), + } + })?; + #[cfg(feature = "rc")] + let provided = provided.downcast_rc().map_err(|_| { + InjectError::InvalidProvider { + service_info: ServiceInfo::of::(), + } + })?; + Ok(provided) + } + + #[inline] + fn from_interface_owned( + provided: Box, + ) -> InjectResult> { + provided + .downcast() + .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 new file mode 100644 index 0000000..d15656d --- /dev/null +++ b/crates/runtime_injector/src/iter/providers.rs @@ -0,0 +1,92 @@ +use crate::{ + FromProvider, Provider, ProviderRegistry, ProviderRegistryIter, Svc, +}; + +/// 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 + S: ?Sized + FromProvider, +{ + parent_registry: Svc>, +} + +impl Providers +where + S: ?Sized + FromProvider, +{ + pub(crate) fn new( + parent_registry: Svc>, + ) -> Self { + 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<'_, S> { + ProviderIter { + inner: self.parent_registry.iter(), + } + } +} + +impl<'a, S> IntoIterator for &'a mut Providers +where + S: ?Sized + FromProvider, +{ + type Item = &'a dyn Provider; + type IntoIter = ProviderIter<'a, S>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator over the providers for the given service or interface type. +pub struct ProviderIter<'a, S> +where + S: ?Sized + FromProvider, +{ + inner: ProviderRegistryIter<'a, S::Interface>, +} + +impl<'a, S> Iterator for ProviderIter<'a, S> +where + S: ?Sized + FromProvider, +{ + type Item = &'a dyn Provider; + + fn next(&mut self) -> Option { + self.inner.find(|&provider| S::should_provide(provider)) + } +} diff --git a/crates/runtime_injector/src/iter/services.rs b/crates/runtime_injector/src/iter/services.rs new file mode 100644 index 0000000..d148af0 --- /dev/null +++ b/crates/runtime_injector/src/iter/services.rs @@ -0,0 +1,271 @@ +use crate::{ + FromProvider, InjectError, InjectResult, Injector, ProviderIter, Providers, + RequestInfo, Svc, +}; + +/// 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 +/// 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: Providers, +} + +impl Services +where + S: ?Sized + FromProvider, +{ + #[inline] + pub(crate) fn new( + injector: Injector, + request_info: RequestInfo, + providers: Providers, + ) -> Self { + Services { + injector, + request_info, + providers, + } + } + + /// 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(), + } + } + + /// 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(), + } + } +} + +/// An iterator over the provided services of the given type. Each service is +/// activated on demand. Because activation of a service may fail, this +/// iterator returns [`InjectResult`]. +/// +/// ``` +/// 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: ProviderIter<'a, 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| { + // Provide the service + let service = + match provider.provide(self.injector, self.request_info) { + Ok(service) => service, + Err(InjectError::ConditionsNotMet { .. }) => return None, + Err(error) => return Some(Err(error)), + }; + + // Downcast the service + let service = S::from_interface(service); + Some(service) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} + +/// 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: ProviderIter<'a, 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| { + // Provide the service + let service = match provider + .provide_owned(self.injector, self.request_info) + { + Ok(service) => service, + Err(InjectError::ConditionsNotMet { .. }) => return None, + Err(error) => return Some(Err(error)), + }; + + // Downcast the service + let service = S::from_interface_owned(service); + Some(service) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.provider_iter.size_hint() + } +} + +#[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 _next = services.iter().next().unwrap().unwrap(); + assert!(initialized.load(Ordering::Relaxed)); + } +} diff --git a/crates/runtime_injector/src/lib.rs b/crates/runtime_injector/src/lib.rs index 7e3789f..4ea14c1 100644 --- a/crates/runtime_injector/src/lib.rs +++ b/crates/runtime_injector/src/lib.rs @@ -15,8 +15,7 @@ //! ## 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 @@ -44,7 +43,6 @@ //! 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 @@ -57,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 @@ -88,7 +86,6 @@ //! 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 @@ -102,7 +99,6 @@ //! 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 @@ -113,10 +109,10 @@ //! //! ## Example //! -//! ``` +//! ```rust //! use runtime_injector::{ -//! define_module, Module, interface, Injector, Svc, IntoSingleton, -//! TypedProvider, IntoTransient, constant, Service +//! constant, define_module, interface, Injector, IntoSingleton, +//! IntoTransient, Module, Service, Svc, TypedProvider, WithInterface, //! }; //! use std::error::Error; //! @@ -127,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 @@ -143,20 +138,22 @@ //! #[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) +//! } //! } //! -//! // 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 @@ -172,7 +169,6 @@ //! 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) @@ -189,26 +185,23 @@ //! //! // 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(), -//! //! // 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), //! ], @@ -226,7 +219,7 @@ //! let injector = builder.build(); //! let user_service: Svc = injector.get()?; //! let _user = user_service.get_user("john"); -//! +//! //! Ok(()) //! } //! ``` @@ -236,10 +229,12 @@ #![warn(missing_docs)] #![allow( clippy::module_name_repetitions, + clippy::module_inception, 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")))] @@ -252,23 +247,34 @@ compile_error!( "The 'arc' and 'rc' features are mutually exclusive and cannot be enabled together." ); -mod any; mod builder; mod injector; mod iter; mod module; +mod provider_registry; +mod providers; mod requests; +mod service_factories; mod services; -pub use any::*; 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 service_factories::*; 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..1e4ebf7 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, Svc}; 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: 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 + .ensure_providers_mut() + .add_provider_for(provider.result(), Svc::new(provider)); } /// Sets the of a value request parameter for requests made by the injector @@ -55,7 +55,7 @@ impl Module { /// ``` /// use runtime_injector::{ /// define_module, interface, Arg, Injector, IntoSingleton, IntoTransient, -/// Service, Svc, +/// Service, Svc, WithArg, /// }; /// /// struct Foo(Arg); @@ -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 = [ @@ -82,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 @@ -116,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 @@ -134,38 +125,44 @@ macro_rules! define_module { ( @provide $module:expr, services = [ - $($service:expr),* + $( + $(#[$attr:meta])* + $service:expr + ),* $(,)? ] - ) => { - $($module.provide($service);)* - }; - ( - @provide $module:expr, - interfaces = { - $($interface:ty = [ - $($implementation:expr),* - $(,)? - ]),* - $(,)? - } ) => { $( - $($module.provide($crate::TypedProvider::with_interface::<$interface>($implementation));)* + $(#[$attr])* + $module.provide($service); )* }; ( @provide $module:expr, - arguments = { - $($service:ty = [ - $($arg:expr),* - $(,)? - ]),* + interfaces = { + $( + $(#[$attr1:meta])* + $interface:ty = [ + $( + $(#[$attr2:meta])* + $implementation:expr + ),* + $(,)? + ] + ),* $(,)? } ) => { $( - $($crate::WithArg::with_arg::<$service, _>($module, $arg);)* + $(#[$attr1])* + $( + $(#[$attr2])* + $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 new file mode 100644 index 0000000..c67f20b --- /dev/null +++ b/crates/runtime_injector/src/provider_registry.rs @@ -0,0 +1,171 @@ +use crate::{Interface, Provider, Service, ServiceInfo, Svc}; +use std::{ + collections::{hash_map::Values, HashMap}, + fmt::{Debug, Formatter}, + slice::Iter, +}; + +/// Stores providers for a particular interface +pub(crate) struct ProviderRegistry +where + I: ?Sized + Interface, +{ + providers: HashMap>>>, +} + +impl ProviderRegistry +where + I: ?Sized + Interface, +{ + pub fn add_provider_for( + &mut self, + service_info: ServiceInfo, + provider: Svc>, + ) { + self.providers + .entry(service_info) + .or_default() + .push(provider); + } + + pub fn remove_providers_for( + &mut self, + service_info: ServiceInfo, + ) -> Option>>> { + self.providers.remove(&service_info) + } + + pub fn iter(&self) -> ProviderRegistryIter<'_, I> { + ProviderRegistryIter { + values: self.providers.values(), + cur_slot: None, + } + } +} + +impl Debug for ProviderRegistry +where + I: ?Sized + Interface, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_map() + .entries( + self.providers.iter().filter(|(_, v)| !v.is_empty()).map( + |(k, v)| (k.name(), format!("<{} providers>", v.len())), + ), + ) + .finish() + } +} + +impl Default for ProviderRegistry +where + I: ?Sized + Interface, +{ + fn default() -> Self { + Self { + providers: HashMap::default(), + } + } +} + +pub(crate) struct ProviderRegistryIter<'a, I> +where + I: ?Sized + Interface, +{ + values: Values<'a, ServiceInfo, Vec>>>, + cur_slot: Option>>>, +} + +impl<'a, I> Iterator for ProviderRegistryIter<'a, I> +where + I: ?Sized + Interface, +{ + 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(next.as_ref()); + } + + // Try to go to next slot + let next_slot = self.values.next()?; + self.cur_slot = Some(next_slot.iter()); + } + } +} + +/// Marker trait for provider registries. +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); + +#[cfg(feature = "rc")] +downcast_rs::impl_downcast!(ProviderRegistryType); + +#[derive(Debug, Default)] +pub(crate) struct InterfaceRegistry { + registries: HashMap>, +} + +impl InterfaceRegistry { + pub fn new( + provider_registries: HashMap< + ServiceInfo, + Svc, + >, + ) -> Self { + InterfaceRegistry { + registries: provider_registries, + } + } + + 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::(); + 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.rs b/crates/runtime_injector/src/providers.rs new file mode 100644 index 0000000..bd57fd1 --- /dev/null +++ b/crates/runtime_injector/src/providers.rs @@ -0,0 +1,15 @@ +mod arg; +mod conditional; +mod constant; +mod interface; +mod providers; +mod singleton; +mod transient; + +pub use arg::*; +pub use conditional::*; +pub use constant::*; +pub use interface::*; +pub use providers::*; +pub use singleton::*; +pub use transient::*; diff --git a/crates/runtime_injector/src/providers/arg.rs b/crates/runtime_injector/src/providers/arg.rs new file mode 100644 index 0000000..bfa36c9 --- /dev/null +++ b/crates/runtime_injector/src/providers/arg.rs @@ -0,0 +1,362 @@ +use crate::{ + InjectError, InjectResult, Injector, Request, RequestInfo, Service, + ServiceInfo, TypedProvider, +}; +use std::{ + error::Error, + fmt::{Display, Formatter}, + ops::{Deref, DerefMut}, +}; + +/// Allows custom pre-defined values to be passed as arguments to services. +/// +/// See [WithArg::with_arg()]. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +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={:?}]", + target.id(), + ServiceInfo::of::().id() + ) + } + + /// Converts an argument into its inner value. + pub fn into_inner(arg: Self) -> T { + arg.0 + } +} + +impl Deref for Arg { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Arg { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +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 { + let parent_request = info.service_path().last().ok_or_else(|| { + InjectError::ActivationFailed { + service_info: ServiceInfo::of::(), + inner: Box::new(ArgRequestError::NoParentRequest), + } + })?; + + let request_name = Self::param_name(*parent_request); + let param = info.get_parameter(&request_name).ok_or_else(|| { + InjectError::ActivationFailed { + service_info: ServiceInfo::of::(), + inner: Box::new(ArgRequestError::MissingParameter), + } + })?; + + let param: &T = param.downcast_ref().ok_or_else(|| { + InjectError::ActivationFailed { + service_info: ServiceInfo::of::(), + inner: Box::new(ArgRequestError::ParameterTypeInvalid), + } + })?; + + Ok(Arg(param.clone())) + } +} + +/// An error occurred while injecting an instance of [`Arg`]. +#[derive(Debug)] +#[non_exhaustive] +pub enum ArgRequestError { + /// The argument value was not provided. + MissingParameter, + /// The argument value is the wrong type. This should never happen. + ParameterTypeInvalid, + /// There is no parent request. + NoParentRequest, +} + +impl Error for ArgRequestError {} + +impl Display for ArgRequestError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ArgRequestError::MissingParameter => { + write!(f, "no value assigned for this argument") + } + ArgRequestError::ParameterTypeInvalid => { + write!(f, "argument value is the wrong type") + } + ArgRequestError::NoParentRequest => { + write!(f, "no parent request was found") + } + } + } +} + +/// 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 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(); + drop(request_info.insert_parameter( + &Arg::::param_name(ServiceInfo::of::()), + self.arg.clone(), + )); + self.inner.provide_typed(injector, &request_info) + } + + fn provide_owned_typed( + &self, + injector: &crate::Injector, + request_info: &crate::RequestInfo, + ) -> crate::InjectResult> { + let mut request_info = request_info.clone(); + drop(request_info.insert_parameter( + &Arg::::param_name(ServiceInfo::of::()), + self.arg.clone(), + )); + self.inner.provide_owned_typed(injector, &request_info) + } +} + +/// 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, interface, Arg, ArgRequestError, InjectError, Injector, + IntoSingleton, Service, ServiceInfo, Svc, WithArg, WithInterface, + }; + + #[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. + 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(); + 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, + inner, + }) => { + assert_eq!(ServiceInfo::of::>(), service_info); + let inner: &ArgRequestError = + inner.downcast_ref().expect("failed to downcast error"); + match inner { + ArgRequestError::NoParentRequest => {} + inner => Err(inner).unwrap(), + } + } + Err(error) => Err(error).unwrap(), + } + } + + #[test] + fn request_fails_if_arg_is_wrong_type() { + let mut builder = Injector::builder(); + builder.provide(Foo.singleton().with_arg(42u32)); + + let injector = builder.build(); + match injector.get::>() { + Ok(_) => unreachable!("request should have failed"), + Err(InjectError::ActivationFailed { + service_info, + inner, + }) => { + assert_eq!(ServiceInfo::of::>(), service_info); + let inner: &ArgRequestError = + inner.downcast_ref().expect("failed to downcast error"); + match inner { + ArgRequestError::MissingParameter => {} + inner => Err(inner).unwrap(), + } + } + Err(error) => Err(error).unwrap(), + } + } + + #[test] + fn request_succeeds_if_arg_is_correct_type() { + let mut builder = Injector::builder(); + builder.provide(Foo.singleton().with_arg(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.provide( + Foo.singleton() + .with_arg(42i32) + .with_interface::(), + ); + + let injector = builder.build(); + 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/services/conditional.rs b/crates/runtime_injector/src/providers/conditional.rs similarity index 55% rename from crates/runtime_injector/src/services/conditional.rs rename to crates/runtime_injector/src/providers/conditional.rs index cdf815d..3e6eeb9 100644 --- a/crates/runtime_injector/src/services/conditional.rs +++ b/crates/runtime_injector/src/providers/conditional.rs @@ -22,11 +22,12 @@ where P: TypedProvider, F: Service + Fn(&Injector, &RequestInfo) -> bool, { + type Interface =

::Interface; type Result = P::Result; #[inline] fn provide_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { @@ -41,7 +42,7 @@ where #[inline] fn provide_owned_typed( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult> { @@ -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, @@ -99,3 +100,78 @@ 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/providers/constant.rs similarity index 86% rename from crates/runtime_injector/src/services/constant.rs rename to crates/runtime_injector/src/providers/constant.rs index c1fc19d..ea1f052 100644 --- a/crates/runtime_injector/src/services/constant.rs +++ b/crates/runtime_injector/src/providers/constant.rs @@ -27,10 +27,11 @@ impl TypedProvider for ConstantProvider where R: Service, { + type Interface = dyn Service; type Result = R; fn provide_typed( - &mut self, + &self, _injector: &Injector, _request_info: &RequestInfo, ) -> InjectResult> { @@ -98,3 +99,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/providers/interface.rs b/crates/runtime_injector/src/providers/interface.rs new file mode 100644 index 0000000..cf15950 --- /dev/null +++ b/crates/runtime_injector/src/providers/interface.rs @@ -0,0 +1,106 @@ +use crate::{ + InjectResult, Injector, InterfaceFor, RequestInfo, Svc, TypedProvider, +}; +use std::marker::PhantomData; + +/// Provides a service as an implementation of an interface. See +/// [`WithInterface::with_interface()`]. +pub struct InterfaceProvider +where + P: TypedProvider, + I: ?Sized + InterfaceFor, +{ + inner: P, + _marker: PhantomData I>, +} + +impl TypedProvider for InterfaceProvider +where + P: TypedProvider, + I: ?Sized + InterfaceFor + InterfaceFor, +{ + type Interface = I; + type Result = I; + + fn provide_typed( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + self.inner + .provide_typed(injector, request_info) + .map(I::from_svc) + } + + fn provide_owned_typed( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + self.inner + .provide_owned_typed(injector, request_info) + .map(I::from_owned_svc) + } +} + +/// 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 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 have been assigned another interface. + /// + /// [`dyn Service`]: crate::Service + /// + /// ## Example + /// + /// ``` + /// use runtime_injector::{ + /// interface, InjectResult, Injector, IntoSingleton, Service, Svc, + /// TypedProvider, WithInterface, + /// }; + /// + /// trait Fooable: Service { + /// fn bar(&self) {} + /// } + /// + /// interface!(Fooable); + /// + /// #[derive(Default)] + /// 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 + /// let injector = builder.build(); + /// let fooable: Svc = injector.get().unwrap(); + /// fooable.bar(); + /// + /// // It can't be requested through its original type anymore + /// 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/providers/providers.rs b/crates/runtime_injector/src/providers/providers.rs new file mode 100644 index 0000000..ff1644d --- /dev/null +++ b/crates/runtime_injector/src/providers/providers.rs @@ -0,0 +1,135 @@ +use crate::{ + InjectError, InjectResult, Injector, Interface, InterfaceFor, RequestInfo, + Service, ServiceInfo, Svc, +}; + +/// Weakly typed service provider. +/// +/// 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; + + /// Provides an instance of the service. + fn provide( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult>; + + /// Provides an owned instance of the service. + fn provide_owned( + &self, + _injector: &Injector, + _request_info: &RequestInfo, + ) -> InjectResult> { + Err(InjectError::OwnedNotSupported { + service_info: self.result(), + }) + } +} + +/// A strongly-typed service provider. +/// +/// 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, 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( +/// &self, +/// _injector: &Injector, +/// _request_info: &RequestInfo, +/// ) -> InjectResult> { +/// Ok(Svc::new(Foo)) +/// } +/// } +/// +/// let mut builder = Injector::builder(); +/// builder.provide(FooProvider); +/// +/// let injector = builder.build(); +/// let _foo: Svc = injector.get().unwrap(); +/// ``` +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: ?Sized + Service; + + /// 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( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult>; + + /// Provides an owned instance of the service. Not all providers can + /// provide an owned variant of the service. + fn provide_owned_typed( + &self, + _injector: &Injector, + _request_info: &RequestInfo, + ) -> InjectResult> { + Err(InjectError::OwnedNotSupported { + service_info: ServiceInfo::of::(), + }) + } +} + +impl Provider for T +where + T: TypedProvider, +{ + type Interface = ::Interface; + + fn result(&self) -> ServiceInfo { + ServiceInfo::of::() + } + + fn provide( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + let service = self.provide_typed(injector, request_info)?; + Ok(Self::Interface::from_svc(service)) + } + + fn provide_owned( + &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/providers/singleton.rs b/crates/runtime_injector/src/providers/singleton.rs new file mode 100644 index 0000000..a4b8562 --- /dev/null +++ b/crates/runtime_injector/src/providers/singleton.rs @@ -0,0 +1,183 @@ +use crate::{ + InjectResult, Injector, RequestInfo, Service, ServiceFactory, ServiceInfo, + Svc, TypedProvider, +}; +use std::marker::PhantomData; + +/// A service provider that only creates a single instance of the service. +/// The service is created only during its first request. Any subsequent +/// requests return service pointers to the same service. +pub struct SingletonProvider +where + R: Service, + F: ServiceFactory, +{ + factory: F, + #[cfg(feature = "arc")] + result: std::sync::RwLock>>, + #[cfg(feature = "rc")] + result: std::cell::RefCell>>, + marker: PhantomData R>, +} + +impl SingletonProvider +where + R: Service, + F: ServiceFactory, +{ + /// Creates a new [`SingletonProvider`] using a service factory. + #[must_use] + pub fn new(func: F) -> Self { + SingletonProvider { + factory: func, + #[cfg(feature = "arc")] + result: std::sync::RwLock::default(), + #[cfg(feature = "rc")] + result: std::cell::RefCell::default(), + marker: PhantomData, + } + } +} + +impl TypedProvider for SingletonProvider +where + D: Service, + R: Service, + F: Service + ServiceFactory, +{ + type Interface = dyn Service; + type Result = R; + + fn provide_typed( + &self, + injector: &Injector, + request_info: &RequestInfo, + ) -> InjectResult> { + 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); + + // 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) + } +} + +/// Defines a conversion into a singleton provider. This trait is automatically +/// implemented for all service factories. +pub trait IntoSingleton +where + R: Service, + F: ServiceFactory, +{ + /// Creates a singleton provider. Singleton providers create their values + /// only once (when first requested) and reuse that value for each future + /// request. + /// + /// ## Example + /// + /// ``` + /// use runtime_injector::{Injector, IntoSingleton, Svc}; + /// + /// #[derive(Default)] + /// struct Foo; + /// + /// let mut builder = Injector::builder(); + /// builder.provide(Foo::default.singleton()); + /// + /// let injector = builder.build(); + /// let foo1: Svc = injector.get().unwrap(); + /// let foo2: Svc = injector.get().unwrap(); + /// + /// assert!(Svc::ptr_eq(&foo1, &foo2)); + /// ``` + #[must_use] + fn singleton(self) -> SingletonProvider; +} + +impl IntoSingleton for F +where + R: Service, + F: ServiceFactory, +{ + fn singleton(self) -> SingletonProvider { + SingletonProvider::new(self) + } +} + +impl From for SingletonProvider +where + R: Service, + F: ServiceFactory, +{ + fn from(func: F) -> Self { + func.singleton() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + #[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/providers/transient.rs similarity index 73% rename from crates/runtime_injector/src/services/transient.rs rename to crates/runtime_injector/src/providers/transient.rs index ebb6b4d..622f8c3 100644 --- a/crates/runtime_injector/src/services/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; @@ -35,25 +35,30 @@ impl TypedProvider for TransientProvider where D: Service, R: Service, - F: ServiceFactory, + F: Service + ServiceFactory, { + type Interface = dyn Service; 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)) } } @@ -109,3 +114,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)); + } +} 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/arg.rs b/crates/runtime_injector/src/requests/arg.rs deleted file mode 100644 index b3842f8..0000000 --- a/crates/runtime_injector/src/requests/arg.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::{ - AsAny, InjectError, InjectResult, Injector, InjectorBuilder, Module, - Request, RequestInfo, RequestParameter, Service, ServiceInfo, -}; -use std::{ - error::Error, - fmt::{Debug, 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); -/// ``` -pub struct Arg(T); - -impl Arg { - pub(crate) fn param_name(target: ServiceInfo) -> String { - format!( - "runtime_injector::Arg[target={:?},type={:?}]", - target.id(), - ServiceInfo::of::().id() - ) - } - - /// Converts an argument into its inner value. - pub fn into_inner(arg: Self) -> T { - arg.0 - } -} - -impl Deref for Arg { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Arg { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut 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 { - let parent_request = info.service_path().last().ok_or_else(|| { - InjectError::ActivationFailed { - service_info: ServiceInfo::of::(), - inner: Box::new(ArgRequestError::NoParentRequest), - } - })?; - - let request_name = Self::param_name(*parent_request); - let param = info.get_parameter(&request_name).ok_or_else(|| { - InjectError::ActivationFailed { - service_info: ServiceInfo::of::(), - inner: Box::new(ArgRequestError::MissingParameter), - } - })?; - - let param: &T = param.downcast_ref().ok_or_else(|| { - InjectError::ActivationFailed { - service_info: ServiceInfo::of::(), - inner: Box::new(ArgRequestError::ParameterTypeInvalid), - } - })?; - - Ok(Arg(param.clone())) - } -} - -/// An error occurred while injecting an instance of [`Arg`]. -#[derive(Debug)] -pub enum ArgRequestError { - /// The argument value was not provided. - MissingParameter, - /// The argument value is the wrong type. This should never happen. - ParameterTypeInvalid, - /// There is no parent request. - NoParentRequest, -} - -impl Error for ArgRequestError {} - -impl Display for ArgRequestError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ArgRequestError::MissingParameter => { - write!(f, "no value assigned for this argument") - } - ArgRequestError::ParameterTypeInvalid => { - write!(f, "argument value is the wrong type") - } - ArgRequestError::NoParentRequest => { - write!(f, "no parent request was found") - } - } - } -} - -/// 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>; -} - -impl WithArg for RequestInfo { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.insert_parameter( - &Arg::::param_name(ServiceInfo::of::()), - value, - ) - } -} - -impl WithArg for InjectorBuilder { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.root_info_mut().with_arg::(value) - } -} - -impl WithArg for Module { - fn with_arg( - &mut self, - value: T, - ) -> Option> { - self.insert_parameter( - &Arg::::param_name(ServiceInfo::of::()), - value, - ) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - define_module, Arg, ArgRequestError, InjectError, Injector, - IntoSingleton, ServiceInfo, Svc, - }; - - #[test] - fn request_fails_if_missing_arg() { - struct Foo(Arg); - - let module = define_module! { - services = [Foo.singleton()], - }; - - let mut builder = Injector::builder(); - builder.add_module(module); - - let injector = builder.build(); - match injector.get::>() { - Ok(_) => unreachable!("request should have failed"), - Err(InjectError::ActivationFailed { - service_info, - inner, - }) => { - assert_eq!(ServiceInfo::of::>(), service_info); - let inner: &ArgRequestError = - inner.downcast_ref().expect("failed to downcast error"); - match inner { - ArgRequestError::MissingParameter => {} - inner => Err(inner).unwrap(), - } - } - Err(error) => Err(error).unwrap(), - } - } - - #[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, - inner, - }) => { - assert_eq!(ServiceInfo::of::>(), service_info); - let inner: &ArgRequestError = - inner.downcast_ref().expect("failed to downcast error"); - match inner { - ArgRequestError::NoParentRequest => {} - inner => Err(inner).unwrap(), - } - } - Err(error) => Err(error).unwrap(), - } - } -} diff --git a/crates/runtime_injector/src/requests/factory.rs b/crates/runtime_injector/src/requests/factory.rs index eb90dfd..cdcfc55 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,21 +92,26 @@ 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(); /// @@ -103,8 +119,22 @@ impl Factory { /// 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) -> Self + where + S: Service, + T: Service + Clone, + { + let mut request_info = self.request_info.clone(); + drop(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 55c23ef..16cbd85 100644 --- a/crates/runtime_injector/src/requests/info.rs +++ b/crates/runtime_injector/src/requests/info.rs @@ -1,11 +1,11 @@ -use crate::{RequestParameter, ServiceInfo}; +use crate::{InjectError, InjectResult, RequestParameter, ServiceInfo}; use std::{ collections::HashMap, fmt::{Debug, Formatter}, }; /// Information about an active request. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct RequestInfo { service_path: Vec, parameters: HashMap>, @@ -22,12 +22,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,7 +80,6 @@ impl RequestInfo { /// let foo: Svc = injector.get().unwrap(); /// let bar: Svc = injector.get().unwrap(); /// let baz: Svc = injector.get().unwrap(); - #[rustfmt::skip] /// assert_eq!(1, foo.0.0); /// assert_eq!(2, bar.0.0); /// assert_eq!(0, baz.0); @@ -118,17 +131,38 @@ impl RequestInfo { } } -impl Default for RequestInfo { - fn default() -> Self { - RequestInfo::new() - } -} - 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 { + 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") + .and_then(::downcast_ref) + ); + assert_eq!( + Some(Box::new("bar".to_string())), + info.remove_parameter("foo") + .and_then(|p| p.downcast::().ok()) + ); + 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/requests/parameter.rs b/crates/runtime_injector/src/requests/parameter.rs index 4c496f4..3bdc3ac 100644 --- a/crates/runtime_injector/src/requests/parameter.rs +++ b/crates/runtime_injector/src/requests/parameter.rs @@ -1,24 +1,24 @@ -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 { +#[cfg(feature = "arc")] +impl_downcast!(sync RequestParameter); + +#[cfg(feature = "rc")] +impl_downcast!(RequestParameter); + +impl RequestParameter for T { fn clone_dyn(&self) -> Box { Box::new(self.clone()) } } -impl dyn RequestParameter { - /// Tries to downcast this request parameter to a concrete type. - pub fn downcast_ref(&self) -> Option<&T> { - self.as_any().downcast_ref() - } -} - impl Clone for Box { fn clone(&self) -> Self { self.as_ref().clone_dyn() diff --git a/crates/runtime_injector/src/requests/request.rs b/crates/runtime_injector/src/requests/request.rs index 5256043..b3b4971 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, Providers, 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,70 +71,91 @@ 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)?; - if services.len() > 1 { - Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), - providers: services.len(), - }) - } else { - let service = services.get_all().next().transpose()?.ok_or( - InjectError::MissingProvider { - service_info: ServiceInfo::of::(), - }, - )?; - - Ok(service) + let mut services: Services = injector.get_with(info)?; + let mut services = services.iter(); + + // Try to get first provided service + let first = + services + .next() + .ok_or_else(|| InjectError::MissingProvider { + service_info: ServiceInfo::of::(), + })?; + + // Check if another service is provided + if services.next().is_some() { + return Err(InjectError::MultipleProviders { + service_info: ServiceInfo::of::(), + }); } + + first } } -/// 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)?; - if services.len() > 1 { - Err(InjectError::MultipleProviders { - service_info: ServiceInfo::of::(), - providers: services.len(), - }) - } else { - let service = services.get_all_owned().next().transpose()?.ok_or( - InjectError::MissingProvider { - service_info: ServiceInfo::of::(), - }, - )?; - - Ok(service) + // Get service iterator + let mut services: Services = injector.get_with(info)?; + 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::(), + })?; + + // Check if another service is provided + if services.next().is_some() { + return Err(InjectError::MultipleProviders { + service_info: ServiceInfo::of::(), + }); } + + first + } +} + +/// 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 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) + let providers: Providers = injector.get_with(info)?; + Ok(Services::new(injector.clone(), info.clone(), providers)) } } /// 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 +163,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 +189,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/service_factories.rs b/crates/runtime_injector/src/service_factories.rs new file mode 100644 index 0000000..55859fe --- /dev/null +++ b/crates/runtime_injector/src/service_factories.rs @@ -0,0 +1,5 @@ +mod factory; +mod fallible; + +pub use factory::*; +pub use fallible::*; diff --git a/crates/runtime_injector/src/services/func.rs b/crates/runtime_injector/src/service_factories/factory.rs similarity index 74% rename from crates/runtime_injector/src/services/func.rs rename to crates/runtime_injector/src/service_factories/factory.rs index db020c2..71a3f24 100644 --- a/crates/runtime_injector/src/services/func.rs +++ b/crates/runtime_injector/src/service_factories/factory.rs @@ -1,6 +1,7 @@ use crate::{ - InjectResult, Injector, Request, RequestInfo, Service, ServiceInfo, + InjectError, 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 @@ -15,23 +16,21 @@ use crate::{ /// 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: Service { +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( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult; @@ -48,26 +47,25 @@ macro_rules! impl_provider_function { (@impl ($($type_name:ident),*)) => { impl ServiceFactory<($($type_name,)*)> for F where - F: Service + FnMut($($type_name),*) -> R, - R: Service, + F: Fn($($type_name),*) -> R, + R: Any, $($type_name: Request,)* { type Result = F::Output; #[allow(unused_variables, unused_mut, unused_assignments, non_snake_case)] fn invoke( - &mut self, + &self, 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 { + 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), diff --git a/crates/runtime_injector/src/services/fallible.rs b/crates/runtime_injector/src/service_factories/fallible.rs similarity index 52% rename from crates/runtime_injector/src/services/fallible.rs rename to crates/runtime_injector/src/service_factories/fallible.rs index 1e52412..73a9a51 100644 --- a/crates/runtime_injector/src/services/fallible.rs +++ b/crates/runtime_injector/src/service_factories/fallible.rs @@ -28,7 +28,7 @@ where type Result = R; fn invoke( - &mut self, + &self, injector: &Injector, request_info: &RequestInfo, ) -> InjectResult { @@ -116,3 +116,104 @@ 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 _foo: 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.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..077ec91 100644 --- a/crates/runtime_injector/src/services/interface.rs +++ b/crates/runtime_injector/src/services/interface.rs @@ -1,78 +1,42 @@ -use crate::{ - DynSvc, InjectError, InjectResult, OwnedDynSvc, Service, ServiceInfo, 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::(), - }) - } +use crate::{Service, Svc}; - 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: ?Sized + 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 -/// 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; @@ -83,59 +47,297 @@ 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); +/// +/// let mut builder = Injector::builder(); +/// builder.provide(Bar::default.singleton().with_interface::()); +/// +/// 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: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, + } + }; { - $( - $interface:ty = [ - $($(#[$($attr:meta),*])* $impl:ty),* - $(,)? - ] - ),* - $(,)? + @impl + interface = $interface:ident, + types = [$($types:ident),*], + assoc = [$($assoc:ident),*], + where = [$($where:tt)*], + unused_name = $unused_name:ident, } => { - $( - 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, - } - )* - - Err($crate::InjectError::MissingProvider { service_info: $crate::ServiceInfo::of::() }) - } - - #[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, - } - )* - - Err($crate::InjectError::MissingProvider { service_info: $crate::ServiceInfo::of::() }) - } + impl< + $($types,)* $($assoc,)* + > $crate::Interface for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Service, + $($where)* + {} + + 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 } - $( - $(#[$($attr),*])* - impl $crate::InterfaceFor<$impl> for $interface {} - )* - )* - }; + fn from_owned_svc( + service: ::std::boxed::Box, + ) -> ::std::boxed::Box { + service + } + } + + #[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<$unused_name>, + ) -> ::std::boxed::Box { + service + } + } + + impl< + $($types,)* + $($assoc,)* + > $crate::FromProvider for dyn $interface< + $($types,)* + $($assoc = $assoc,)* + > + where + Self: $crate::Interface, + $($where)* + { + type Interface = Self; + + fn should_provide( + _provider: &dyn $crate::Provider, + ) -> bool { + true + } + + fn from_interface( + provided: $crate::Svc, + ) -> $crate::InjectResult<$crate::Svc> { + ::std::result::Result::Ok(provided) + } + + fn from_interface_owned( + provided: ::std::boxed::Box, + ) -> $crate::InjectResult<::std::boxed::Box> { + ::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")); + } } diff --git a/crates/runtime_injector/src/services/providers.rs b/crates/runtime_injector/src/services/providers.rs deleted file mode 100644 index 32cb751..0000000 --- a/crates/runtime_injector/src/services/providers.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::marker::PhantomData; - -use crate::{ - DynSvc, InjectError, InjectResult, Injector, Interface, InterfaceFor, - OwnedDynSvc, RequestInfo, Service, ServiceInfo, Svc, -}; - -/// Weakly typed service provider. -/// -/// Given an injector, this can provide an instance of a service. 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 [`ServiceInfo`] which describes the type returned by this provider. - fn result(&self) -> ServiceInfo; - - /// Provides an instance of the service. - fn provide( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult; - - /// Provides an owned instance of the service. - fn provide_owned( - &mut self, - _injector: &Injector, - _request_info: &RequestInfo, - ) -> InjectResult { - Err(InjectError::OwnedNotSupported { - service_info: self.result(), - }) - } -} - -impl Provider for T -where - T: TypedProvider, -{ - fn result(&self) -> ServiceInfo { - ServiceInfo::of::() - } - - fn provide( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult { - let result = self.provide_typed(injector, request_info)?; - Ok(result as DynSvc) - } - - fn provide_owned( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult { - let result = self.provide_owned_typed(injector, request_info)?; - Ok(result as OwnedDynSvc) - } -} - -/// 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 -/// implemented for all types which implement [`TypedProvider`]. -/// -/// ## Example -/// -/// ``` -/// use runtime_injector::{ -/// InjectResult, Injector, RequestInfo, Svc, TypedProvider, -/// }; -/// -/// struct Foo; -/// -/// struct FooProvider; -/// impl TypedProvider for FooProvider { -/// type Result = Foo; -/// -/// fn provide_typed( -/// &mut self, -/// _injector: &Injector, -/// _request_info: &RequestInfo, -/// ) -> InjectResult> { -/// Ok(Svc::new(Foo)) -/// } -/// } -/// -/// let mut builder = Injector::builder(); -/// builder.provide(FooProvider); -/// -/// let injector = builder.build(); -/// let _foo: Svc = injector.get().unwrap(); -/// ``` -pub trait TypedProvider: Sized + Provider { - /// The type of service this can provide. - type Result: Interface; - - /// 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, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult>; - - /// Provides an owned instance of the service. Not all providers can - /// provide an owned variant of the service. - fn provide_owned_typed( - &mut self, - _injector: &Injector, - _request_info: &RequestInfo, - ) -> InjectResult> { - Err(InjectError::OwnedNotSupported { - 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/service.rs b/crates/runtime_injector/src/services/service.rs index 4915751..497bb7d 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; 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_rs::Downcast {} + impl Service for T {} }, { - pub trait Service: Any + Send + Sync {} - impl Service for T {} + pub trait Service: downcast_rs::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 @@ -112,6 +116,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 { @@ -157,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 @@ -185,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, }, @@ -193,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 { @@ -211,8 +224,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, @@ -232,20 +252,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, - providers, - } => write!( + InjectError::MultipleProviders { service_info } => 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 - } => write!( + InjectError::OwnedNotSupported { service_info } => write!( f, "the registered provider can't provide an owned variant of {}", service_info.name() @@ -258,11 +276,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): {}", 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/services/singleton.rs b/crates/runtime_injector/src/services/singleton.rs deleted file mode 100644 index b4cf835..0000000 --- a/crates/runtime_injector/src/services/singleton.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::{ - InjectResult, Injector, RequestInfo, Service, ServiceFactory, Svc, - TypedProvider, -}; -use std::marker::PhantomData; - -/// A service provider that only creates a single instance of the service. -/// The service is created only during its first request. Any subsequent -/// requests return service pointers to the same service. -pub struct SingletonProvider -where - R: Service, - F: ServiceFactory, -{ - factory: F, - result: Option>, - marker: PhantomData R>, -} - -impl SingletonProvider -where - R: Service, - F: ServiceFactory, -{ - /// Creates a new [`SingletonProvider`] using a service factory. - #[must_use] - pub fn new(func: F) -> Self { - SingletonProvider { - factory: func, - result: None, - marker: PhantomData, - } - } -} - -impl TypedProvider for SingletonProvider -where - D: Service, - R: Service, - F: ServiceFactory, -{ - type Result = R; - - fn provide_typed( - &mut self, - injector: &Injector, - request_info: &RequestInfo, - ) -> InjectResult> { - if let Some(ref service) = self.result { - return Ok(service.clone()); - } - - let result = self.factory.invoke(injector, request_info)?; - let result = Svc::new(result); - self.result = Some(result.clone()); - Ok(result) - } -} - -/// Defines a conversion into a singleton provider. This trait is automatically -/// implemented for all service factories. -pub trait IntoSingleton -where - R: Service, - F: ServiceFactory, -{ - /// Creates a singleton provider. Singleton providers create their values - /// only once (when first requested) and reuse that value for each future - /// request. - /// - /// ## Example - /// - /// ``` - /// use runtime_injector::{Injector, IntoSingleton, Svc}; - /// - /// #[derive(Default)] - /// struct Foo; - /// - /// let mut builder = Injector::builder(); - /// builder.provide(Foo::default.singleton()); - /// - /// let injector = builder.build(); - /// let foo1: Svc = injector.get().unwrap(); - /// let foo2: Svc = injector.get().unwrap(); - /// - /// assert!(Svc::ptr_eq(&foo1, &foo2)); - /// ``` - #[must_use] - fn singleton(self) -> SingletonProvider; -} - -impl IntoSingleton for F -where - R: Service, - F: ServiceFactory, -{ - fn singleton(self) -> SingletonProvider { - SingletonProvider::new(self) - } -} - -impl From for SingletonProvider -where - R: Service, - F: ServiceFactory, -{ - fn from(func: F) -> Self { - func.singleton() - } -} diff --git a/crates/runtime_injector/src/tests.rs b/crates/runtime_injector/src/tests.rs index c4dd674..bf886ff 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; @@ -108,6 +107,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 +134,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); } @@ -143,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 { @@ -209,22 +202,20 @@ fn interfaces() { #[test] fn multi_injection() { trait Foo: Service {} - impl Foo for Svc1 {} - 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::()); 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.get_all().collect::>().unwrap(); + foos.iter().collect::>().unwrap(); assert_eq!(1, foos.len()); } @@ -270,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/Cargo.toml b/crates/runtime_injector_actix/Cargo.toml index 6cf426b..3fa6d08 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" @@ -16,11 +16,11 @@ default = [] arc = [] # Ignored, just used for CI [dependencies] -actix-web = "3" +actix-web = "4" futures-util = "0.3" [dependencies.runtime_injector] -version = "0.4" +version = "0.5" path = "../runtime_injector" default_features = false features = ["arc"] diff --git a/crates/runtime_injector_actix/src/service.rs b/crates/runtime_injector_actix/src/service.rs index 3f2ba12..aa63653 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() { @@ -67,7 +86,7 @@ impl FromRequest for Injected { 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"