From e5f497756a9232689878b39d3e3022aac6e80c4e Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Sun, 2 Jul 2023 14:02:34 +0100 Subject: [PATCH] Implementations for `Arc` and other wrapping types. --- .vscode/settings.json | 3 + Cargo.toml | 5 +- src/async_constructible.rs | 109 +++++++++++++++++++++++++++++++++---- src/axum.rs | 2 +- src/lib.rs | 6 +- src/resource.rs | 2 +- src/state.rs | 2 +- src/sync_constructible.rs | 94 +++++++++++++++++++++++++++++--- 8 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b2b22c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.features": "all" +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index babb578..fbe6744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,15 @@ [package] name = "aerosol" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" authors = ["Diggory Blake "] edition = "2018" description = "Simple dependency injection for Rust" repository = "https://github.com/Diggsey/aerosol" license = "MIT OR Apache-2.0" +[package.metadata.docs.rs] +all-features = true + [features] default = [] async = ["async-trait"] diff --git a/src/async_constructible.rs b/src/async_constructible.rs index 8426fee..4ba3c0e 100644 --- a/src/async_constructible.rs +++ b/src/async_constructible.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{error::Error, sync::Arc}; use async_trait::async_trait; @@ -6,13 +6,13 @@ use crate::{ resource::{unwrap_constructed, Resource}, slot::SlotDesc, state::Aerosol, - ConstructibleResource, + sync_constructible::Constructible, }; -/// Implemented for resources which can be constructed asynchronously from other +/// Implemented for values which can be constructed asynchronously from other /// resources. Requires feature `async`. #[async_trait] -pub trait AsyncConstructibleResource: Resource { +pub trait AsyncConstructible: Sized { /// Error type for when resource fails to be constructed. type Error: Error + Send + Sync; /// Construct the resource with the provided application state. @@ -20,13 +20,59 @@ pub trait AsyncConstructibleResource: Resource { } #[async_trait] -impl AsyncConstructibleResource for T { - type Error = ::Error; +impl AsyncConstructible for T { + type Error = ::Error; async fn construct_async(aero: &Aerosol) -> Result { Self::construct(aero) } } +/// Automatically implemented for values which can be indirectly asynchronously constructed from other resources. +/// Requires feature `async`. +#[async_trait] +pub trait IndirectlyAsyncConstructible: Sized { + /// Error type for when resource fails to be constructed. + type Error: Error + Send + Sync; + /// Construct the resource with the provided application state. + async fn construct_async(aero: &Aerosol) -> Result; +} + +#[async_trait] +impl IndirectlyAsyncConstructible for T { + type Error = T::Error; + + async fn construct_async(aero: &Aerosol) -> Result { + T::construct_async(aero).await + } +} + +macro_rules! impl_async_constructible { + (<$t:ident>; $($x:ty: $y:expr;)*) => { + $( + #[async_trait] + impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x { + type Error = $t::Error; + + async fn construct_async(aero: &Aerosol) -> Result { + $t::construct_async(aero).await.map($y) + } + } + )* + }; +} +impl_async_constructible! { + ; + Arc: Arc::new; + std::sync::Mutex: std::sync::Mutex::new; + parking_lot::Mutex: parking_lot::Mutex::new; + std::sync::RwLock: std::sync::RwLock::new; + parking_lot::RwLock: parking_lot::RwLock::new; +} + +/// Implemented for resources which can be asynchronously constructed from other resources. Requires feature `async`. +pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {} +impl AsyncConstructibleResource for T {} + impl Aerosol { /// Try to get or construct an instance of `T` asynchronously. Requires feature `async`. pub async fn try_obtain_async(&self) -> Result { @@ -49,7 +95,27 @@ impl Aerosol { } /// Get or construct an instance of `T` asynchronously. Panics if unable. Requires feature `async`. pub async fn obtain_async(&self) -> T { - unwrap_constructed(self.try_obtain_async::().await) + unwrap_constructed::(self.try_obtain_async::().await) + } + /// Try to initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. + pub async fn try_init_async(&self) -> Result<(), T::Error> { + match self.wait_for_slot_async::(true).await { + Some(_) => Ok(()), + None => match T::construct_async(self).await { + Ok(x) => { + self.fill_placeholder::(x); + Ok(()) + } + Err(e) => { + self.clear_placeholder::(); + Err(e) + } + }, + } + } + /// Initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. Panics if unable. + pub async fn init_async(&self) { + unwrap_constructed::(self.try_init_async::().await) } } @@ -63,7 +129,7 @@ mod tests { struct Dummy; #[async_trait] - impl AsyncConstructibleResource for Dummy { + impl AsyncConstructible for Dummy { type Error = Infallible; async fn construct_async(_app_state: &Aerosol) -> Result { @@ -97,7 +163,7 @@ mod tests { struct DummyRecursive; #[async_trait] - impl AsyncConstructibleResource for DummyRecursive { + impl AsyncConstructible for DummyRecursive { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { @@ -128,7 +194,7 @@ mod tests { struct DummyCyclic; #[async_trait] - impl AsyncConstructibleResource for DummyCyclic { + impl AsyncConstructible for DummyCyclic { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { @@ -147,7 +213,7 @@ mod tests { #[derive(Debug, Clone)] struct DummySync; - impl ConstructibleResource for DummySync { + impl Constructible for DummySync { type Error = Infallible; fn construct(_app_state: &Aerosol) -> Result { @@ -160,7 +226,7 @@ mod tests { struct DummySyncRecursive; #[async_trait] - impl AsyncConstructibleResource for DummySyncRecursive { + impl AsyncConstructible for DummySyncRecursive { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { @@ -186,4 +252,23 @@ mod tests { })); } } + + #[derive(Debug)] + struct DummyNonClone; + + #[async_trait] + impl AsyncConstructible for DummyNonClone { + type Error = Infallible; + + async fn construct_async(_app_state: &Aerosol) -> Result { + tokio::time::sleep(Duration::from_millis(100)).await; + Ok(Self) + } + } + + #[tokio::test] + async fn obtain_non_clone() { + let state = Aerosol::new(); + state.obtain_async::>().await; + } } diff --git a/src/axum.rs b/src/axum.rs index 047a982..b44aab0 100644 --- a/src/axum.rs +++ b/src/axum.rs @@ -1,6 +1,6 @@ //! Integration with the `axum` web framework. //! -//! Provies the `Dep` and `Obtain` axum extractors for easily accessing +//! Provides the `Dep` and `Obtain` axum extractors for easily accessing //! resources from within route handlers. //! //! To make use of these extractors, your application state must either be diff --git a/src/lib.rs b/src/lib.rs index 097a8c1..046f0f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,16 +2,16 @@ //! # aerosol //! Simple dependency injection for Rust //! -//! Optional features: `async` +//! Optional features: //! //! ## `async` //! -//! Allows resources to be constructed asynchrously, and provies a corresponding +//! Allows resources to be constructed asynchrously, and provides a corresponding //! `AsyncConstructibleResource` trait. //! //! ## `axum` //! -//! Provies integrations with the `axum` web framework. See the `axum` module +//! Provides integrations with the `axum` web framework. See the `axum` module //! for more information. #[cfg(feature = "async")] diff --git a/src/resource.rs b/src/resource.rs index de5e6e4..7515546 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -15,7 +15,7 @@ pub(crate) fn unwrap_resource(opt: Option) -> T { } } -pub(crate) fn unwrap_constructed(res: Result) -> T { +pub(crate) fn unwrap_constructed(res: Result) -> U { match res { Ok(x) => x, Err(e) => panic!("Failed to construct `{}`: {}", type_name::(), e), diff --git a/src/state.rs b/src/state.rs index 27f756b..58fb0e6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,7 +14,7 @@ struct InnerAerosol { } /// Stores a collection of resources keyed on resource type. -/// Provies methods for accessing this collection. +/// Provides methods for accessing this collection. /// Can be cheaply cloned. #[derive(Debug, Clone, Default)] pub struct Aerosol { diff --git a/src/sync_constructible.rs b/src/sync_constructible.rs index 0653271..721aa44 100644 --- a/src/sync_constructible.rs +++ b/src/sync_constructible.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{error::Error, sync::Arc}; use crate::{ resource::{unwrap_constructed, Resource}, @@ -6,14 +6,56 @@ use crate::{ state::Aerosol, }; -/// Implemented for resources which can be constructed from other resources. -pub trait ConstructibleResource: Resource { +/// Implemented for values which can be constructed from other resources. +pub trait Constructible: Sized { /// Error type for when resource fails to be constructed. type Error: Error + Send + Sync; /// Construct the resource with the provided application state. fn construct(aero: &Aerosol) -> Result; } +/// Automatically implemented for values which can be indirectly constructed from other resources. +pub trait IndirectlyConstructible: Sized { + /// Error type for when resource fails to be constructed. + type Error: Error + Send + Sync; + /// Construct the resource with the provided application state. + fn construct(aero: &Aerosol) -> Result; +} + +impl IndirectlyConstructible for T { + type Error = T::Error; + + fn construct(aero: &Aerosol) -> Result { + T::construct(aero) + } +} + +macro_rules! impl_constructible { + (<$t:ident>; $($x:ty: $y:expr;)*) => { + $( + impl<$t: IndirectlyConstructible> IndirectlyConstructible for $x { + type Error = $t::Error; + + fn construct(aero: &Aerosol) -> Result { + $t::construct(aero).map($y) + } + } + )* + }; +} +impl_constructible! { + ; + Arc: Arc::new; + std::sync::Mutex: std::sync::Mutex::new; + parking_lot::Mutex: parking_lot::Mutex::new; + std::sync::RwLock: std::sync::RwLock::new; + parking_lot::RwLock: parking_lot::RwLock::new; +} + +/// Implemented for resources which can be constructed from other resources. +pub trait ConstructibleResource: Resource + IndirectlyConstructible {} +impl ConstructibleResource for T {} + impl Aerosol { /// Try to get or construct an instance of `T`. pub fn try_obtain(&self) -> Result { @@ -36,7 +78,27 @@ impl Aerosol { } /// Get or construct an instance of `T`. Panics if unable. pub fn obtain(&self) -> T { - unwrap_constructed(self.try_obtain::()) + unwrap_constructed::(self.try_obtain::()) + } + /// Try to initialize an instance of `T`. Does nothing if `T` is already initialized. + pub fn try_init(&self) -> Result<(), T::Error> { + match self.wait_for_slot::(true) { + Some(_) => Ok(()), + None => match T::construct(self) { + Ok(x) => { + self.fill_placeholder::(x); + Ok(()) + } + Err(e) => { + self.clear_placeholder::(); + Err(e) + } + }, + } + } + /// Initialize an instance of `T`. Does nothing if `T` is already initialized. Panics if unable. + pub fn init(&self) { + unwrap_constructed::(self.try_init::()) } } @@ -49,7 +111,7 @@ mod tests { #[derive(Debug, Clone)] struct Dummy; - impl ConstructibleResource for Dummy { + impl Constructible for Dummy { type Error = Infallible; fn construct(_app_state: &Aerosol) -> Result { @@ -77,7 +139,7 @@ mod tests { #[derive(Debug, Clone)] struct DummyRecursive; - impl ConstructibleResource for DummyRecursive { + impl Constructible for DummyRecursive { type Error = Infallible; fn construct(aero: &Aerosol) -> Result { @@ -105,7 +167,7 @@ mod tests { #[derive(Debug, Clone)] struct DummyCyclic; - impl ConstructibleResource for DummyCyclic { + impl Constructible for DummyCyclic { type Error = Infallible; fn construct(aero: &Aerosol) -> Result { @@ -120,4 +182,22 @@ mod tests { let state = Aerosol::new(); state.obtain::(); } + + #[derive(Debug)] + struct DummyNonClone; + + impl Constructible for DummyNonClone { + type Error = Infallible; + + fn construct(_app_state: &Aerosol) -> Result { + std::thread::sleep(Duration::from_millis(100)); + Ok(Self) + } + } + + #[test] + fn obtain_non_clone() { + let state = Aerosol::new(); + state.obtain::>(); + } }