mirror of
https://github.com/TECHNOFAB11/aerosol.git
synced 2025-12-11 23:50:07 +01:00
Improve documentation
This commit is contained in:
parent
a5395a5d33
commit
119f582327
9 changed files with 486 additions and 154 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, _>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/axum.rs
16
src/axum.rs
|
|
@ -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)
|
||||
|
|
|
|||
128
src/lib.rs
128
src/lib.rs
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)*]>
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 `{}`",
|
||||
|
|
|
|||
121
src/state.rs
121
src/state.rs
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
src/sync.rs
14
src/sync.rs
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, _>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue