Improve documentation

This commit is contained in:
Diggory Blake 2023-08-04 00:56:01 +01:00
parent a5395a5d33
commit 119f582327
No known key found for this signature in database
GPG key ID: E6BDFA83146ABD40
9 changed files with 486 additions and 154 deletions

View file

@ -5,18 +5,20 @@ use std::{
task::{Context, Poll}, task::{Context, Poll},
}; };
use frunk::prelude::HList; use crate::{
resource::{Resource, ResourceList},
slot::SlotDesc,
state::Aero,
};
use crate::{resource::Resource, slot::SlotDesc, state::Aerosol}; pub(crate) struct WaitForSlot<R: ResourceList, T: Resource> {
state: Aero<R>,
pub(crate) struct WaitForSlot<R: HList, T: Resource> {
state: Aerosol<R>,
wait_index: Option<usize>, wait_index: Option<usize>,
insert_placeholder: bool, insert_placeholder: bool,
phantom: PhantomData<fn() -> T>, phantom: PhantomData<fn() -> T>,
} }
impl<R: HList, T: Resource> Future for WaitForSlot<R, T> { impl<R: ResourceList, T: Resource> Future for WaitForSlot<R, T> {
type Output = Option<T>; type Output = Option<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@ -26,7 +28,7 @@ impl<R: HList, T: Resource> Future for WaitForSlot<R, T> {
} }
} }
impl<R: HList> Aerosol<R> { impl<R: ResourceList> Aero<R> {
pub(crate) fn wait_for_slot_async<T: Resource>( pub(crate) fn wait_for_slot_async<T: Resource>(
&self, &self,
insert_placeholder: bool, insert_placeholder: bool,
@ -54,13 +56,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn try_get_some() { async fn try_get_some() {
let state = Aerosol::new().with(42); let state = Aero::new().with(42);
assert_eq!(state.try_get_async::<i32>().await, Some(42)); assert_eq!(state.try_get_async::<i32>().await, Some(42));
} }
#[tokio::test] #[tokio::test]
async fn try_get_none() { async fn try_get_none() {
let state = Aerosol::new().with("Hello"); let state = Aero::new().with("Hello");
assert_eq!(state.try_get_async::<i32>().await, None); assert_eq!(state.try_get_async::<i32>().await, None);
} }
} }

View file

@ -1,12 +1,12 @@
use std::{any::Any, sync::Arc}; use std::{any::Any, marker::PhantomData, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use frunk::prelude::HList; use frunk::{hlist::Sculptor, HCons, HNil};
use crate::{ use crate::{
resource::{unwrap_constructed, Resource}, resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList},
slot::SlotDesc, slot::SlotDesc,
state::Aerosol, state::Aero,
sync_constructible::Constructible, sync_constructible::Constructible,
}; };
@ -17,13 +17,13 @@ pub trait AsyncConstructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed. /// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync; type Error: Into<anyhow::Error> + Send + Sync;
/// Construct the resource with the provided application state. /// Construct the resource with the provided application state.
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error>; async fn construct_async(aero: &Aero) -> Result<Self, Self::Error>;
/// Called after construction with the concrete resource to allow the callee /// Called after construction with the concrete resource to allow the callee
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also /// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`. /// provide an implementation of `Arc<dyn Bar>`.
async fn after_construction_async( async fn after_construction_async(
_this: &(dyn Any + Send + Sync), _this: &(dyn Any + Send + Sync),
_aero: &Aerosol, _aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
@ -32,12 +32,12 @@ pub trait AsyncConstructible: Sized + Any + Send + Sync {
#[async_trait] #[async_trait]
impl<T: Constructible> AsyncConstructible for T { impl<T: Constructible> AsyncConstructible for T {
type Error = <T as Constructible>::Error; type Error = <T as Constructible>::Error;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
Self::construct(aero) Self::construct(aero)
} }
async fn after_construction_async( async fn after_construction_async(
this: &(dyn Any + Send + Sync), this: &(dyn Any + Send + Sync),
aero: &Aerosol, aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Self::after_construction(this, aero) Self::after_construction(this, aero)
} }
@ -50,13 +50,13 @@ pub trait IndirectlyAsyncConstructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed. /// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync; type Error: Into<anyhow::Error> + Send + Sync;
/// Construct the resource with the provided application state. /// Construct the resource with the provided application state.
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error>; async fn construct_async(aero: &Aero) -> Result<Self, Self::Error>;
/// Called after construction with the concrete resource to allow the callee /// Called after construction with the concrete resource to allow the callee
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also /// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`. /// provide an implementation of `Arc<dyn Bar>`.
async fn after_construction_async( async fn after_construction_async(
_this: &(dyn Any + Send + Sync), _this: &(dyn Any + Send + Sync),
_aero: &Aerosol, _aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
@ -66,14 +66,14 @@ pub trait IndirectlyAsyncConstructible: Sized + Any + Send + Sync {
impl<T: AsyncConstructible> IndirectlyAsyncConstructible for T { impl<T: AsyncConstructible> IndirectlyAsyncConstructible for T {
type Error = T::Error; type Error = T::Error;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
let res = <T as AsyncConstructible>::construct_async(aero).await?; let res = <T as AsyncConstructible>::construct_async(aero).await?;
<T as AsyncConstructible>::after_construction_async(&res, aero).await?; <T as AsyncConstructible>::after_construction_async(&res, aero).await?;
Ok(res) Ok(res)
} }
async fn after_construction_async( async fn after_construction_async(
this: &(dyn Any + Send + Sync), this: &(dyn Any + Send + Sync),
aero: &Aerosol, aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
<T as AsyncConstructible>::after_construction_async(this, aero).await <T as AsyncConstructible>::after_construction_async(this, aero).await
} }
@ -86,13 +86,13 @@ macro_rules! impl_async_constructible {
impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x { impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x {
type Error = $t::Error; type Error = $t::Error;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
let res = $y($t::construct_async(aero).await?); let res = $y($t::construct_async(aero).await?);
<$t as IndirectlyAsyncConstructible>::after_construction_async(&res, aero).await?; <$t as IndirectlyAsyncConstructible>::after_construction_async(&res, aero).await?;
Ok(res) Ok(res)
} }
async fn after_construction_async(this: &(dyn Any + Send + Sync), aero: &Aerosol) -> Result<(), Self::Error> { async fn after_construction_async(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
<$t as IndirectlyAsyncConstructible>::after_construction_async(this, aero).await <$t as IndirectlyAsyncConstructible>::after_construction_async(this, aero).await
} }
} }
@ -109,10 +109,36 @@ impl_async_constructible! {
} }
/// Implemented for resources which can be asynchronously constructed from other resources. Requires feature `async`. /// Implemented for resources which can be asynchronously constructed from other resources. Requires feature `async`.
/// Do not implement this trait directly, instead implement `AsyncConstructible` and ensure
/// the remaining type bounds are met for the automatic implementation of `AsyncConstructibleResource`.
pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {} pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {}
impl<T: Resource + IndirectlyAsyncConstructible> AsyncConstructibleResource for T {} impl<T: Resource + IndirectlyAsyncConstructible> AsyncConstructibleResource for T {}
impl<R: HList> Aerosol<R> { /// Automatically implemented for resource lists where every resource can be asynchronously constructed.
#[async_trait]
pub trait AsyncConstructibleResourceList: ResourceList {
/// Construct every resource in this list in the provided aerosol instance
async fn construct_async<R: ResourceList>(aero: &Aero<R>) -> anyhow::Result<()>;
}
#[async_trait]
impl AsyncConstructibleResourceList for HNil {
async fn construct_async<R: ResourceList>(_aero: &Aero<R>) -> anyhow::Result<()> {
Ok(())
}
}
#[async_trait]
impl<H: AsyncConstructibleResource, T: AsyncConstructibleResourceList>
AsyncConstructibleResourceList for HCons<H, T>
{
async fn construct_async<R: ResourceList>(aero: &Aero<R>) -> anyhow::Result<()> {
aero.try_init_async::<H>().await.map_err(Into::into)?;
T::construct_async(aero).await
}
}
impl<R: ResourceList> Aero<R> {
/// Try to get or construct an instance of `T` asynchronously. Requires feature `async`. /// Try to get or construct an instance of `T` asynchronously. Requires feature `async`.
pub async fn try_obtain_async<T: AsyncConstructibleResource>(&self) -> Result<T, T::Error> { pub async fn try_obtain_async<T: AsyncConstructibleResource>(&self) -> Result<T, T::Error> {
match self.try_get_slot() { match self.try_get_slot() {
@ -156,12 +182,58 @@ impl<R: HList> Aerosol<R> {
pub async fn init_async<T: AsyncConstructibleResource>(&self) { pub async fn init_async<T: AsyncConstructibleResource>(&self) {
unwrap_constructed::<T, _>(self.try_init_async::<T>().await) unwrap_constructed::<T, _>(self.try_init_async::<T>().await)
} }
/// Builder method equivalent to calling `try_init_async()` but can be chained.
pub async fn try_with_constructed_async<T: AsyncConstructibleResource>(
self,
) -> Result<Aero<HCons<T, R>>, T::Error> {
self.try_init_async::<T>().await?;
Ok(Aero {
inner: self.inner,
phantom: PhantomData,
})
}
/// Builder method equivalent to calling `try_init_async()` but can be chained. Panics if construction fails.
pub async fn with_constructed_async<T: AsyncConstructibleResource>(self) -> Aero<HCons<T, R>> {
unwrap_constructed::<T, _>(self.try_with_constructed_async().await)
}
/// Convert into a different variant of the Aero type. Any missing required resources
/// will be automatically asynchronously constructed.
pub async fn try_construct_remaining_async<R2: ResourceList, I>(
self,
) -> anyhow::Result<Aero<R2>>
where
R2: Sculptor<R, I>,
<R2 as Sculptor<R, I>>::Remainder: AsyncConstructibleResourceList,
{
<<R2 as Sculptor<R, I>>::Remainder>::construct_async(&self).await?;
Ok(Aero {
inner: self.inner,
phantom: PhantomData,
})
}
/// Convert into a different variant of the Aero type. Any missing required resources
/// will be automatically asynchronously constructed. Panics if construction of any missing resource fails.
pub async fn construct_remaining_async<R2: ResourceList, I>(self) -> Aero<R2>
where
R2: Sculptor<R, I>,
<R2 as Sculptor<R, I>>::Remainder: AsyncConstructibleResourceList,
{
unwrap_constructed_hlist::<<R2 as Sculptor<R, I>>::Remainder, _>(
self.try_construct_remaining_async().await,
)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{convert::Infallible, time::Duration}; use std::{convert::Infallible, time::Duration};
use crate::Aero;
use super::*; use super::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -171,7 +243,7 @@ mod tests {
impl AsyncConstructible for Dummy { impl AsyncConstructible for Dummy {
type Error = Infallible; type Error = Infallible;
async fn construct_async(_app_state: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(_app_state: &Aero) -> Result<Self, Self::Error> {
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
Ok(Self) Ok(Self)
} }
@ -179,13 +251,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn obtain() { async fn obtain() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain_async::<Dummy>().await; state.obtain_async::<Dummy>().await;
} }
#[tokio::test] #[tokio::test]
async fn obtain_race() { async fn obtain_race() {
let state = Aerosol::new(); let state = Aero::new();
let mut handles = Vec::new(); let mut handles = Vec::new();
for _ in 0..100 { for _ in 0..100 {
let state = state.clone(); let state = state.clone();
@ -205,7 +277,7 @@ mod tests {
impl AsyncConstructible for DummyRecursive { impl AsyncConstructible for DummyRecursive {
type Error = Infallible; type Error = Infallible;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain_async::<Dummy>().await; aero.obtain_async::<Dummy>().await;
Ok(Self) Ok(Self)
} }
@ -213,13 +285,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn obtain_recursive() { async fn obtain_recursive() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain_async::<DummyRecursive>().await; state.obtain_async::<DummyRecursive>().await;
} }
#[tokio::test] #[tokio::test]
async fn obtain_recursive_race() { async fn obtain_recursive_race() {
let state = Aerosol::new(); let state = Aero::new();
let mut handles = Vec::new(); let mut handles = Vec::new();
for _ in 0..100 { for _ in 0..100 {
let state = state.clone(); let state = state.clone();
@ -236,7 +308,7 @@ mod tests {
impl AsyncConstructible for DummyCyclic { impl AsyncConstructible for DummyCyclic {
type Error = Infallible; type Error = Infallible;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain_async::<DummyCyclic>().await; aero.obtain_async::<DummyCyclic>().await;
Ok(Self) Ok(Self)
} }
@ -245,7 +317,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[should_panic(expected = "Cycle detected")] #[should_panic(expected = "Cycle detected")]
async fn obtain_cyclic() { async fn obtain_cyclic() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain_async::<DummyCyclic>().await; state.obtain_async::<DummyCyclic>().await;
} }
@ -255,7 +327,7 @@ mod tests {
impl Constructible for DummySync { impl Constructible for DummySync {
type Error = Infallible; type Error = Infallible;
fn construct(_app_state: &Aerosol) -> Result<Self, Self::Error> { fn construct(_app_state: &Aero) -> Result<Self, Self::Error> {
std::thread::sleep(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(100));
Ok(Self) Ok(Self)
} }
@ -268,7 +340,7 @@ mod tests {
impl AsyncConstructible for DummySyncRecursive { impl AsyncConstructible for DummySyncRecursive {
type Error = Infallible; type Error = Infallible;
async fn construct_async(aero: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain_async::<DummySync>().await; aero.obtain_async::<DummySync>().await;
Ok(Self) Ok(Self)
} }
@ -276,13 +348,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn obtain_sync_recursive() { async fn obtain_sync_recursive() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain_async::<DummySyncRecursive>().await; state.obtain_async::<DummySyncRecursive>().await;
} }
#[tokio::test] #[tokio::test]
async fn obtain_sync_recursive_race() { async fn obtain_sync_recursive_race() {
let state = Aerosol::new(); let state = Aero::new();
let mut handles = Vec::new(); let mut handles = Vec::new();
for _ in 0..100 { for _ in 0..100 {
let state = state.clone(); let state = state.clone();
@ -299,7 +371,7 @@ mod tests {
impl AsyncConstructible for DummyNonClone { impl AsyncConstructible for DummyNonClone {
type Error = Infallible; type Error = Infallible;
async fn construct_async(_app_state: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(_app_state: &Aero) -> Result<Self, Self::Error> {
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
Ok(Self) Ok(Self)
} }
@ -307,7 +379,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn obtain_non_clone() { async fn obtain_non_clone() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain_async::<Arc<DummyNonClone>>().await; state.obtain_async::<Arc<DummyNonClone>>().await;
} }
@ -322,13 +394,13 @@ mod tests {
impl AsyncConstructible for DummyImpl { impl AsyncConstructible for DummyImpl {
type Error = Infallible; type Error = Infallible;
async fn construct_async(_app_state: &Aerosol) -> Result<Self, Self::Error> { async fn construct_async(_app_state: &Aero) -> Result<Self, Self::Error> {
Ok(Self) Ok(Self)
} }
async fn after_construction_async( async fn after_construction_async(
this: &(dyn Any + Send + Sync), this: &(dyn Any + Send + Sync),
aero: &Aerosol, aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if let Some(arc) = this.downcast_ref::<Arc<Self>>() { if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
aero.insert(arc.clone() as Arc<dyn DummyTrait>) aero.insert(arc.clone() as Arc<dyn DummyTrait>)
@ -339,8 +411,29 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn obtain_impl() { async fn obtain_impl() {
let state = Aerosol::new(); let state = Aero::new();
state.init_async::<Arc<DummyImpl>>().await; state.init_async::<Arc<DummyImpl>>().await;
state.try_get_async::<Arc<dyn DummyTrait>>().await.unwrap(); state.try_get_async::<Arc<dyn DummyTrait>>().await.unwrap();
} }
#[tokio::test]
async fn with_constructed_async() {
let state = Aero::new()
.with(42)
.with_constructed_async::<Dummy>()
.await
.with("hi");
state.get::<Dummy, _>();
}
#[tokio::test]
async fn construct_remaining_async() {
let state: Aero![i32, Dummy, DummyRecursive, &str] = Aero::new()
.with(42)
.with("hi")
.construct_remaining_async()
.await;
state.get::<Dummy, _>();
state.get::<DummyRecursive, _>();
}
} }

View file

@ -4,7 +4,7 @@
//! resources from within route handlers. //! resources from within route handlers.
//! //!
//! To make use of these extractors, your application state must either be //! To make use of these extractors, your application state must either be
//! an `Aerosol`, or you must implement `FromRef<YourState>` for `Aerosol`. //! an `Aero`, or you must implement `FromRef<YourState>` for `Aero`.
use std::any::type_name; use std::any::type_name;
@ -15,7 +15,7 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use crate::{Aerosol, AsyncConstructibleResource, ConstructibleResource, Resource}; use crate::{Aero, AsyncConstructibleResource, ConstructibleResource, Resource};
/// Type of axum Rejection returned when a resource cannot be acquired /// Type of axum Rejection returned when a resource cannot be acquired
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -59,18 +59,18 @@ impl DependencyError {
} }
} }
/// Get an already-existing resource from the state. Equivalent to calling `Aerosol::try_get_async`. /// Get an already-existing resource from the state. Equivalent to calling `Aero::try_get_async`.
pub struct Dep<T: Resource>(pub T); pub struct Dep<T: Resource>(pub T);
#[async_trait] #[async_trait]
impl<T: ConstructibleResource, S: Send + Sync> FromRequestParts<S> for Dep<T> impl<T: ConstructibleResource, S: Send + Sync> FromRequestParts<S> for Dep<T>
where where
Aerosol: FromRef<S>, Aero: FromRef<S>,
{ {
type Rejection = DependencyError; type Rejection = DependencyError;
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
Aerosol::from_ref(state) Aero::from_ref(state)
.try_get_async() .try_get_async()
.await .await
.map(Self) .map(Self)
@ -78,18 +78,18 @@ where
} }
} }
/// Get a resource from the state, or construct it if it doesn't exist. Equivalent to calling `Aerosol::try_obtain_async`. /// Get a resource from the state, or construct it if it doesn't exist. Equivalent to calling `Aero::try_obtain_async`.
pub struct Obtain<T: AsyncConstructibleResource>(pub T); pub struct Obtain<T: AsyncConstructibleResource>(pub T);
#[async_trait] #[async_trait]
impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T> impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T>
where where
Aerosol: FromRef<S>, Aero: FromRef<S>,
{ {
type Rejection = DependencyError; type Rejection = DependencyError;
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
Aerosol::from_ref(state) Aero::from_ref(state)
.try_obtain_async() .try_obtain_async()
.await .await
.map(Self) .map(Self)

View file

@ -1,18 +1,127 @@
#![deny(missing_docs)] #![deny(missing_docs)]
//! # aerosol //! # aerosol
//! Simple dependency injection for Rust //! Simple but powerful dependency injection for Rust.
//! //!
//! Optional features: //! This crate provides the `Aero` type, which stores dependencies (called resources) keyed by their
//! type. Resources can be constructed eagerly at application startup, or on-demand when they are
//! first needed. Resources can access and/or initialize other resources on creation.
//! //!
//! ## `async` //! The crate will detect dependency cycles (if constructing resource A requires resource B which
//! itself requires resource A) and will panic rather than stack overflow in that case.
//!
//! The `Aero` type has an optional type parameter to make certain resources *required*. When
//! a resource is required it can be accessed infallibly. The `Aero![...]` macro exists to
//! easily name an `Aero` with a specific set of required resources.
//!
//! ## Optional features
//!
//! ### `async`
//! //!
//! Allows resources to be constructed asynchrously, and provides a corresponding //! Allows resources to be constructed asynchrously, and provides a corresponding
//! `AsyncConstructibleResource` trait. //! `AsyncConstructibleResource` trait.
//! //!
//! ## `axum` //! ### `axum`
//! //!
//! Provides integrations with the `axum` web framework. See the `axum` module //! Provides integrations with the `axum` web framework. See the `axum` module
//! for more information. //! for more information.
//!
//! ## Example usage
//!
//! ```rust
//! use std::{sync::Arc, any::Any};
//!
//! # struct PostmarkClient;
//! # #[derive(Clone)]
//! # struct ConnectionPool;
//! # #[derive(Clone)]
//! # struct MessageQueue;
//! # #[derive(Clone)]
//! # struct MagicNumber(i32);
//! # trait EmailSender: Send + Sync { fn send(&self) {} }
//! # impl EmailSender for PostmarkClient {}
//! # impl PostmarkClient { fn new() -> anyhow::Result<Self> { Ok(Self) }}
//! use aerosol::{Aero, Constructible};
//!
//! // Here, we can list all the things we want to guarantee are in
//! // our app state. This is entirely optional, we could also just
//! // use the `Aero` type with default arguments and check that
//! // resources are present at runtime.
//! type AppState = Aero![
//! Arc<PostmarkClient>,
//! Arc<dyn EmailSender>,
//! ConnectionPool,
//! MessageQueue,
//! MagicNumber,
//! ];
//!
//! fn main() {
//! let app_state: AppState = Aero::new()
//! // Directly add a resource which doesn't implement `Constructible`.
//! .with(MagicNumber(42))
//! // Construct an `Arc<PostmarkClient>` resource in the AppState
//! .with_constructed::<Arc<PostmarkClient>>()
//! // Check that an implementation of `EmailSender` was added as a result
//! .assert::<Arc<dyn EmailSender>>()
//! // Automatically construct anything else necessary for our AppState
//! // (in this case, `ConnectionPool` and `MessageQueue`)
//! .construct_remaining();
//!
//! // Add an extra resource
//! app_state.insert("Hello, world");
//!
//! run(app_state);
//! }
//!
//! fn run(app_state: AppState) {
//! // The `get()` method is infallible because the `Arc<dyn EmailSender>` was
//! // explicitly listed when defining our `AppState`.
//! let email_sender: Arc<dyn EmailSender> = app_state.get();
//! email_sender.send(/* email */);
//!
//! // We have to use `try_get()` here because a `&str` is not guaranteed to
//! // exist on our `AppState`.
//! let hello_message: &str = app_state.try_get().unwrap();
//! println!("{hello_message}");
//!
//! // ... more application logic
//! }
//!
//! // The `Constructible` trait can be implemented to allow resources to be automatically
//! // constructed.
//! impl Constructible for PostmarkClient {
//! type Error = anyhow::Error;
//!
//! fn construct(aero: &Aero) -> Result<Self, Self::Error> {
//! PostmarkClient::new(/* initialize using environment variables */)
//! }
//!
//! fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
//! // We can use this to automatically populate extra resources on the context.
//! // For example, in this case we can make it so that if an `Arc<PostmarkClient>` gets
//! // constructed, we also provide `Arc<dyn EmailSender>`.
//! if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
//! aero.insert(arc.clone() as Arc<dyn EmailSender>)
//! }
//! Ok(())
//! }
//! }
//!
//! impl Constructible for ConnectionPool {
//! type Error = anyhow::Error;
//! fn construct(aero: &Aero) -> Result<Self, Self::Error> {
//! // ...
//! # Ok(ConnectionPool)
//! }
//! }
//!
//! impl Constructible for MessageQueue {
//! type Error = anyhow::Error;
//! fn construct(aero: &Aero) -> Result<Self, Self::Error> {
//! // ...
//! # Ok(MessageQueue)
//! }
//! }
//! ```
pub use frunk; pub use frunk;
#[cfg(feature = "async")] #[cfg(feature = "async")]
@ -28,12 +137,15 @@ mod state;
mod sync; mod sync;
mod sync_constructible; mod sync_constructible;
pub use resource::Resource; pub use resource::{Resource, ResourceList};
pub use state::Aerosol; pub use state::Aero;
pub use sync_constructible::{Constructible, ConstructibleResource, IndirectlyConstructible}; pub use sync_constructible::{
Constructible, ConstructibleResource, ConstructibleResourceList, IndirectlyConstructible,
};
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub use async_constructible::{ pub use async_constructible::{
AsyncConstructible, AsyncConstructibleResource, IndirectlyAsyncConstructible, AsyncConstructible, AsyncConstructibleResource, AsyncConstructibleResourceList,
IndirectlyAsyncConstructible,
}; };

View file

@ -1,7 +1,14 @@
/// Define a custom `Aerosol` alias with a specific set of required types /// Define a custom `Aero` alias with a specific set of required types
///
/// Example usage:
/// ```rust
/// use aerosol::Aero;
///
/// type AppState = Aero![&'static str, i32, bool];
/// ```
#[macro_export] #[macro_export]
macro_rules! Aero { macro_rules! Aero {
($($tok:tt)*) => { ($($tok:tt)*) => {
$crate::Aerosol<$crate::frunk::HList![$($tok)*]> $crate::Aero<$crate::frunk::HList![$($tok)*]>
}; };
} }

View file

@ -1,14 +1,38 @@
use std::any::{type_name, Any}; use std::any::{type_name, Any};
use frunk::{prelude::HList, HCons, HNil};
use crate::Aero;
/// Bound on the types that can be used as an aerosol resource. /// Bound on the types that can be used as an aerosol resource.
pub trait Resource: Any + Send + Sync + Clone {} pub trait Resource: Any + Send + Sync + Clone {}
impl<T: Any + Send + Sync + Clone> Resource for T {} impl<T: Any + Send + Sync + Clone> Resource for T {}
/// A compile-time list of resource types which are statically guaranteed to be present.
pub trait ResourceList: HList + Any + Send + Sync + Clone {
/// Test at runtmie whether every resource in this list is present in the given Aero instance.
fn test<R: ResourceList>(aero: &Aero<R>) -> bool;
}
impl ResourceList for HNil {
fn test<R: ResourceList>(_aero: &Aero<R>) -> bool {
true
}
}
impl<H: Resource, T: ResourceList> ResourceList for HCons<H, T> {
fn test<R: ResourceList>(aero: &Aero<R>) -> bool {
aero.has::<H>() && T::test(aero)
}
}
pub(crate) fn missing_resource<T: Resource>() -> ! {
panic!("Resource `{}` does not exist", type_name::<T>())
}
pub(crate) fn unwrap_resource<T: Resource>(opt: Option<T>) -> T { pub(crate) fn unwrap_resource<T: Resource>(opt: Option<T>) -> T {
if let Some(value) = opt { if let Some(value) = opt {
value value
} else { } else {
panic!("Resource `{}` does not exist", type_name::<T>()) missing_resource::<T>()
} }
} }
@ -19,6 +43,17 @@ pub(crate) fn unwrap_constructed<T: Resource, U>(res: Result<U, impl Into<anyhow
} }
} }
pub(crate) fn unwrap_constructed_hlist<T, U>(res: Result<U, impl Into<anyhow::Error>>) -> U {
match res {
Ok(x) => x,
Err(e) => panic!(
"Failed to construct one of `{}`: {}",
type_name::<T>(),
e.into()
),
}
}
pub(crate) fn duplicate_resource<T: Resource>() -> ! { pub(crate) fn duplicate_resource<T: Resource>() -> ! {
panic!( panic!(
"Duplicate resource: attempted to add a second `{}`", "Duplicate resource: attempted to add a second `{}`",

View file

@ -3,18 +3,17 @@ use std::{any::Any, marker::PhantomData, sync::Arc, task::Poll};
use anymap::hashbrown::{Entry, Map}; use anymap::hashbrown::{Entry, Map};
use frunk::{ use frunk::{
hlist::{HFoldRightable, Sculptor}, hlist::{HFoldRightable, Sculptor},
prelude::HList,
HCons, HNil, Poly, HCons, HNil, Poly,
}; };
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::{ use crate::{
resource::{cyclic_resource, duplicate_resource, Resource}, resource::{cyclic_resource, duplicate_resource, missing_resource, Resource, ResourceList},
slot::{Slot, SlotDesc, ThreadOrWaker}, slot::{Slot, SlotDesc, ThreadOrWaker},
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct InnerAerosol { pub(crate) struct InnerAero {
items: Map<dyn Any + Send + Sync>, items: Map<dyn Any + Send + Sync>,
} }
@ -23,12 +22,12 @@ struct InnerAerosol {
/// Can be cheaply cloned. /// Can be cheaply cloned.
#[derive(Debug)] #[derive(Debug)]
#[repr(transparent)] #[repr(transparent)]
pub struct Aerosol<R: HList = HNil> { pub struct Aero<R: ResourceList = HNil> {
inner: Arc<RwLock<InnerAerosol>>, pub(crate) inner: Arc<RwLock<InnerAero>>,
phantom: PhantomData<Arc<R>>, pub(crate) phantom: PhantomData<Arc<R>>,
} }
impl Aerosol { impl Aero {
/// Construct a new instance of the type with no initial resources. /// Construct a new instance of the type with no initial resources.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -38,7 +37,7 @@ impl Aerosol {
} }
} }
impl<R: HList> Clone for Aerosol<R> { impl<R: ResourceList> Clone for Aero<R> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
inner: self.inner.clone(), inner: self.inner.clone(),
@ -47,41 +46,24 @@ impl<R: HList> Clone for Aerosol<R> {
} }
} }
struct AerosolBuilderFolder; struct AerosolDefaultFolder;
impl<R: HList, T: Resource> frunk::Func<(Aerosol<R>, T)> for AerosolBuilderFolder { impl<R: ResourceList, T: Resource> frunk::Func<(Aero<R>, T)> for AerosolDefaultFolder {
type Output = Aerosol<HCons<T, R>>; type Output = Aero<HCons<T, R>>;
fn call((aero, value): (Aerosol<R>, T)) -> Self::Output { fn call((aero, value): (Aero<R>, T)) -> Self::Output {
aero.with(value) aero.with(value)
} }
} }
#[doc(hidden)]
pub trait HTestable {
fn test<R: HList>(aero: &Aerosol<R>) -> bool;
}
impl HTestable for HNil {
fn test<R: HList>(_aero: &Aerosol<R>) -> bool {
true
}
}
impl<H: Resource, T: HTestable> HTestable for HCons<H, T> {
fn test<R: HList>(aero: &Aerosol<R>) -> bool {
aero.has::<H>() && T::test(aero)
}
}
impl< impl<
R: Default + HFoldRightable<Poly<AerosolBuilderFolder>, Aerosol, Output = Aerosol<R>> + HList, R: Default + HFoldRightable<Poly<AerosolDefaultFolder>, Aero, Output = Aero<R>> + ResourceList,
> Default for Aerosol<R> > Default for Aero<R>
{ {
fn default() -> Self { fn default() -> Self {
R::default().foldr(Poly(AerosolBuilderFolder), Aerosol::new()) R::default().foldr(Poly(AerosolDefaultFolder), Aero::new())
} }
} }
impl<R: HList> Aerosol<R> { impl<R: ResourceList> Aero<R> {
/// Directly insert a resource into the collection. Panics if a resource of the /// Directly insert a resource into the collection. Panics if a resource of the
/// same type already exists. /// same type already exists.
pub fn insert<T: Resource>(&self, value: T) { pub fn insert<T: Resource>(&self, value: T) {
@ -94,44 +76,44 @@ impl<R: HList> Aerosol<R> {
} }
/// Builder method equivalent to calling `insert()` but can be chained. /// Builder method equivalent to calling `insert()` but can be chained.
pub fn with<T: Resource>(self, value: T) -> Aerosol<HCons<T, R>> { pub fn with<T: Resource>(self, value: T) -> Aero<HCons<T, R>> {
self.insert(value); self.insert(value);
Aerosol { Aero {
inner: self.inner, inner: self.inner,
phantom: PhantomData, phantom: PhantomData,
} }
} }
/// Convert into a different variant of the Aerosol type. The new variant must /// Convert into a different variant of the Aero type. The new variant must
/// not require any resources which are not required as part of this type. /// not require any resources which are not required as part of this type.
pub fn into<R2: HList, I>(self) -> Aerosol<R2> pub fn into<R2: ResourceList, I>(self) -> Aero<R2>
where where
R: Sculptor<R2, I>, R: Sculptor<R2, I>,
{ {
Aerosol { Aero {
inner: self.inner, inner: self.inner,
phantom: PhantomData, phantom: PhantomData,
} }
} }
/// Reborrow as a different variant of the Aerosol type. The new variant must /// Reborrow as a different variant of the Aero type. The new variant must
/// not require any resources which are not required as part of this type. /// not require any resources which are not required as part of this type.
#[allow(clippy::should_implement_trait)] #[allow(clippy::should_implement_trait)]
pub fn as_ref<R2: HList, I>(&self) -> &Aerosol<R2> pub fn as_ref<R2: ResourceList, I>(&self) -> &Aero<R2>
where where
R: Sculptor<R2, I>, R: Sculptor<R2, I>,
{ {
// Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around // Safety: all Aero variants are `#[repr(transparent)]` wrappers around
// the same concrete type. // the same concrete type.
unsafe { std::mem::transmute(self) } unsafe { std::mem::transmute(self) }
} }
/// Try to convert into a different variant of the Aerosol type. Returns the /// Try to convert into a different variant of the Aero type. Returns the
/// original type if one or more of the required resources are not fully /// original type if one or more of the required resources are not fully
/// constructed. /// constructed.
pub fn try_into<R2: HList + HTestable>(self) -> Result<Aerosol<R2>, Self> { pub fn try_into<R2: ResourceList>(self) -> Result<Aero<R2>, Self> {
if R2::test(&self) { if R2::test(&self) {
Ok(Aerosol { Ok(Aero {
inner: self.inner, inner: self.inner,
phantom: PhantomData, phantom: PhantomData,
}) })
@ -140,13 +122,13 @@ impl<R: HList> Aerosol<R> {
} }
} }
/// Try to convert into a different variant of the Aerosol type. Returns /// Try to convert into a different variant of the Aero type. Returns
/// `None` if one or more of the required resources are not fully /// `None` if one or more of the required resources are not fully
/// constructed. /// constructed.
pub fn try_as_ref<R2: HList + HTestable>(&self) -> Option<&Aerosol<R2>> { pub fn try_as_ref<R2: ResourceList>(&self) -> Option<&Aero<R2>> {
if R2::test(self) { if R2::test(self) {
Some( Some(
// Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around // Safety: all Aero variants are `#[repr(transparent)]` wrappers around
// the same concrete type. // the same concrete type.
unsafe { std::mem::transmute(self) }, unsafe { std::mem::transmute(self) },
) )
@ -164,6 +146,24 @@ impl<R: HList> Aerosol<R> {
) )
} }
/// Assert that a resource exists, returns `self` unchanged if not
pub fn try_assert<T: Resource>(self) -> Result<Aero<HCons<T, R>>, Self> {
if self.has::<T>() {
Ok(Aero {
inner: self.inner,
phantom: PhantomData,
})
} else {
Err(self)
}
}
/// Assert that a resource exists, panic if not
pub fn assert<T: Resource>(self) -> Aero<HCons<T, R>> {
self.try_assert()
.unwrap_or_else(|_| missing_resource::<T>())
}
pub(crate) fn try_get_slot<T: Resource>(&self) -> Option<SlotDesc<T>> { pub(crate) fn try_get_slot<T: Resource>(&self) -> Option<SlotDesc<T>> {
self.inner.read().items.get().map(Slot::desc) self.inner.read().items.get().map(Slot::desc)
} }
@ -211,14 +211,14 @@ impl<R: HList> Aerosol<R> {
} }
} }
impl<R: HList> AsRef<Aerosol> for Aerosol<R> { impl<R: ResourceList> AsRef<Aero> for Aero<R> {
fn as_ref(&self) -> &Aerosol { fn as_ref(&self) -> &Aero {
Aerosol::as_ref(self) Aero::as_ref(self)
} }
} }
impl<H, T: HList> From<Aerosol<HCons<H, T>>> for Aerosol { impl<H: Resource, T: ResourceList> From<Aero<HCons<H, T>>> for Aero {
fn from(value: Aerosol<HCons<H, T>>) -> Self { fn from(value: Aero<HCons<H, T>>) -> Self {
value.into() value.into()
} }
} }
@ -227,32 +227,37 @@ impl<H, T: HList> From<Aerosol<HCons<H, T>>> for Aerosol {
mod tests { mod tests {
use crate::Aero; use crate::Aero;
use super::*;
#[test] #[test]
fn create() { fn create() {
let state = Aerosol::new().with(42); let state = Aero::new().with(42);
state.insert("Hello, world!"); state.insert("Hello, world!");
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn duplicate() { fn duplicate() {
let state = Aerosol::new().with(13); let state = Aero::new().with(13);
state.insert(42); state.insert(42);
} }
#[test] #[test]
fn default() { fn default() {
let state: Aero![i32] = Aerosol::default(); let state: Aero![i32] = Aero::default();
state.insert("Hello, world!"); state.insert("Hello, world!");
} }
#[test] #[test]
fn convert() { fn convert() {
let state: Aero![i32, String, f32] = Aerosol::default(); let state: Aero![i32, String, f32] = Aero::default();
state.insert("Hello, world!"); state.insert("Hello, world!");
let state2: Aero![f32, String] = state.into(); let state2: Aero![f32, String] = state.into();
let _state3: Aero![i32, String, f32] = state2.try_into().unwrap(); let _state3: Aero![i32, String, f32] = state2.try_into().unwrap();
} }
#[test]
fn assert() {
let state: Aero![i32, String, f32] = Aero::default();
state.insert("Hello, world!");
let _state2: Aero![&str, f32] = state.assert::<&str>().into();
}
} }

View file

@ -1,11 +1,11 @@
use std::{task::Poll, thread}; use std::{task::Poll, thread};
use frunk::{hlist::Plucker, prelude::HList}; use frunk::hlist::Plucker;
use crate::{ use crate::{
resource::{unwrap_resource, Resource}, resource::{unwrap_resource, Resource, ResourceList},
slot::SlotDesc, slot::SlotDesc,
state::Aerosol, state::Aero,
}; };
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
@ -18,7 +18,7 @@ pub fn safe_park() {
std::thread::park(); std::thread::park();
} }
impl<R: HList> Aerosol<R> { impl<R: ResourceList> Aero<R> {
/// Synchronously wait for the slot for `T` to not have a placeholder. /// Synchronously wait for the slot for `T` to not have a placeholder.
/// Returns immediately if there is no `T` present, or if `T`'s slot is filled. /// Returns immediately if there is no `T` present, or if `T`'s slot is filled.
pub(crate) fn wait_for_slot<T: Resource>(&self, insert_placeholder: bool) -> Option<T> { pub(crate) fn wait_for_slot<T: Resource>(&self, insert_placeholder: bool) -> Option<T> {
@ -54,19 +54,19 @@ mod tests {
#[test] #[test]
fn get_with() { fn get_with() {
let state = Aerosol::new().with(42); let state = Aero::new().with(42);
assert_eq!(state.get::<i32, _>(), 42); assert_eq!(state.get::<i32, _>(), 42);
} }
#[test] #[test]
fn try_get_some() { fn try_get_some() {
let state = Aerosol::new().with(42); let state = Aero::new().with(42);
assert_eq!(state.try_get::<i32>(), Some(42)); assert_eq!(state.try_get::<i32>(), Some(42));
} }
#[test] #[test]
fn try_get_none() { fn try_get_none() {
let state = Aerosol::new().with("Hello"); let state = Aero::new().with("Hello");
assert_eq!(state.try_get::<i32>(), None); assert_eq!(state.try_get::<i32>(), None);
} }
} }

View file

@ -1,11 +1,11 @@
use std::{any::Any, sync::Arc}; use std::{any::Any, marker::PhantomData, sync::Arc};
use frunk::prelude::HList; use frunk::{hlist::Sculptor, HCons, HNil};
use crate::{ use crate::{
resource::{unwrap_constructed, Resource}, resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList},
slot::SlotDesc, slot::SlotDesc,
state::Aerosol, state::Aero,
}; };
/// Implemented for values which can be constructed from other resources. /// Implemented for values which can be constructed from other resources.
@ -13,14 +13,14 @@ pub trait Constructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed. /// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync; type Error: Into<anyhow::Error> + Send + Sync;
/// Construct the resource with the provided application state. /// Construct the resource with the provided application state.
fn construct(aero: &Aerosol) -> Result<Self, Self::Error>; fn construct(aero: &Aero) -> Result<Self, Self::Error>;
/// Called after construction with the concrete resource to allow the callee /// Called after construction with the concrete resource to allow the callee
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also /// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`. /// provide an implementation of `Arc<dyn Bar>`.
fn after_construction( fn after_construction(
_this: &(dyn Any + Send + Sync), _this: &(dyn Any + Send + Sync),
_aero: &Aerosol, _aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
@ -31,13 +31,13 @@ pub trait IndirectlyConstructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed. /// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync; type Error: Into<anyhow::Error> + Send + Sync;
/// Construct the resource with the provided application state. /// Construct the resource with the provided application state.
fn construct(aero: &Aerosol) -> Result<Self, Self::Error>; fn construct(aero: &Aero) -> Result<Self, Self::Error>;
/// Called after construction with the concrete resource to allow the callee /// Called after construction with the concrete resource to allow the callee
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also /// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`. /// provide an implementation of `Arc<dyn Bar>`.
fn after_construction( fn after_construction(
_this: &(dyn Any + Send + Sync), _this: &(dyn Any + Send + Sync),
_aero: &Aerosol, _aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
@ -46,16 +46,13 @@ pub trait IndirectlyConstructible: Sized + Any + Send + Sync {
impl<T: Constructible> IndirectlyConstructible for T { impl<T: Constructible> IndirectlyConstructible for T {
type Error = T::Error; type Error = T::Error;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> { fn construct(aero: &Aero) -> Result<Self, Self::Error> {
let res = <T as Constructible>::construct(aero)?; let res = <T as Constructible>::construct(aero)?;
<T as Constructible>::after_construction(&res, aero)?; <T as Constructible>::after_construction(&res, aero)?;
Ok(res) Ok(res)
} }
fn after_construction( fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
this: &(dyn Any + Send + Sync),
aero: &Aerosol,
) -> Result<(), Self::Error> {
<T as Constructible>::after_construction(this, aero) <T as Constructible>::after_construction(this, aero)
} }
} }
@ -66,13 +63,13 @@ macro_rules! impl_constructible {
impl<$t: IndirectlyConstructible> IndirectlyConstructible for $x { impl<$t: IndirectlyConstructible> IndirectlyConstructible for $x {
type Error = $t::Error; type Error = $t::Error;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> { fn construct(aero: &Aero) -> Result<Self, Self::Error> {
let res = $y($t::construct(aero)?); let res = $y($t::construct(aero)?);
<$t as IndirectlyConstructible>::after_construction(&res, aero)?; <$t as IndirectlyConstructible>::after_construction(&res, aero)?;
Ok(res) Ok(res)
} }
fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aerosol) -> Result<(), Self::Error> { fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
<$t as IndirectlyConstructible>::after_construction(this, aero) <$t as IndirectlyConstructible>::after_construction(this, aero)
} }
} }
@ -89,10 +86,33 @@ impl_constructible! {
} }
/// Implemented for resources which can be constructed from other resources. /// Implemented for resources which can be constructed from other resources.
/// Do not implement this trait directly, instead implement `Constructible` and ensure
/// the remaining type bounds are met for the automatic implementation of `ConstructibleResource`.
pub trait ConstructibleResource: Resource + IndirectlyConstructible {} pub trait ConstructibleResource: Resource + IndirectlyConstructible {}
impl<T: Resource + IndirectlyConstructible> ConstructibleResource for T {} impl<T: Resource + IndirectlyConstructible> ConstructibleResource for T {}
impl<R: HList> Aerosol<R> { /// Automatically implemented for resource lists where every resource can be constructed.
pub trait ConstructibleResourceList: ResourceList {
/// Construct every resource in this list in the provided aerosol instance
fn construct<R: ResourceList>(aero: &Aero<R>) -> anyhow::Result<()>;
}
impl ConstructibleResourceList for HNil {
fn construct<R: ResourceList>(_aero: &Aero<R>) -> anyhow::Result<()> {
Ok(())
}
}
impl<H: ConstructibleResource, T: ConstructibleResourceList> ConstructibleResourceList
for HCons<H, T>
{
fn construct<R: ResourceList>(aero: &Aero<R>) -> anyhow::Result<()> {
aero.try_init::<H>().map_err(Into::into)?;
T::construct(aero)
}
}
impl<R: ResourceList> Aero<R> {
/// Try to get or construct an instance of `T`. /// Try to get or construct an instance of `T`.
pub fn try_obtain<T: ConstructibleResource>(&self) -> Result<T, T::Error> { pub fn try_obtain<T: ConstructibleResource>(&self) -> Result<T, T::Error> {
match self.try_get_slot() { match self.try_get_slot() {
@ -136,12 +156,56 @@ impl<R: HList> Aerosol<R> {
pub fn init<T: ConstructibleResource>(&self) { pub fn init<T: ConstructibleResource>(&self) {
unwrap_constructed::<T, _>(self.try_init::<T>()) unwrap_constructed::<T, _>(self.try_init::<T>())
} }
/// Builder method equivalent to calling `try_init()` but can be chained.
pub fn try_with_constructed<T: ConstructibleResource>(
self,
) -> Result<Aero<HCons<T, R>>, T::Error> {
self.try_init::<T>()?;
Ok(Aero {
inner: self.inner,
phantom: PhantomData,
})
}
/// Builder method equivalent to calling `try_init()` but can be chained. Panics if construction fails.
pub fn with_constructed<T: ConstructibleResource>(self) -> Aero<HCons<T, R>> {
unwrap_constructed::<T, _>(self.try_with_constructed())
}
/// Convert into a different variant of the Aero type. Any missing required resources
/// will be automatically constructed.
pub fn try_construct_remaining<R2: ResourceList, I>(self) -> anyhow::Result<Aero<R2>>
where
R2: Sculptor<R, I>,
<R2 as Sculptor<R, I>>::Remainder: ConstructibleResourceList,
{
<<R2 as Sculptor<R, I>>::Remainder>::construct(&self)?;
Ok(Aero {
inner: self.inner,
phantom: PhantomData,
})
}
/// Convert into a different variant of the Aero type. Any missing required resources
/// will be automatically constructed. Panics if construction of any missing resource fails.
pub fn construct_remaining<R2: ResourceList, I>(self) -> Aero<R2>
where
R2: Sculptor<R, I>,
<R2 as Sculptor<R, I>>::Remainder: ConstructibleResourceList,
{
unwrap_constructed_hlist::<<R2 as Sculptor<R, I>>::Remainder, _>(
self.try_construct_remaining(),
)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{convert::Infallible, thread::scope, time::Duration}; use std::{convert::Infallible, thread::scope, time::Duration};
use crate::Aero;
use super::*; use super::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -150,7 +214,7 @@ mod tests {
impl Constructible for Dummy { impl Constructible for Dummy {
type Error = Infallible; type Error = Infallible;
fn construct(_app_state: &Aerosol) -> Result<Self, Self::Error> { fn construct(_app_state: &Aero) -> Result<Self, Self::Error> {
std::thread::sleep(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(100));
Ok(Self) Ok(Self)
} }
@ -158,13 +222,13 @@ mod tests {
#[test] #[test]
fn obtain() { fn obtain() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain::<Dummy>(); state.obtain::<Dummy>();
} }
#[test] #[test]
fn obtain_race() { fn obtain_race() {
let state = Aerosol::new(); let state = Aero::new();
scope(|s| { scope(|s| {
for _ in 0..100 { for _ in 0..100 {
s.spawn(|| state.obtain::<Dummy>()); s.spawn(|| state.obtain::<Dummy>());
@ -178,7 +242,7 @@ mod tests {
impl Constructible for DummyRecursive { impl Constructible for DummyRecursive {
type Error = Infallible; type Error = Infallible;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> { fn construct(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain::<Dummy>(); aero.obtain::<Dummy>();
Ok(Self) Ok(Self)
} }
@ -186,13 +250,13 @@ mod tests {
#[test] #[test]
fn obtain_recursive() { fn obtain_recursive() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain::<DummyRecursive>(); state.obtain::<DummyRecursive>();
} }
#[test] #[test]
fn obtain_recursive_race() { fn obtain_recursive_race() {
let state = Aerosol::new(); let state = Aero::new();
scope(|s| { scope(|s| {
for _ in 0..100 { for _ in 0..100 {
s.spawn(|| state.obtain::<DummyRecursive>()); s.spawn(|| state.obtain::<DummyRecursive>());
@ -206,7 +270,7 @@ mod tests {
impl Constructible for DummyCyclic { impl Constructible for DummyCyclic {
type Error = Infallible; type Error = Infallible;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> { fn construct(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain::<DummyCyclic>(); aero.obtain::<DummyCyclic>();
Ok(Self) Ok(Self)
} }
@ -215,7 +279,7 @@ mod tests {
#[test] #[test]
#[should_panic(expected = "Cycle detected")] #[should_panic(expected = "Cycle detected")]
fn obtain_cyclic() { fn obtain_cyclic() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain::<DummyCyclic>(); state.obtain::<DummyCyclic>();
} }
@ -225,7 +289,7 @@ mod tests {
impl Constructible for DummyNonClone { impl Constructible for DummyNonClone {
type Error = Infallible; type Error = Infallible;
fn construct(_app_state: &Aerosol) -> Result<Self, Self::Error> { fn construct(_app_state: &Aero) -> Result<Self, Self::Error> {
std::thread::sleep(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(100));
Ok(Self) Ok(Self)
} }
@ -233,7 +297,7 @@ mod tests {
#[test] #[test]
fn obtain_non_clone() { fn obtain_non_clone() {
let state = Aerosol::new(); let state = Aero::new();
state.obtain::<Arc<DummyNonClone>>(); state.obtain::<Arc<DummyNonClone>>();
} }
@ -247,13 +311,13 @@ mod tests {
impl Constructible for DummyImpl { impl Constructible for DummyImpl {
type Error = Infallible; type Error = Infallible;
fn construct(_app_state: &Aerosol) -> Result<Self, Self::Error> { fn construct(_app_state: &Aero) -> Result<Self, Self::Error> {
Ok(Self) Ok(Self)
} }
fn after_construction( fn after_construction(
this: &(dyn Any + Send + Sync), this: &(dyn Any + Send + Sync),
aero: &Aerosol, aero: &Aero,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if let Some(arc) = this.downcast_ref::<Arc<Self>>() { if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
aero.insert(arc.clone() as Arc<dyn DummyTrait>) aero.insert(arc.clone() as Arc<dyn DummyTrait>)
@ -264,8 +328,22 @@ mod tests {
#[test] #[test]
fn obtain_impl() { fn obtain_impl() {
let state = Aerosol::new(); let state = Aero::new();
state.init::<Arc<DummyImpl>>(); state.init::<Arc<DummyImpl>>();
state.try_get::<Arc<dyn DummyTrait>>().unwrap(); state.try_get::<Arc<dyn DummyTrait>>().unwrap();
} }
#[test]
fn with_constructed() {
let state = Aero::new().with(42).with_constructed::<Dummy>().with("hi");
state.get::<Dummy, _>();
}
#[test]
fn construct_remaining() {
let state: Aero![i32, Dummy, DummyRecursive, &str] =
Aero::new().with(42).with("hi").construct_remaining();
state.get::<Dummy, _>();
state.get::<DummyRecursive, _>();
}
} }