Skip to content

Commit

Permalink
Implement statically required resources
Browse files Browse the repository at this point in the history
  • Loading branch information
Diggsey committed Aug 3, 2023
1 parent 3d7c0be commit a5395a5
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 56 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aerosol"
version = "1.0.0-alpha.4"
version = "1.0.0-alpha.5"
authors = ["Diggory Blake <diggsey@googlemail.com>"]
edition = "2018"
description = "Simple dependency injection for Rust"
Expand All @@ -23,6 +23,7 @@ axum = { version = "0.6", optional = true }
tracing = { version = "0.1", optional = true }
thiserror = { version = "1.0", optional = true }
anyhow = { version = "1.0" }
frunk = "0.4.2"

[dev-dependencies]
tokio = { version = "1.0", features = ["macros"] }
36 changes: 8 additions & 28 deletions src/async_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ use std::{
task::{Context, Poll},
};

use crate::{
resource::{unwrap_resource, Resource},
slot::SlotDesc,
state::Aerosol,
};
use frunk::prelude::HList;

use crate::{resource::Resource, slot::SlotDesc, state::Aerosol};

pub(crate) struct WaitForSlot<T: Resource> {
state: Aerosol,
pub(crate) struct WaitForSlot<R: HList, T: Resource> {
state: Aerosol<R>,
wait_index: Option<usize>,
insert_placeholder: bool,
phantom: PhantomData<fn() -> T>,
}

impl<T: Resource> Future for WaitForSlot<T> {
impl<R: HList, T: Resource> Future for WaitForSlot<R, T> {
type Output = Option<T>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Expand All @@ -28,11 +26,11 @@ impl<T: Resource> Future for WaitForSlot<T> {
}
}

impl Aerosol {
impl<R: HList> Aerosol<R> {
pub(crate) fn wait_for_slot_async<T: Resource>(
&self,
insert_placeholder: bool,
) -> WaitForSlot<T> {
) -> WaitForSlot<R, T> {
WaitForSlot {
state: self.clone(),
wait_index: None,
Expand All @@ -48,30 +46,12 @@ impl Aerosol {
SlotDesc::Placeholder => self.wait_for_slot_async::<T>(false).await,
}
}
/// Get an instance of `T` from the AppState, and panic if not found.
/// This function does not attempt to construct `T` if it does not exist.
pub async fn get_async<T: Resource>(&self) -> T {
unwrap_resource(self.try_get_async().await)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn get_with() {
let state = Aerosol::new().with(42);
assert_eq!(state.get_async::<i32>().await, 42);
}

#[tokio::test]
async fn get_inserted() {
let state = Aerosol::new();
state.insert(42);
assert_eq!(state.get_async::<i32>().await, 42);
}

#[tokio::test]
async fn try_get_some() {
let state = Aerosol::new().with(42);
Expand Down
9 changes: 5 additions & 4 deletions src/async_constructible.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{any::Any, sync::Arc};

use async_trait::async_trait;
use frunk::prelude::HList;

use crate::{
resource::{unwrap_constructed, Resource},
Expand Down Expand Up @@ -111,14 +112,14 @@ impl_async_constructible! {
pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {}
impl<T: Resource + IndirectlyAsyncConstructible> AsyncConstructibleResource for T {}

impl Aerosol {
impl<R: HList> Aerosol<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() {
Some(SlotDesc::Filled(x)) => Ok(x),
Some(SlotDesc::Placeholder) | None => match self.wait_for_slot_async::<T>(true).await {
Some(x) => Ok(x),
None => match T::construct_async(self).await {
None => match T::construct_async(self.as_ref()).await {
Ok(x) => {
self.fill_placeholder::<T>(x.clone());
Ok(x)
Expand All @@ -139,7 +140,7 @@ impl Aerosol {
pub async fn try_init_async<T: AsyncConstructibleResource>(&self) -> Result<(), T::Error> {
match self.wait_for_slot_async::<T>(true).await {
Some(_) => Ok(()),
None => match T::construct_async(self).await {
None => match T::construct_async(self.as_ref()).await {
Ok(x) => {
self.fill_placeholder::<T>(x);
Ok(())
Expand Down Expand Up @@ -340,6 +341,6 @@ mod tests {
async fn obtain_impl() {
let state = Aerosol::new();
state.init_async::<Arc<DummyImpl>>().await;
state.get_async::<Arc<dyn DummyTrait>>().await;
state.try_get_async::<Arc<dyn DummyTrait>>().await.unwrap();
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
//!
//! Provides integrations with the `axum` web framework. See the `axum` module
//! for more information.
pub use frunk;

#[cfg(feature = "async")]
mod async_;
#[cfg(feature = "async")]
mod async_constructible;
#[cfg(feature = "axum")]
pub mod axum;
mod macros;
mod resource;
mod slot;
mod state;
Expand Down
7 changes: 7 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// Define a custom `Aerosol` alias with a specific set of required types
#[macro_export]
macro_rules! Aero {
($($tok:tt)*) => {
$crate::Aerosol<$crate::frunk::HList![$($tok)*]>
};
}
1 change: 0 additions & 1 deletion src/main.rs

This file was deleted.

162 changes: 156 additions & 6 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::{any::Any, sync::Arc, task::Poll};
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::{
Expand All @@ -16,16 +21,67 @@ struct InnerAerosol {
/// Stores a collection of resources keyed on resource type.
/// Provides methods for accessing this collection.
/// Can be cheaply cloned.
#[derive(Debug, Clone, Default)]
pub struct Aerosol {
#[derive(Debug)]
#[repr(transparent)]
pub struct Aerosol<R: HList = HNil> {
inner: Arc<RwLock<InnerAerosol>>,
phantom: PhantomData<Arc<R>>,
}

impl Aerosol {
/// Construct a new instance of the type with no initial resources.
pub fn new() -> Self {
Self::default()
Self {
inner: Default::default(),
phantom: PhantomData,
}
}
}

impl<R: HList> Clone for Aerosol<R> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
phantom: PhantomData,
}
}
}

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 {
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>
{
fn default() -> Self {
R::default().foldr(Poly(AerosolBuilderFolder), Aerosol::new())
}
}

impl<R: HList> Aerosol<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) {
Expand All @@ -38,10 +94,76 @@ impl Aerosol {
}

/// Builder method equivalent to calling `insert()` but can be chained.
pub fn with<T: Resource>(self, value: T) -> Self {
pub fn with<T: Resource>(self, value: T) -> Aerosol<HCons<T, R>> {
self.insert(value);
self
Aerosol {
inner: self.inner,
phantom: PhantomData,
}
}

/// Convert into a different variant of the Aerosol 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>
where
R: Sculptor<R2, I>,
{
Aerosol {
inner: self.inner,
phantom: PhantomData,
}
}

/// Reborrow as a different variant of the Aerosol 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>
where
R: Sculptor<R2, I>,
{
// Safety: all Aerosol 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
/// 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> {
if R2::test(&self) {
Ok(Aerosol {
inner: self.inner,
phantom: PhantomData,
})
} else {
Err(self)
}
}

/// Try to convert into a different variant of the Aerosol 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>> {
if R2::test(self) {
Some(
// Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around
// the same concrete type.
unsafe { std::mem::transmute(self) },
)
} else {
None
}
}

/// Check if a resource with a specific type is fully constructed in this
/// aerosol instance
pub fn has<T: Resource>(&self) -> bool {
matches!(
self.inner.read().items.get::<Slot<T>>(),
Some(Slot::Filled(_))
)
}

pub(crate) fn try_get_slot<T: Resource>(&self) -> Option<SlotDesc<T>> {
self.inner.read().items.get().map(Slot::desc)
}
Expand Down Expand Up @@ -89,8 +211,22 @@ impl Aerosol {
}
}

impl<R: HList> AsRef<Aerosol> for Aerosol<R> {
fn as_ref(&self) -> &Aerosol {
Aerosol::as_ref(self)
}
}

impl<H, T: HList> From<Aerosol<HCons<H, T>>> for Aerosol {
fn from(value: Aerosol<HCons<H, T>>) -> Self {
value.into()
}
}

#[cfg(test)]
mod tests {
use crate::Aero;

use super::*;

#[test]
Expand All @@ -105,4 +241,18 @@ mod tests {
let state = Aerosol::new().with(13);
state.insert(42);
}

#[test]
fn default() {
let state: Aero![i32] = Aerosol::default();
state.insert("Hello, world!");
}

#[test]
fn convert() {
let state: Aero![i32, String, f32] = Aerosol::default();
state.insert("Hello, world!");
let state2: Aero![f32, String] = state.into();
let _state3: Aero![i32, String, f32] = state2.try_into().unwrap();
}
}
Loading

0 comments on commit a5395a5

Please sign in to comment.