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},
};
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: HList, T: Resource> {
state: Aerosol<R>,
pub(crate) struct WaitForSlot<R: ResourceList, T: Resource> {
state: Aero<R>,
wait_index: Option<usize>,
insert_placeholder: bool,
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>;
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>(
&self,
insert_placeholder: bool,
@ -54,13 +56,13 @@ mod tests {
#[tokio::test]
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));
}
#[tokio::test]
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);
}
}

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 frunk::prelude::HList;
use frunk::{hlist::Sculptor, HCons, HNil};
use crate::{
resource::{unwrap_constructed, Resource},
resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList},
slot::SlotDesc,
state::Aerosol,
state::Aero,
sync_constructible::Constructible,
};
@ -17,13 +17,13 @@ pub trait AsyncConstructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync;
/// 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
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`.
async fn after_construction_async(
_this: &(dyn Any + Send + Sync),
_aero: &Aerosol,
_aero: &Aero,
) -> Result<(), Self::Error> {
Ok(())
}
@ -32,12 +32,12 @@ pub trait AsyncConstructible: Sized + Any + Send + Sync {
#[async_trait]
impl<T: Constructible> AsyncConstructible for T {
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)
}
async fn after_construction_async(
this: &(dyn Any + Send + Sync),
aero: &Aerosol,
aero: &Aero,
) -> Result<(), Self::Error> {
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.
type Error: Into<anyhow::Error> + Send + Sync;
/// 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
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`.
async fn after_construction_async(
_this: &(dyn Any + Send + Sync),
_aero: &Aerosol,
_aero: &Aero,
) -> Result<(), Self::Error> {
Ok(())
}
@ -66,14 +66,14 @@ pub trait IndirectlyAsyncConstructible: Sized + Any + Send + Sync {
impl<T: AsyncConstructible> IndirectlyAsyncConstructible for T {
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?;
<T as AsyncConstructible>::after_construction_async(&res, aero).await?;
Ok(res)
}
async fn after_construction_async(
this: &(dyn Any + Send + Sync),
aero: &Aerosol,
aero: &Aero,
) -> Result<(), Self::Error> {
<T as AsyncConstructible>::after_construction_async(this, aero).await
}
@ -86,13 +86,13 @@ macro_rules! impl_async_constructible {
impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x {
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?);
<$t as IndirectlyAsyncConstructible>::after_construction_async(&res, aero).await?;
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
}
}
@ -109,10 +109,36 @@ impl_async_constructible! {
}
/// 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 {}
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`.
pub async fn try_obtain_async<T: AsyncConstructibleResource>(&self) -> Result<T, T::Error> {
match self.try_get_slot() {
@ -156,12 +182,58 @@ impl<R: HList> Aerosol<R> {
pub async fn init_async<T: AsyncConstructibleResource>(&self) {
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)]
mod tests {
use std::{convert::Infallible, time::Duration};
use crate::Aero;
use super::*;
#[derive(Debug, Clone)]
@ -171,7 +243,7 @@ mod tests {
impl AsyncConstructible for Dummy {
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;
Ok(Self)
}
@ -179,13 +251,13 @@ mod tests {
#[tokio::test]
async fn obtain() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain_async::<Dummy>().await;
}
#[tokio::test]
async fn obtain_race() {
let state = Aerosol::new();
let state = Aero::new();
let mut handles = Vec::new();
for _ in 0..100 {
let state = state.clone();
@ -205,7 +277,7 @@ mod tests {
impl AsyncConstructible for DummyRecursive {
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;
Ok(Self)
}
@ -213,13 +285,13 @@ mod tests {
#[tokio::test]
async fn obtain_recursive() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain_async::<DummyRecursive>().await;
}
#[tokio::test]
async fn obtain_recursive_race() {
let state = Aerosol::new();
let state = Aero::new();
let mut handles = Vec::new();
for _ in 0..100 {
let state = state.clone();
@ -236,7 +308,7 @@ mod tests {
impl AsyncConstructible for DummyCyclic {
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;
Ok(Self)
}
@ -245,7 +317,7 @@ mod tests {
#[tokio::test]
#[should_panic(expected = "Cycle detected")]
async fn obtain_cyclic() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain_async::<DummyCyclic>().await;
}
@ -255,7 +327,7 @@ mod tests {
impl Constructible for DummySync {
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));
Ok(Self)
}
@ -268,7 +340,7 @@ mod tests {
impl AsyncConstructible for DummySyncRecursive {
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;
Ok(Self)
}
@ -276,13 +348,13 @@ mod tests {
#[tokio::test]
async fn obtain_sync_recursive() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain_async::<DummySyncRecursive>().await;
}
#[tokio::test]
async fn obtain_sync_recursive_race() {
let state = Aerosol::new();
let state = Aero::new();
let mut handles = Vec::new();
for _ in 0..100 {
let state = state.clone();
@ -299,7 +371,7 @@ mod tests {
impl AsyncConstructible for DummyNonClone {
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;
Ok(Self)
}
@ -307,7 +379,7 @@ mod tests {
#[tokio::test]
async fn obtain_non_clone() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain_async::<Arc<DummyNonClone>>().await;
}
@ -322,13 +394,13 @@ mod tests {
impl AsyncConstructible for DummyImpl {
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)
}
async fn after_construction_async(
this: &(dyn Any + Send + Sync),
aero: &Aerosol,
aero: &Aero,
) -> Result<(), Self::Error> {
if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
aero.insert(arc.clone() as Arc<dyn DummyTrait>)
@ -339,8 +411,29 @@ mod tests {
#[tokio::test]
async fn obtain_impl() {
let state = Aerosol::new();
let state = Aero::new();
state.init_async::<Arc<DummyImpl>>().await;
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.
//!
//! 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;
@ -15,7 +15,7 @@ use axum::{
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
#[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);
#[async_trait]
impl<T: ConstructibleResource, S: Send + Sync> FromRequestParts<S> for Dep<T>
where
Aerosol: FromRef<S>,
Aero: FromRef<S>,
{
type Rejection = DependencyError;
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()
.await
.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);
#[async_trait]
impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T>
where
Aerosol: FromRef<S>,
Aero: FromRef<S>,
{
type Rejection = DependencyError;
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()
.await
.map(Self)

View file

@ -1,18 +1,127 @@
#![deny(missing_docs)]
//! # 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
//! `AsyncConstructibleResource` trait.
//!
//! ## `axum`
//! ### `axum`
//!
//! Provides integrations with the `axum` web framework. See the `axum` module
//! 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;
#[cfg(feature = "async")]
@ -28,12 +137,15 @@ mod state;
mod sync;
mod sync_constructible;
pub use resource::Resource;
pub use state::Aerosol;
pub use resource::{Resource, ResourceList};
pub use state::Aero;
pub use sync_constructible::{Constructible, ConstructibleResource, IndirectlyConstructible};
pub use sync_constructible::{
Constructible, ConstructibleResource, ConstructibleResourceList, IndirectlyConstructible,
};
#[cfg(feature = "async")]
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_rules! Aero {
($($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 frunk::{prelude::HList, HCons, HNil};
use crate::Aero;
/// Bound on the types that can be used as an aerosol resource.
pub trait Resource: Any + Send + Sync + Clone {}
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 {
if let Some(value) = opt {
value
} 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>() -> ! {
panic!(
"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 frunk::{
hlist::{HFoldRightable, Sculptor},
prelude::HList,
HCons, HNil, Poly,
};
use parking_lot::RwLock;
use crate::{
resource::{cyclic_resource, duplicate_resource, Resource},
resource::{cyclic_resource, duplicate_resource, missing_resource, Resource, ResourceList},
slot::{Slot, SlotDesc, ThreadOrWaker},
};
#[derive(Debug, Default)]
struct InnerAerosol {
pub(crate) struct InnerAero {
items: Map<dyn Any + Send + Sync>,
}
@ -23,12 +22,12 @@ struct InnerAerosol {
/// Can be cheaply cloned.
#[derive(Debug)]
#[repr(transparent)]
pub struct Aerosol<R: HList = HNil> {
inner: Arc<RwLock<InnerAerosol>>,
phantom: PhantomData<Arc<R>>,
pub struct Aero<R: ResourceList = HNil> {
pub(crate) inner: Arc<RwLock<InnerAero>>,
pub(crate) phantom: PhantomData<Arc<R>>,
}
impl Aerosol {
impl Aero {
/// Construct a new instance of the type with no initial resources.
pub fn new() -> 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 {
Self {
inner: self.inner.clone(),
@ -47,41 +46,24 @@ impl<R: HList> Clone for Aerosol<R> {
}
}
struct AerosolBuilderFolder;
impl<R: HList, T: Resource> frunk::Func<(Aerosol<R>, T)> for AerosolBuilderFolder {
type Output = Aerosol<HCons<T, R>>;
fn call((aero, value): (Aerosol<R>, T)) -> Self::Output {
struct AerosolDefaultFolder;
impl<R: ResourceList, T: Resource> frunk::Func<(Aero<R>, T)> for AerosolDefaultFolder {
type Output = Aero<HCons<T, R>>;
fn call((aero, value): (Aero<R>, T)) -> Self::Output {
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<
R: Default + HFoldRightable<Poly<AerosolBuilderFolder>, Aerosol, Output = Aerosol<R>> + HList,
> Default for Aerosol<R>
R: Default + HFoldRightable<Poly<AerosolDefaultFolder>, Aero, Output = Aero<R>> + ResourceList,
> Default for Aero<R>
{
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
/// same type already exists.
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.
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);
Aerosol {
Aero {
inner: self.inner,
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.
pub fn into<R2: HList, I>(self) -> Aerosol<R2>
pub fn into<R2: ResourceList, I>(self) -> Aero<R2>
where
R: Sculptor<R2, I>,
{
Aerosol {
Aero {
inner: self.inner,
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.
#[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
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.
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
/// 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) {
Ok(Aerosol {
Ok(Aero {
inner: self.inner,
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
/// 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) {
Some(
// Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around
// Safety: all Aero variants are `#[repr(transparent)]` wrappers around
// the same concrete type.
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>> {
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> {
fn as_ref(&self) -> &Aerosol {
Aerosol::as_ref(self)
impl<R: ResourceList> AsRef<Aero> for Aero<R> {
fn as_ref(&self) -> &Aero {
Aero::as_ref(self)
}
}
impl<H, T: HList> From<Aerosol<HCons<H, T>>> for Aerosol {
fn from(value: Aerosol<HCons<H, T>>) -> Self {
impl<H: Resource, T: ResourceList> From<Aero<HCons<H, T>>> for Aero {
fn from(value: Aero<HCons<H, T>>) -> Self {
value.into()
}
}
@ -227,32 +227,37 @@ impl<H, T: HList> From<Aerosol<HCons<H, T>>> for Aerosol {
mod tests {
use crate::Aero;
use super::*;
#[test]
fn create() {
let state = Aerosol::new().with(42);
let state = Aero::new().with(42);
state.insert("Hello, world!");
}
#[test]
#[should_panic]
fn duplicate() {
let state = Aerosol::new().with(13);
let state = Aero::new().with(13);
state.insert(42);
}
#[test]
fn default() {
let state: Aero![i32] = Aerosol::default();
let state: Aero![i32] = Aero::default();
state.insert("Hello, world!");
}
#[test]
fn convert() {
let state: Aero![i32, String, f32] = Aerosol::default();
let state: Aero![i32, String, f32] = Aero::default();
state.insert("Hello, world!");
let state2: Aero![f32, String] = state.into();
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 frunk::{hlist::Plucker, prelude::HList};
use frunk::hlist::Plucker;
use crate::{
resource::{unwrap_resource, Resource},
resource::{unwrap_resource, Resource, ResourceList},
slot::SlotDesc,
state::Aerosol,
state::Aero,
};
#[cfg(target_family = "wasm")]
@ -18,7 +18,7 @@ pub fn safe_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.
/// 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> {
@ -54,19 +54,19 @@ mod tests {
#[test]
fn get_with() {
let state = Aerosol::new().with(42);
let state = Aero::new().with(42);
assert_eq!(state.get::<i32, _>(), 42);
}
#[test]
fn try_get_some() {
let state = Aerosol::new().with(42);
let state = Aero::new().with(42);
assert_eq!(state.try_get::<i32>(), Some(42));
}
#[test]
fn try_get_none() {
let state = Aerosol::new().with("Hello");
let state = Aero::new().with("Hello");
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::{
resource::{unwrap_constructed, Resource},
resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList},
slot::SlotDesc,
state::Aerosol,
state::Aero,
};
/// 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.
type Error: Into<anyhow::Error> + Send + Sync;
/// 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
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`.
fn after_construction(
_this: &(dyn Any + Send + Sync),
_aero: &Aerosol,
_aero: &Aero,
) -> Result<(), Self::Error> {
Ok(())
}
@ -31,13 +31,13 @@ pub trait IndirectlyConstructible: Sized + Any + Send + Sync {
/// Error type for when resource fails to be constructed.
type Error: Into<anyhow::Error> + Send + Sync;
/// 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
/// to provide additional resources. Can be used by eg. an `Arc<Foo>` to also
/// provide an implementation of `Arc<dyn Bar>`.
fn after_construction(
_this: &(dyn Any + Send + Sync),
_aero: &Aerosol,
_aero: &Aero,
) -> Result<(), Self::Error> {
Ok(())
}
@ -46,16 +46,13 @@ pub trait IndirectlyConstructible: Sized + Any + Send + Sync {
impl<T: Constructible> IndirectlyConstructible for T {
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)?;
<T as Constructible>::after_construction(&res, aero)?;
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 Constructible>::after_construction(this, aero)
}
}
@ -66,13 +63,13 @@ macro_rules! impl_constructible {
impl<$t: IndirectlyConstructible> IndirectlyConstructible for $x {
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)?);
<$t as IndirectlyConstructible>::after_construction(&res, aero)?;
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)
}
}
@ -89,10 +86,33 @@ impl_constructible! {
}
/// 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 {}
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`.
pub fn try_obtain<T: ConstructibleResource>(&self) -> Result<T, T::Error> {
match self.try_get_slot() {
@ -136,12 +156,56 @@ impl<R: HList> Aerosol<R> {
pub fn init<T: ConstructibleResource>(&self) {
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)]
mod tests {
use std::{convert::Infallible, thread::scope, time::Duration};
use crate::Aero;
use super::*;
#[derive(Debug, Clone)]
@ -150,7 +214,7 @@ mod tests {
impl Constructible for Dummy {
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));
Ok(Self)
}
@ -158,13 +222,13 @@ mod tests {
#[test]
fn obtain() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain::<Dummy>();
}
#[test]
fn obtain_race() {
let state = Aerosol::new();
let state = Aero::new();
scope(|s| {
for _ in 0..100 {
s.spawn(|| state.obtain::<Dummy>());
@ -178,7 +242,7 @@ mod tests {
impl Constructible for DummyRecursive {
type Error = Infallible;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> {
fn construct(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain::<Dummy>();
Ok(Self)
}
@ -186,13 +250,13 @@ mod tests {
#[test]
fn obtain_recursive() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain::<DummyRecursive>();
}
#[test]
fn obtain_recursive_race() {
let state = Aerosol::new();
let state = Aero::new();
scope(|s| {
for _ in 0..100 {
s.spawn(|| state.obtain::<DummyRecursive>());
@ -206,7 +270,7 @@ mod tests {
impl Constructible for DummyCyclic {
type Error = Infallible;
fn construct(aero: &Aerosol) -> Result<Self, Self::Error> {
fn construct(aero: &Aero) -> Result<Self, Self::Error> {
aero.obtain::<DummyCyclic>();
Ok(Self)
}
@ -215,7 +279,7 @@ mod tests {
#[test]
#[should_panic(expected = "Cycle detected")]
fn obtain_cyclic() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain::<DummyCyclic>();
}
@ -225,7 +289,7 @@ mod tests {
impl Constructible for DummyNonClone {
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));
Ok(Self)
}
@ -233,7 +297,7 @@ mod tests {
#[test]
fn obtain_non_clone() {
let state = Aerosol::new();
let state = Aero::new();
state.obtain::<Arc<DummyNonClone>>();
}
@ -247,13 +311,13 @@ mod tests {
impl Constructible for DummyImpl {
type Error = Infallible;
fn construct(_app_state: &Aerosol) -> Result<Self, Self::Error> {
fn construct(_app_state: &Aero) -> Result<Self, Self::Error> {
Ok(Self)
}
fn after_construction(
this: &(dyn Any + Send + Sync),
aero: &Aerosol,
aero: &Aero,
) -> Result<(), Self::Error> {
if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
aero.insert(arc.clone() as Arc<dyn DummyTrait>)
@ -264,8 +328,22 @@ mod tests {
#[test]
fn obtain_impl() {
let state = Aerosol::new();
let state = Aero::new();
state.init::<Arc<DummyImpl>>();
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, _>();
}
}