├── .github └── workflows │ ├── publish.yml │ └── toolchain.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md └── src ├── async_.rs ├── async_constructible.rs ├── axum.rs ├── lib.rs ├── macros.rs ├── resource.rs ├── slot.rs ├── state.rs ├── sync.rs └── sync_constructible.rs /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | name: Publish 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: dtolnay/rust-toolchain@stable 14 | - run: cargo login ${{secrets.CARGO_TOKEN}} 15 | - run: cargo publish 16 | -------------------------------------------------------------------------------- /.github/workflows/toolchain.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | args: --all-features 20 | 21 | fmt: 22 | name: Rustfmt 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - run: rustup component add rustfmt 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: -- --check 36 | 37 | clippy: 38 | name: Clippy 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | - run: rustup component add clippy 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: clippy 51 | args: --all-features -- -D warnings 52 | 53 | test: 54 | name: Test 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v2 58 | - uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | override: true 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: test 66 | args: --all-features 67 | 68 | test_minimum: 69 | name: Test (1.75.0) 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v2 73 | - uses: actions-rs/toolchain@v1 74 | with: 75 | profile: minimal 76 | toolchain: 1.75.0 77 | override: true 78 | - uses: actions-rs/cargo@v1 79 | with: 80 | command: test 81 | args: --all-features 82 | 83 | test_beta: 84 | name: Test (Beta) 85 | runs-on: ubuntu-latest 86 | steps: 87 | - uses: actions/checkout@v2 88 | - uses: actions-rs/toolchain@v1 89 | with: 90 | profile: minimal 91 | toolchain: beta 92 | override: true 93 | - uses: actions-rs/cargo@v1 94 | with: 95 | command: test 96 | args: --all-features 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all" 3 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aerosol" 3 | version = "1.1.0" 4 | authors = ["Diggory Blake "] 5 | edition = "2018" 6 | description = "Simple dependency injection for Rust" 7 | repository = "https://github.com/Diggsey/aerosol" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [package.metadata.docs.rs] 11 | all-features = true 12 | 13 | [features] 14 | default = [] 15 | async = ["async-trait"] 16 | axum = ["dep:axum", "async", "tracing", "thiserror"] 17 | axum-extra = ["axum", "dep:axum-extra"] 18 | 19 | [dependencies] 20 | parking_lot = "0.12.1" 21 | anymap = { package = "anymap3", version = "1.0.0", features = ["hashbrown"] } 22 | async-trait = { version = "0.1", optional = true } 23 | axum = { version = "0.8.0", optional = true } 24 | axum-extra = { version = "0.10.0", optional = true, features = [ 25 | "cookie-private", 26 | ] } 27 | tracing = { version = "0.1", optional = true } 28 | thiserror = { version = "1.0", optional = true } 29 | anyhow = { version = "1.0" } 30 | frunk = "0.4.2" 31 | 32 | [dev-dependencies] 33 | tokio = { version = "1.0", features = ["macros"] } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aerosol [![GHA Build Status](https://github.com/Diggsey/aerosol/workflows/CI/badge.svg)](https://github.com/Diggsey/aerosol/actions?query=workflow%3ACI) 2 | 3 | Simple dependency injection for Rust 4 | 5 | [Documentation](https://docs.rs/aerosol/) 6 | -------------------------------------------------------------------------------- /src/async_.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | marker::PhantomData, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::{ 9 | resource::{Resource, ResourceList}, 10 | slot::SlotDesc, 11 | state::Aero, 12 | }; 13 | 14 | pub(crate) struct WaitForSlot { 15 | state: Aero, 16 | wait_index: Option, 17 | insert_placeholder: bool, 18 | phantom: PhantomData T>, 19 | } 20 | 21 | impl Future for WaitForSlot { 22 | type Output = Option; 23 | 24 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 25 | let this = self.get_mut(); 26 | this.state 27 | .poll_for_slot(&mut this.wait_index, || cx.waker(), this.insert_placeholder) 28 | } 29 | } 30 | 31 | impl Aero { 32 | pub(crate) fn wait_for_slot_async( 33 | &self, 34 | insert_placeholder: bool, 35 | ) -> WaitForSlot { 36 | WaitForSlot { 37 | state: self.clone(), 38 | wait_index: None, 39 | insert_placeholder, 40 | phantom: PhantomData, 41 | } 42 | } 43 | /// Tries to get an instance of `T` from the AppState. Returns `None` if there is no such instance. 44 | /// This function does not attempt to construct `T` if it does not exist. 45 | pub async fn try_get_async(&self) -> Option { 46 | match self.try_get_slot()? { 47 | SlotDesc::Filled(x) => Some(x), 48 | SlotDesc::Placeholder => self.wait_for_slot_async::(false).await, 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[tokio::test] 58 | async fn try_get_some() { 59 | let state = Aero::new().with(42); 60 | assert_eq!(state.try_get_async::().await, Some(42)); 61 | } 62 | 63 | #[tokio::test] 64 | async fn try_get_none() { 65 | let state = Aero::new().with("Hello"); 66 | assert_eq!(state.try_get_async::().await, None); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/async_constructible.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData, sync::Arc}; 2 | 3 | use async_trait::async_trait; 4 | use frunk::{hlist::Sculptor, HCons, HNil}; 5 | 6 | use crate::{ 7 | resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList}, 8 | slot::SlotDesc, 9 | state::Aero, 10 | sync_constructible::Constructible, 11 | }; 12 | 13 | /// Implemented for values which can be constructed asynchronously from other 14 | /// resources. Requires feature `async`. 15 | #[async_trait] 16 | pub trait AsyncConstructible: Sized + Any + Send + Sync { 17 | /// Error type for when resource fails to be constructed. 18 | type Error: Into + Send + Sync; 19 | /// Construct the resource with the provided application state. 20 | async fn construct_async(aero: &Aero) -> Result; 21 | /// Called after construction with the concrete resource to allow the callee 22 | /// to provide additional resources. Can be used by eg. an `Arc` to also 23 | /// provide an implementation of `Arc`. 24 | async fn after_construction_async( 25 | _this: &(dyn Any + Send + Sync), 26 | _aero: &Aero, 27 | ) -> Result<(), Self::Error> { 28 | Ok(()) 29 | } 30 | } 31 | 32 | #[async_trait] 33 | impl AsyncConstructible for T { 34 | type Error = ::Error; 35 | async fn construct_async(aero: &Aero) -> Result { 36 | Self::construct(aero) 37 | } 38 | async fn after_construction_async( 39 | this: &(dyn Any + Send + Sync), 40 | aero: &Aero, 41 | ) -> Result<(), Self::Error> { 42 | Self::after_construction(this, aero) 43 | } 44 | } 45 | 46 | /// Automatically implemented for values which can be indirectly asynchronously constructed from other resources. 47 | /// Requires feature `async`. 48 | #[async_trait] 49 | pub trait IndirectlyAsyncConstructible: Sized + Any + Send + Sync { 50 | /// Error type for when resource fails to be constructed. 51 | type Error: Into + Send + Sync; 52 | /// Construct the resource with the provided application state. 53 | async fn construct_async(aero: &Aero) -> Result; 54 | /// Called after construction with the concrete resource to allow the callee 55 | /// to provide additional resources. Can be used by eg. an `Arc` to also 56 | /// provide an implementation of `Arc`. 57 | async fn after_construction_async( 58 | _this: &(dyn Any + Send + Sync), 59 | _aero: &Aero, 60 | ) -> Result<(), Self::Error> { 61 | Ok(()) 62 | } 63 | } 64 | 65 | #[async_trait] 66 | impl IndirectlyAsyncConstructible for T { 67 | type Error = T::Error; 68 | 69 | async fn construct_async(aero: &Aero) -> Result { 70 | let res = ::construct_async(aero).await?; 71 | ::after_construction_async(&res, aero).await?; 72 | Ok(res) 73 | } 74 | async fn after_construction_async( 75 | this: &(dyn Any + Send + Sync), 76 | aero: &Aero, 77 | ) -> Result<(), Self::Error> { 78 | ::after_construction_async(this, aero).await 79 | } 80 | } 81 | 82 | macro_rules! impl_async_constructible { 83 | (<$t:ident>; $($x:ty: $y:expr;)*) => { 84 | $( 85 | #[async_trait] 86 | impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x { 87 | type Error = $t::Error; 88 | 89 | async fn construct_async(aero: &Aero) -> Result { 90 | let res = $y($t::construct_async(aero).await?); 91 | <$t as IndirectlyAsyncConstructible>::after_construction_async(&res as &(dyn Any + Send + Sync), aero).await?; 92 | Ok(res) 93 | } 94 | 95 | async fn after_construction_async(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> { 96 | <$t as IndirectlyAsyncConstructible>::after_construction_async(this, aero).await 97 | } 98 | } 99 | )* 100 | }; 101 | } 102 | impl_async_constructible! { 103 | ; 104 | Arc: Arc::new; 105 | std::sync::Mutex: std::sync::Mutex::new; 106 | parking_lot::Mutex: parking_lot::Mutex::new; 107 | std::sync::RwLock: std::sync::RwLock::new; 108 | parking_lot::RwLock: parking_lot::RwLock::new; 109 | } 110 | 111 | /// Implemented for resources which can be asynchronously constructed from other resources. Requires feature `async`. 112 | /// Do not implement this trait directly, instead implement `AsyncConstructible` and ensure 113 | /// the remaining type bounds are met for the automatic implementation of `AsyncConstructibleResource`. 114 | pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {} 115 | impl AsyncConstructibleResource for T {} 116 | 117 | /// Automatically implemented for resource lists where every resource can be asynchronously constructed. 118 | #[async_trait] 119 | pub trait AsyncConstructibleResourceList: ResourceList { 120 | /// Construct every resource in this list in the provided aerosol instance 121 | async fn construct_async(aero: &Aero) -> anyhow::Result<()>; 122 | } 123 | 124 | #[async_trait] 125 | impl AsyncConstructibleResourceList for HNil { 126 | async fn construct_async(_aero: &Aero) -> anyhow::Result<()> { 127 | Ok(()) 128 | } 129 | } 130 | 131 | #[async_trait] 132 | impl 133 | AsyncConstructibleResourceList for HCons 134 | { 135 | async fn construct_async(aero: &Aero) -> anyhow::Result<()> { 136 | aero.try_init_async::().await.map_err(Into::into)?; 137 | T::construct_async(aero).await 138 | } 139 | } 140 | 141 | impl Aero { 142 | /// Try to get or construct an instance of `T` asynchronously. Requires feature `async`. 143 | pub async fn try_obtain_async(&self) -> Result { 144 | match self.try_get_slot() { 145 | Some(SlotDesc::Filled(x)) => Ok(x), 146 | Some(SlotDesc::Placeholder) | None => match self.wait_for_slot_async::(true).await { 147 | Some(x) => Ok(x), 148 | None => match T::construct_async(self.as_ref()).await { 149 | Ok(x) => { 150 | self.fill_placeholder::(x.clone()); 151 | Ok(x) 152 | } 153 | Err(e) => { 154 | self.clear_placeholder::(); 155 | Err(e) 156 | } 157 | }, 158 | }, 159 | } 160 | } 161 | /// Get or construct an instance of `T` asynchronously. Panics if unable. Requires feature `async`. 162 | pub async fn obtain_async(&self) -> T { 163 | unwrap_constructed::(self.try_obtain_async::().await) 164 | } 165 | /// Try to initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. 166 | pub async fn try_init_async(&self) -> Result<(), T::Error> { 167 | match self.wait_for_slot_async::(true).await { 168 | Some(_) => Ok(()), 169 | None => match T::construct_async(self.as_ref()).await { 170 | Ok(x) => { 171 | self.fill_placeholder::(x); 172 | Ok(()) 173 | } 174 | Err(e) => { 175 | self.clear_placeholder::(); 176 | Err(e) 177 | } 178 | }, 179 | } 180 | } 181 | /// Initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. Panics if unable. 182 | pub async fn init_async(&self) { 183 | unwrap_constructed::(self.try_init_async::().await) 184 | } 185 | 186 | /// Builder method equivalent to calling `try_init_async()` but can be chained. 187 | pub async fn try_with_constructed_async( 188 | self, 189 | ) -> Result>, T::Error> { 190 | self.try_init_async::().await?; 191 | Ok(Aero { 192 | inner: self.inner, 193 | phantom: PhantomData, 194 | }) 195 | } 196 | 197 | /// Builder method equivalent to calling `try_init_async()` but can be chained. Panics if construction fails. 198 | pub async fn with_constructed_async(self) -> Aero> { 199 | unwrap_constructed::(self.try_with_constructed_async().await) 200 | } 201 | 202 | /// Convert into a different variant of the Aero type. Any missing required resources 203 | /// will be automatically asynchronously constructed. 204 | pub async fn try_construct_remaining_async(self) -> anyhow::Result> 205 | where 206 | R2: Sculptor + ResourceList, 207 | >::Remainder: AsyncConstructibleResourceList, 208 | { 209 | <>::Remainder>::construct_async(&self).await?; 210 | Ok(Aero { 211 | inner: self.inner, 212 | phantom: PhantomData, 213 | }) 214 | } 215 | 216 | /// Convert into a different variant of the Aero type. Any missing required resources 217 | /// will be automatically asynchronously constructed. Panics if construction of any missing resource fails. 218 | pub async fn construct_remaining_async(self) -> Aero 219 | where 220 | R2: Sculptor + ResourceList, 221 | >::Remainder: AsyncConstructibleResourceList, 222 | { 223 | unwrap_constructed_hlist::<>::Remainder, _>( 224 | self.try_construct_remaining_async().await, 225 | ) 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use std::{convert::Infallible, time::Duration}; 232 | 233 | use crate::Aero; 234 | 235 | use super::*; 236 | 237 | #[derive(Debug, Clone)] 238 | struct Dummy; 239 | 240 | #[async_trait] 241 | impl AsyncConstructible for Dummy { 242 | type Error = Infallible; 243 | 244 | async fn construct_async(_app_state: &Aero) -> Result { 245 | tokio::time::sleep(Duration::from_millis(100)).await; 246 | Ok(Self) 247 | } 248 | } 249 | 250 | #[tokio::test] 251 | async fn obtain() { 252 | let state = Aero::new(); 253 | state.obtain_async::().await; 254 | } 255 | 256 | #[tokio::test] 257 | async fn obtain_race() { 258 | let state = Aero::new(); 259 | let mut handles = Vec::new(); 260 | for _ in 0..100 { 261 | let state = state.clone(); 262 | handles.push(tokio::spawn(async move { 263 | state.obtain_async::().await; 264 | })); 265 | } 266 | for handle in handles { 267 | handle.await.unwrap(); 268 | } 269 | } 270 | 271 | #[derive(Debug, Clone)] 272 | struct DummyRecursive; 273 | 274 | #[async_trait] 275 | impl AsyncConstructible for DummyRecursive { 276 | type Error = Infallible; 277 | 278 | async fn construct_async(aero: &Aero) -> Result { 279 | aero.obtain_async::().await; 280 | Ok(Self) 281 | } 282 | } 283 | 284 | #[tokio::test] 285 | async fn obtain_recursive() { 286 | let state = Aero::new(); 287 | state.obtain_async::().await; 288 | } 289 | 290 | #[tokio::test] 291 | async fn obtain_recursive_race() { 292 | let state = Aero::new(); 293 | let mut handles = Vec::new(); 294 | for _ in 0..100 { 295 | let state = state.clone(); 296 | handles.push(tokio::spawn(async move { 297 | state.obtain_async::().await; 298 | })); 299 | } 300 | } 301 | 302 | #[derive(Debug, Clone)] 303 | struct DummyCyclic; 304 | 305 | #[async_trait] 306 | impl AsyncConstructible for DummyCyclic { 307 | type Error = Infallible; 308 | 309 | async fn construct_async(aero: &Aero) -> Result { 310 | aero.obtain_async::().await; 311 | Ok(Self) 312 | } 313 | } 314 | 315 | #[tokio::test] 316 | #[should_panic(expected = "Cycle detected")] 317 | async fn obtain_cyclic() { 318 | let state = Aero::new(); 319 | state.obtain_async::().await; 320 | } 321 | 322 | #[derive(Debug, Clone)] 323 | struct DummySync; 324 | 325 | impl Constructible for DummySync { 326 | type Error = Infallible; 327 | 328 | fn construct(_app_state: &Aero) -> Result { 329 | std::thread::sleep(Duration::from_millis(100)); 330 | Ok(Self) 331 | } 332 | } 333 | 334 | #[derive(Debug, Clone)] 335 | struct DummySyncRecursive; 336 | 337 | #[async_trait] 338 | impl AsyncConstructible for DummySyncRecursive { 339 | type Error = Infallible; 340 | 341 | async fn construct_async(aero: &Aero) -> Result { 342 | aero.obtain_async::().await; 343 | Ok(Self) 344 | } 345 | } 346 | 347 | #[tokio::test] 348 | async fn obtain_sync_recursive() { 349 | let state = Aero::new(); 350 | state.obtain_async::().await; 351 | } 352 | 353 | #[tokio::test] 354 | async fn obtain_sync_recursive_race() { 355 | let state = Aero::new(); 356 | let mut handles = Vec::new(); 357 | for _ in 0..100 { 358 | let state = state.clone(); 359 | handles.push(tokio::spawn(async move { 360 | state.obtain_async::().await; 361 | })); 362 | } 363 | } 364 | 365 | #[derive(Debug)] 366 | struct DummyNonClone; 367 | 368 | #[async_trait] 369 | impl AsyncConstructible for DummyNonClone { 370 | type Error = Infallible; 371 | 372 | async fn construct_async(_app_state: &Aero) -> Result { 373 | tokio::time::sleep(Duration::from_millis(100)).await; 374 | Ok(Self) 375 | } 376 | } 377 | 378 | #[tokio::test] 379 | async fn obtain_non_clone() { 380 | let state = Aero::new(); 381 | state.obtain_async::>().await; 382 | } 383 | 384 | trait DummyTrait: Send + Sync {} 385 | 386 | #[derive(Debug)] 387 | struct DummyImpl; 388 | 389 | impl DummyTrait for DummyImpl {} 390 | 391 | #[async_trait] 392 | impl AsyncConstructible for DummyImpl { 393 | type Error = Infallible; 394 | 395 | async fn construct_async(_app_state: &Aero) -> Result { 396 | Ok(Self) 397 | } 398 | 399 | async fn after_construction_async( 400 | this: &(dyn Any + Send + Sync), 401 | aero: &Aero, 402 | ) -> Result<(), Self::Error> { 403 | if let Some(arc) = this.downcast_ref::>() { 404 | aero.insert(arc.clone() as Arc) 405 | } 406 | Ok(()) 407 | } 408 | } 409 | 410 | #[tokio::test] 411 | async fn obtain_impl() { 412 | let state = Aero::new(); 413 | state.init_async::>().await; 414 | state.try_get_async::>().await.unwrap(); 415 | } 416 | 417 | #[tokio::test] 418 | async fn with_constructed_async() { 419 | let state = Aero::new() 420 | .with(42) 421 | .with_constructed_async::() 422 | .await 423 | .with("hi"); 424 | state.get::(); 425 | } 426 | 427 | #[tokio::test] 428 | async fn construct_remaining_async() { 429 | let state: Aero![i32, Dummy, DummyRecursive, &str] = Aero::new() 430 | .with(42) 431 | .with("hi") 432 | .construct_remaining_async() 433 | .await; 434 | state.get::(); 435 | state.get::(); 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /src/axum.rs: -------------------------------------------------------------------------------- 1 | //! Integration with the `axum` web framework. 2 | //! 3 | //! Provides the `Dep` and `Obtain` axum extractors for easily accessing 4 | //! resources from within route handlers. 5 | //! 6 | //! To make use of these extractors, your application state must either be 7 | //! an `Aero`, or you must implement `FromRef` for `Aero`. 8 | 9 | use std::any::type_name; 10 | use std::convert::Infallible; 11 | 12 | use axum::{ 13 | extract::{FromRef, FromRequestParts, OptionalFromRequestParts}, 14 | http::{request::Parts, StatusCode}, 15 | response::{IntoResponse, Response}, 16 | }; 17 | use frunk::HCons; 18 | 19 | use crate::{Aero, AsyncConstructibleResource, Resource, ResourceList}; 20 | 21 | /// Type of axum Rejection returned when a resource cannot be acquired 22 | #[derive(Debug, thiserror::Error)] 23 | pub enum DependencyError { 24 | /// Tried to get a resource which did not exist. Use `Obtain(..)` if you want aerosol to 25 | /// try to construct the resource on demand. 26 | #[error("Resource `{name}` does not exist")] 27 | DoesNotExist { 28 | /// Name of the resource type 29 | name: &'static str, 30 | }, 31 | /// Tried and failed to construct a resource. 32 | #[error("Failed to construct `{name}`: {source}")] 33 | FailedToConstruct { 34 | /// Name of the resource type 35 | name: &'static str, 36 | /// Error returned by the resource constructor 37 | #[source] 38 | source: anyhow::Error, 39 | }, 40 | } 41 | 42 | impl IntoResponse for DependencyError { 43 | fn into_response(self) -> Response { 44 | tracing::error!("{}", self); 45 | StatusCode::INTERNAL_SERVER_ERROR.into_response() 46 | } 47 | } 48 | 49 | impl DependencyError { 50 | pub(crate) fn does_not_exist() -> Self { 51 | Self::DoesNotExist { 52 | name: type_name::(), 53 | } 54 | } 55 | pub(crate) fn failed_to_construct(error: impl Into) -> Self { 56 | Self::FailedToConstruct { 57 | name: type_name::(), 58 | source: error.into(), 59 | } 60 | } 61 | } 62 | 63 | /// Get an already-existing resource from the state. Equivalent to calling `Aero::try_get_async`. 64 | pub struct Dep(pub T); 65 | 66 | impl FromRequestParts for Dep 67 | where 68 | Aero: FromRef, 69 | { 70 | type Rejection = DependencyError; 71 | 72 | async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result { 73 | Aero::from_ref(state) 74 | .try_get_async() 75 | .await 76 | .map(Self) 77 | .ok_or_else(DependencyError::does_not_exist::) 78 | } 79 | } 80 | 81 | impl OptionalFromRequestParts for Dep 82 | where 83 | Aero: FromRef, 84 | { 85 | type Rejection = Infallible; 86 | 87 | // Required method 88 | async fn from_request_parts( 89 | parts: &mut Parts, 90 | state: &S, 91 | ) -> Result, Self::Rejection> { 92 | Ok( 93 | >::from_request_parts(parts, state) 94 | .await 95 | .ok(), 96 | ) 97 | } 98 | } 99 | 100 | /// Get a resource from the state, or construct it if it doesn't exist. Equivalent to calling `Aero::try_obtain_async`. 101 | pub struct Obtain(pub T); 102 | 103 | impl FromRequestParts for Obtain 104 | where 105 | Aero: FromRef, 106 | { 107 | type Rejection = DependencyError; 108 | 109 | async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result { 110 | Aero::from_ref(state) 111 | .try_obtain_async() 112 | .await 113 | .map(Self) 114 | .map_err(DependencyError::failed_to_construct::) 115 | } 116 | } 117 | 118 | impl OptionalFromRequestParts for Obtain 119 | where 120 | Aero: FromRef, 121 | { 122 | type Rejection = Infallible; 123 | 124 | // Required method 125 | async fn from_request_parts( 126 | parts: &mut Parts, 127 | state: &S, 128 | ) -> Result, Self::Rejection> { 129 | Ok( 130 | >::from_request_parts(parts, state) 131 | .await 132 | .ok(), 133 | ) 134 | } 135 | } 136 | 137 | impl FromRef>> for Aero { 138 | fn from_ref(input: &Aero>) -> Self { 139 | input.clone().into() 140 | } 141 | } 142 | #[cfg(feature = "axum-extra")] 143 | mod extra_impls { 144 | use axum::extract::FromRef; 145 | 146 | use crate::{Aero, ResourceList}; 147 | 148 | impl FromRef> for axum_extra::extract::cookie::Key { 149 | fn from_ref(input: &Aero) -> Self { 150 | input.try_get().expect("Missing cookie key") 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | //! # aerosol 3 | //! Simple but powerful dependency injection for Rust. 4 | //! 5 | //! This crate provides the `Aero` type, which stores dependencies (called resources) keyed by their 6 | //! type. Resources can be constructed eagerly at application startup, or on-demand when they are 7 | //! first needed. Resources can access and/or initialize other resources on creation. 8 | //! 9 | //! The crate will detect dependency cycles (if constructing resource A requires resource B which 10 | //! itself requires resource A) and will panic rather than stack overflow in that case. 11 | //! 12 | //! The `Aero` type has an optional type parameter to make certain resources *required*. When 13 | //! a resource is required it can be accessed infallibly. The `Aero![...]` macro exists to 14 | //! easily name an `Aero` with a specific set of required resources. 15 | //! 16 | //! Cloning or type casting an `Aero` type is cheap (equivalent to cloning an `Arc`). 17 | //! 18 | //! ## Optional features 19 | //! 20 | //! ### `async` 21 | //! 22 | //! Allows resources to be constructed asynchrously, and provides a corresponding 23 | //! `AsyncConstructibleResource` trait. 24 | //! 25 | //! ### `axum` 26 | //! 27 | //! Provides integrations with the `axum` web framework. See the `axum` module 28 | //! for more information. 29 | //! 30 | //! ## Example usage 31 | //! 32 | //! ```rust 33 | //! use std::{sync::Arc, any::Any}; 34 | //! 35 | //! # struct PostmarkClient; 36 | //! # #[derive(Clone)] 37 | //! # struct ConnectionPool; 38 | //! # #[derive(Clone)] 39 | //! # struct MessageQueue; 40 | //! # #[derive(Clone)] 41 | //! # struct MagicNumber(i32); 42 | //! # trait EmailSender: Send + Sync { fn send(&self) {} } 43 | //! # impl EmailSender for PostmarkClient {} 44 | //! # impl PostmarkClient { fn new() -> anyhow::Result { Ok(Self) }} 45 | //! use aerosol::{Aero, Constructible}; 46 | //! 47 | //! // Here, we can list all the things we want to guarantee are in 48 | //! // our app state. This is entirely optional, we could also just 49 | //! // use the `Aero` type with default arguments and check that 50 | //! // resources are present at runtime. 51 | //! type AppState = Aero![ 52 | //! Arc, 53 | //! Arc, 54 | //! ConnectionPool, 55 | //! MessageQueue, 56 | //! MagicNumber, 57 | //! ]; 58 | //! 59 | //! fn main() { 60 | //! let app_state: AppState = Aero::new() 61 | //! // Directly add a resource which doesn't implement `Constructible`. 62 | //! .with(MagicNumber(42)) 63 | //! // Construct an `Arc` resource in the AppState 64 | //! .with_constructed::>() 65 | //! // Check that an implementation of `EmailSender` was added as a result 66 | //! .assert::>() 67 | //! // Automatically construct anything else necessary for our AppState 68 | //! // (in this case, `ConnectionPool` and `MessageQueue`) 69 | //! .construct_remaining(); 70 | //! 71 | //! // Add an extra resource 72 | //! app_state.insert("Hello, world"); 73 | //! 74 | //! run(app_state); 75 | //! } 76 | //! 77 | //! fn run(app_state: AppState) { 78 | //! // The `get()` method is infallible because the `Arc` was 79 | //! // explicitly listed when defining our `AppState`. 80 | //! let email_sender: Arc = app_state.get(); 81 | //! email_sender.send(/* email */); 82 | //! 83 | //! // We have to use `try_get()` here because a `&str` is not guaranteed to 84 | //! // exist on our `AppState`. 85 | //! let hello_message: &str = app_state.try_get().unwrap(); 86 | //! println!("{hello_message}"); 87 | //! 88 | //! // ... more application logic 89 | //! } 90 | //! 91 | //! // The `Constructible` trait can be implemented to allow resources to be automatically 92 | //! // constructed. 93 | //! impl Constructible for PostmarkClient { 94 | //! type Error = anyhow::Error; 95 | //! 96 | //! fn construct(aero: &Aero) -> Result { 97 | //! PostmarkClient::new(/* initialize using environment variables */) 98 | //! } 99 | //! 100 | //! fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> { 101 | //! // We can use this to automatically populate extra resources on the context. 102 | //! // For example, in this case we can make it so that if an `Arc` gets 103 | //! // constructed, we also provide `Arc`. 104 | //! if let Some(arc) = this.downcast_ref::>() { 105 | //! aero.insert(arc.clone() as Arc) 106 | //! } 107 | //! Ok(()) 108 | //! } 109 | //! } 110 | //! 111 | //! impl Constructible for ConnectionPool { 112 | //! type Error = anyhow::Error; 113 | //! fn construct(aero: &Aero) -> Result { 114 | //! // ... 115 | //! # Ok(ConnectionPool) 116 | //! } 117 | //! } 118 | //! 119 | //! impl Constructible for MessageQueue { 120 | //! type Error = anyhow::Error; 121 | //! fn construct(aero: &Aero) -> Result { 122 | //! // ... 123 | //! # Ok(MessageQueue) 124 | //! } 125 | //! } 126 | //! ``` 127 | //! 128 | //! ## Implementation details 129 | //! 130 | //! The `Aero` type manages shared ownership of a map from resource types to "slots". 131 | //! For a given resource type, the corresponding "slot" can be in one of three state: 132 | //! 1) Absent. 133 | //! No instance of this resource is present in the map. 134 | //! 2) Present. 135 | //! An instance of this resource exists in the map and can be accessed immediately. 136 | //! 3) Under construction. 137 | //! An instance of this resource is currently under construction, and may be accessed 138 | //! once construction has finished. 139 | //! The slot maintains a list of threads or tasks waiting for this resource to be 140 | //! constructed, and will wake them when the resource becomes available. 141 | //! 142 | //! Resources can be constructed synchronously, or (when the feature is enabled) asynchronously. 143 | //! 144 | //! If a resource is accessed whilst under construction, the caller will wait for construction 145 | //! to complete. The caller determines whether the wait occurs synchronously or asynchronously, 146 | //! depending on whether `obtain()` or `obtain_async()` is used. 147 | //! 148 | //! It is possible (and allowed) for a thread to synchronously wait on a resource being constructed 149 | //! asynchronously in a task, or for a task to asynchronously wait on a resource being synchronously 150 | //! constructed on a thread. 151 | //! 152 | 153 | pub use frunk; 154 | 155 | #[cfg(feature = "async")] 156 | mod async_; 157 | #[cfg(feature = "async")] 158 | mod async_constructible; 159 | #[cfg(feature = "axum")] 160 | pub mod axum; 161 | mod macros; 162 | mod resource; 163 | mod slot; 164 | mod state; 165 | mod sync; 166 | mod sync_constructible; 167 | 168 | pub use resource::{Resource, ResourceList}; 169 | pub use state::Aero; 170 | 171 | pub use sync_constructible::{ 172 | Constructible, ConstructibleResource, ConstructibleResourceList, IndirectlyConstructible, 173 | }; 174 | 175 | #[cfg(feature = "async")] 176 | pub use async_constructible::{ 177 | AsyncConstructible, AsyncConstructibleResource, AsyncConstructibleResourceList, 178 | IndirectlyAsyncConstructible, 179 | }; 180 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Define a custom `Aero` alias with a specific set of required types 2 | /// 3 | /// Example usage: 4 | /// ```rust 5 | /// use aerosol::Aero; 6 | /// 7 | /// type AppState = Aero![&'static str, i32, bool]; 8 | /// ``` 9 | #[macro_export] 10 | macro_rules! Aero { 11 | ($($tok:tt)*) => { 12 | $crate::Aero<$crate::frunk::HList![$($tok)*]> 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | use std::any::{type_name, Any}; 2 | 3 | use frunk::{prelude::HList, HCons, HNil}; 4 | 5 | use crate::Aero; 6 | 7 | /// Bound on the types that can be used as an aerosol resource. 8 | pub trait Resource: Any + Send + Sync + Clone {} 9 | impl Resource for T {} 10 | 11 | /// A compile-time list of resource types which are statically guaranteed to be present. 12 | pub trait ResourceList: HList + Any + Send + Sync + Clone { 13 | /// Test at runtmie whether every resource in this list is present in the given Aero instance. 14 | fn test(aero: &Aero) -> bool; 15 | } 16 | impl ResourceList for HNil { 17 | fn test(_aero: &Aero) -> bool { 18 | true 19 | } 20 | } 21 | impl ResourceList for HCons { 22 | fn test(aero: &Aero) -> bool { 23 | aero.has::() && T::test(aero) 24 | } 25 | } 26 | 27 | pub(crate) fn missing_resource() -> ! { 28 | panic!("Resource `{}` does not exist", type_name::()) 29 | } 30 | 31 | pub(crate) fn unwrap_resource(opt: Option) -> T { 32 | if let Some(value) = opt { 33 | value 34 | } else { 35 | missing_resource::() 36 | } 37 | } 38 | 39 | pub(crate) fn unwrap_constructed(res: Result>) -> U { 40 | match res { 41 | Ok(x) => x, 42 | Err(e) => panic!("Failed to construct `{}`: {}", type_name::(), e.into()), 43 | } 44 | } 45 | 46 | pub(crate) fn unwrap_constructed_hlist(res: Result>) -> U { 47 | match res { 48 | Ok(x) => x, 49 | Err(e) => panic!( 50 | "Failed to construct one of `{}`: {}", 51 | type_name::(), 52 | e.into() 53 | ), 54 | } 55 | } 56 | 57 | pub(crate) fn duplicate_resource() -> ! { 58 | panic!( 59 | "Duplicate resource: attempted to add a second `{}`", 60 | type_name::() 61 | ) 62 | } 63 | 64 | pub(crate) fn cyclic_resource() -> ! { 65 | panic!( 66 | "Cycle detected when constructing resource `{}`", 67 | type_name::() 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /src/slot.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "async")] 2 | use std::task::Waker; 3 | use std::thread::Thread; 4 | 5 | use crate::resource::Resource; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum ThreadOrWaker { 9 | Thread(Thread), 10 | #[cfg(feature = "async")] 11 | Waker(Waker), 12 | } 13 | 14 | impl PartialEq for ThreadOrWaker { 15 | fn eq(&self, other: &Self) -> bool { 16 | match (self, other) { 17 | (Self::Thread(l0), Self::Thread(r0)) => l0.id() == r0.id(), 18 | #[cfg(feature = "async")] 19 | (Self::Waker(l0), Self::Waker(r0)) => l0.will_wake(r0), 20 | #[cfg(feature = "async")] 21 | _ => false, 22 | } 23 | } 24 | } 25 | 26 | impl From for ThreadOrWaker { 27 | fn from(value: Thread) -> Self { 28 | Self::Thread(value) 29 | } 30 | } 31 | 32 | #[cfg(feature = "async")] 33 | impl From<&Waker> for ThreadOrWaker { 34 | fn from(value: &Waker) -> Self { 35 | Self::Waker(value.clone()) 36 | } 37 | } 38 | 39 | impl ThreadOrWaker { 40 | pub fn unpark_or_wake(self) { 41 | match self { 42 | ThreadOrWaker::Thread(thread) => thread.unpark(), 43 | #[cfg(feature = "async")] 44 | ThreadOrWaker::Waker(waker) => waker.wake(), 45 | } 46 | } 47 | } 48 | 49 | pub enum Slot { 50 | Filled(T), 51 | Placeholder { 52 | owner: ThreadOrWaker, 53 | waiting: Vec, 54 | }, 55 | } 56 | 57 | impl Slot { 58 | pub fn desc(&self) -> SlotDesc { 59 | if let Slot::Filled(x) = self { 60 | SlotDesc::Filled(x.clone()) 61 | } else { 62 | SlotDesc::Placeholder 63 | } 64 | } 65 | } 66 | 67 | impl Drop for Slot { 68 | fn drop(&mut self) { 69 | if let Self::Placeholder { waiting, .. } = self { 70 | for item in waiting.drain(..) { 71 | item.unpark_or_wake(); 72 | } 73 | } 74 | } 75 | } 76 | 77 | pub enum SlotDesc { 78 | Filled(T), 79 | Placeholder, 80 | } 81 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, fmt::Debug, marker::PhantomData, sync::Arc, task::Poll}; 2 | 3 | use anymap::hashbrown::{Entry, Map}; 4 | use frunk::{ 5 | hlist::{HFoldRightable, Sculptor}, 6 | HCons, HNil, Poly, 7 | }; 8 | use parking_lot::RwLock; 9 | 10 | use crate::{ 11 | resource::{cyclic_resource, duplicate_resource, missing_resource, Resource, ResourceList}, 12 | slot::{Slot, SlotDesc, ThreadOrWaker}, 13 | }; 14 | 15 | #[derive(Debug, Default)] 16 | pub(crate) struct InnerAero { 17 | items: Map, 18 | } 19 | 20 | /// Stores a collection of resources keyed on resource type. 21 | /// Provides methods for accessing this collection. 22 | /// Can be cheaply cloned. 23 | #[repr(transparent)] 24 | pub struct Aero { 25 | pub(crate) inner: Arc>, 26 | pub(crate) phantom: PhantomData>, 27 | } 28 | 29 | impl Debug for Aero { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | self.inner.fmt(f) 32 | } 33 | } 34 | 35 | impl Aero { 36 | /// Construct a new instance of the type with no initial resources. 37 | pub fn new() -> Self { 38 | Self { 39 | inner: Default::default(), 40 | phantom: PhantomData, 41 | } 42 | } 43 | } 44 | 45 | impl Clone for Aero { 46 | fn clone(&self) -> Self { 47 | Self { 48 | inner: self.inner.clone(), 49 | phantom: PhantomData, 50 | } 51 | } 52 | } 53 | 54 | struct AerosolDefaultFolder; 55 | impl frunk::Func<(Aero, T)> for AerosolDefaultFolder { 56 | type Output = Aero>; 57 | fn call((aero, value): (Aero, T)) -> Self::Output { 58 | aero.with(value) 59 | } 60 | } 61 | 62 | impl< 63 | R: Default + HFoldRightable, Aero, Output = Aero> + ResourceList, 64 | > Default for Aero 65 | { 66 | fn default() -> Self { 67 | R::default().foldr(Poly(AerosolDefaultFolder), Aero::new()) 68 | } 69 | } 70 | 71 | impl Aero { 72 | /// Directly insert a resource into the collection. Panics if a resource of the 73 | /// same type already exists. 74 | pub fn insert(&self, value: T) { 75 | match self.inner.write().items.entry() { 76 | Entry::Occupied(_) => duplicate_resource::(), 77 | Entry::Vacant(vac) => { 78 | vac.insert(Slot::Filled(value)); 79 | } 80 | } 81 | } 82 | 83 | /// Builder method equivalent to calling `insert()` but can be chained. 84 | pub fn with(self, value: T) -> Aero> { 85 | self.insert(value); 86 | Aero { 87 | inner: self.inner, 88 | phantom: PhantomData, 89 | } 90 | } 91 | 92 | /// Convert into a different variant of the Aero type. The new variant must 93 | /// not require any resources which are not required as part of this type. 94 | pub fn into(self) -> Aero 95 | where 96 | R: Sculptor, 97 | { 98 | Aero { 99 | inner: self.inner, 100 | phantom: PhantomData, 101 | } 102 | } 103 | 104 | /// Reborrow as a different variant of the Aero type. The new variant must 105 | /// not require any resources which are not required as part of this type. 106 | #[allow(clippy::should_implement_trait)] 107 | pub fn as_ref(&self) -> &Aero 108 | where 109 | R: Sculptor, 110 | { 111 | // Safety: all Aero variants are `#[repr(transparent)]` wrappers around 112 | // the same concrete type. 113 | unsafe { std::mem::transmute(self) } 114 | } 115 | 116 | /// Try to convert into a different variant of the Aero type. Returns the 117 | /// original type if one or more of the required resources are not fully 118 | /// constructed. 119 | pub fn try_into(self) -> Result, Self> { 120 | if R2::test(&self) { 121 | Ok(Aero { 122 | inner: self.inner, 123 | phantom: PhantomData, 124 | }) 125 | } else { 126 | Err(self) 127 | } 128 | } 129 | 130 | /// Try to convert into a different variant of the Aero type. Returns 131 | /// `None` if one or more of the required resources are not fully 132 | /// constructed. 133 | pub fn try_as_ref(&self) -> Option<&Aero> { 134 | if R2::test(self) { 135 | Some( 136 | // Safety: all Aero variants are `#[repr(transparent)]` wrappers around 137 | // the same concrete type. 138 | unsafe { std::mem::transmute::<&Aero, &Aero>(self) }, 139 | ) 140 | } else { 141 | None 142 | } 143 | } 144 | 145 | /// Check if a resource with a specific type is fully constructed in this 146 | /// aerosol instance 147 | pub fn has(&self) -> bool { 148 | matches!( 149 | self.inner.read().items.get::>(), 150 | Some(Slot::Filled(_)) 151 | ) 152 | } 153 | 154 | /// Assert that a resource exists, returns `self` unchanged if not 155 | pub fn try_assert(self) -> Result>, Self> { 156 | if self.has::() { 157 | Ok(Aero { 158 | inner: self.inner, 159 | phantom: PhantomData, 160 | }) 161 | } else { 162 | Err(self) 163 | } 164 | } 165 | 166 | /// Assert that a resource exists, panic if not 167 | pub fn assert(self) -> Aero> { 168 | self.try_assert() 169 | .unwrap_or_else(|_| missing_resource::()) 170 | } 171 | 172 | pub(crate) fn try_get_slot(&self) -> Option> { 173 | self.inner.read().items.get().map(Slot::desc) 174 | } 175 | pub(crate) fn poll_for_slot>( 176 | &self, 177 | wait_index: &mut Option, 178 | thread_or_waker_fn: impl Fn() -> C, 179 | insert_placeholder: bool, 180 | ) -> Poll> { 181 | let mut guard = self.inner.write(); 182 | match guard.items.entry::>() { 183 | Entry::Occupied(mut occ) => match occ.get_mut() { 184 | Slot::Filled(x) => Poll::Ready(Some(x.clone())), 185 | Slot::Placeholder { owner, waiting } => { 186 | let current = thread_or_waker_fn().into(); 187 | if current == *owner { 188 | cyclic_resource::() 189 | } 190 | if let Some(idx) = *wait_index { 191 | waiting[idx] = current; 192 | } else { 193 | *wait_index = Some(waiting.len()); 194 | waiting.push(current); 195 | } 196 | Poll::Pending 197 | } 198 | }, 199 | Entry::Vacant(vac) => { 200 | if insert_placeholder { 201 | vac.insert(Slot::Placeholder { 202 | owner: thread_or_waker_fn().into(), 203 | waiting: Vec::new(), 204 | }); 205 | } 206 | Poll::Ready(None) 207 | } 208 | } 209 | } 210 | 211 | pub(crate) fn fill_placeholder(&self, value: T) { 212 | self.inner.write().items.insert(Slot::Filled(value)); 213 | } 214 | pub(crate) fn clear_placeholder(&self) { 215 | self.inner.write().items.remove::>(); 216 | } 217 | } 218 | 219 | impl AsRef for Aero { 220 | fn as_ref(&self) -> &Aero { 221 | Aero::as_ref(self) 222 | } 223 | } 224 | 225 | impl From>> for Aero { 226 | fn from(value: Aero>) -> Self { 227 | value.into() 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests { 233 | use crate::Aero; 234 | 235 | #[test] 236 | fn create() { 237 | let state = Aero::new().with(42); 238 | state.insert("Hello, world!"); 239 | } 240 | 241 | #[test] 242 | #[should_panic] 243 | fn duplicate() { 244 | let state = Aero::new().with(13); 245 | state.insert(42); 246 | } 247 | 248 | #[test] 249 | fn default() { 250 | let state: Aero![i32] = Aero::default(); 251 | state.insert("Hello, world!"); 252 | } 253 | 254 | #[test] 255 | fn convert() { 256 | let state: Aero![i32, String, f32] = Aero::default(); 257 | state.insert("Hello, world!"); 258 | let state2: Aero![f32, String] = state.into(); 259 | let _state3: Aero![i32, String, f32] = state2.try_into().unwrap(); 260 | } 261 | 262 | #[test] 263 | fn assert() { 264 | let state: Aero![i32, String, f32] = Aero::default(); 265 | state.insert("Hello, world!"); 266 | let _state2: Aero![&str, f32] = state.assert::<&str>().into(); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | use std::{task::Poll, thread}; 2 | 3 | use frunk::hlist::Plucker; 4 | 5 | use crate::{ 6 | resource::{unwrap_resource, Resource, ResourceList}, 7 | slot::SlotDesc, 8 | state::Aero, 9 | }; 10 | 11 | #[cfg(target_family = "wasm")] 12 | pub fn safe_park() { 13 | panic!("Cannot block on dependency construction on WASM") 14 | } 15 | 16 | #[cfg(not(target_family = "wasm"))] 17 | pub fn safe_park() { 18 | std::thread::park(); 19 | } 20 | 21 | impl Aero { 22 | /// Synchronously wait for the slot for `T` to not have a placeholder. 23 | /// Returns immediately if there is no `T` present, or if `T`'s slot is filled. 24 | pub(crate) fn wait_for_slot(&self, insert_placeholder: bool) -> Option { 25 | let mut wait_index = None; 26 | loop { 27 | match self.poll_for_slot(&mut wait_index, thread::current, insert_placeholder) { 28 | Poll::Pending => safe_park(), 29 | Poll::Ready(x) => break x, 30 | } 31 | } 32 | } 33 | 34 | /// Tries to get an instance of `T` from the AppState. Returns `None` if there is no such instance. 35 | /// This function does not attempt to construct `T` if it does not exist. 36 | pub fn try_get(&self) -> Option { 37 | match self.try_get_slot()? { 38 | SlotDesc::Filled(x) => Some(x), 39 | SlotDesc::Placeholder => self.wait_for_slot::(false), 40 | } 41 | } 42 | /// Get an instance of `T` from the AppState which is statically known to be present. 43 | pub fn get(&self) -> T 44 | where 45 | R: Plucker, 46 | { 47 | unwrap_resource(self.try_get()) 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn get_with() { 57 | let state = Aero::new().with(42); 58 | assert_eq!(state.get::(), 42); 59 | } 60 | 61 | #[test] 62 | fn try_get_some() { 63 | let state = Aero::new().with(42); 64 | assert_eq!(state.try_get::(), Some(42)); 65 | } 66 | 67 | #[test] 68 | fn try_get_none() { 69 | let state = Aero::new().with("Hello"); 70 | assert_eq!(state.try_get::(), None); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/sync_constructible.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData, sync::Arc}; 2 | 3 | use frunk::{hlist::Sculptor, HCons, HNil}; 4 | 5 | use crate::{ 6 | resource::{unwrap_constructed, unwrap_constructed_hlist, Resource, ResourceList}, 7 | slot::SlotDesc, 8 | state::Aero, 9 | }; 10 | 11 | /// Implemented for values which can be constructed from other resources. 12 | pub trait Constructible: Sized + Any + Send + Sync { 13 | /// Error type for when resource fails to be constructed. 14 | type Error: Into + Send + Sync; 15 | /// Construct the resource with the provided application state. 16 | fn construct(aero: &Aero) -> Result; 17 | 18 | /// Called after construction with the concrete resource to allow the callee 19 | /// to provide additional resources. Can be used by eg. an `Arc` to also 20 | /// provide an implementation of `Arc`. 21 | fn after_construction( 22 | _this: &(dyn Any + Send + Sync), 23 | _aero: &Aero, 24 | ) -> Result<(), Self::Error> { 25 | Ok(()) 26 | } 27 | } 28 | 29 | /// Automatically implemented for values which can be indirectly constructed from other resources. 30 | pub trait IndirectlyConstructible: Sized + Any + Send + Sync { 31 | /// Error type for when resource fails to be constructed. 32 | type Error: Into + Send + Sync; 33 | /// Construct the resource with the provided application state. 34 | fn construct(aero: &Aero) -> Result; 35 | /// Called after construction with the concrete resource to allow the callee 36 | /// to provide additional resources. Can be used by eg. an `Arc` to also 37 | /// provide an implementation of `Arc`. 38 | fn after_construction( 39 | _this: &(dyn Any + Send + Sync), 40 | _aero: &Aero, 41 | ) -> Result<(), Self::Error> { 42 | Ok(()) 43 | } 44 | } 45 | 46 | impl IndirectlyConstructible for T { 47 | type Error = T::Error; 48 | 49 | fn construct(aero: &Aero) -> Result { 50 | let res = ::construct(aero)?; 51 | ::after_construction(&res, aero)?; 52 | Ok(res) 53 | } 54 | 55 | fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> { 56 | ::after_construction(this, aero) 57 | } 58 | } 59 | 60 | macro_rules! impl_constructible { 61 | (<$t:ident>; $($x:ty: $y:expr;)*) => { 62 | $( 63 | impl<$t: IndirectlyConstructible> IndirectlyConstructible for $x { 64 | type Error = $t::Error; 65 | 66 | fn construct(aero: &Aero) -> Result { 67 | let res = $y($t::construct(aero)?); 68 | <$t as IndirectlyConstructible>::after_construction(&res, aero)?; 69 | Ok(res) 70 | } 71 | 72 | fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> { 73 | <$t as IndirectlyConstructible>::after_construction(this, aero) 74 | } 75 | } 76 | )* 77 | }; 78 | } 79 | impl_constructible! { 80 | ; 81 | Arc: Arc::new; 82 | std::sync::Mutex: std::sync::Mutex::new; 83 | parking_lot::Mutex: parking_lot::Mutex::new; 84 | std::sync::RwLock: std::sync::RwLock::new; 85 | parking_lot::RwLock: parking_lot::RwLock::new; 86 | } 87 | 88 | /// Implemented for resources which can be constructed from other resources. 89 | /// Do not implement this trait directly, instead implement `Constructible` and ensure 90 | /// the remaining type bounds are met for the automatic implementation of `ConstructibleResource`. 91 | pub trait ConstructibleResource: Resource + IndirectlyConstructible {} 92 | impl ConstructibleResource for T {} 93 | 94 | /// Automatically implemented for resource lists where every resource can be constructed. 95 | pub trait ConstructibleResourceList: ResourceList { 96 | /// Construct every resource in this list in the provided aerosol instance 97 | fn construct(aero: &Aero) -> anyhow::Result<()>; 98 | } 99 | 100 | impl ConstructibleResourceList for HNil { 101 | fn construct(_aero: &Aero) -> anyhow::Result<()> { 102 | Ok(()) 103 | } 104 | } 105 | 106 | impl ConstructibleResourceList 107 | for HCons 108 | { 109 | fn construct(aero: &Aero) -> anyhow::Result<()> { 110 | aero.try_init::().map_err(Into::into)?; 111 | T::construct(aero) 112 | } 113 | } 114 | 115 | impl Aero { 116 | /// Try to get or construct an instance of `T`. 117 | pub fn try_obtain(&self) -> Result { 118 | match self.try_get_slot() { 119 | Some(SlotDesc::Filled(x)) => Ok(x), 120 | Some(SlotDesc::Placeholder) | None => match self.wait_for_slot::(true) { 121 | Some(x) => Ok(x), 122 | None => match T::construct(self.as_ref()) { 123 | Ok(x) => { 124 | self.fill_placeholder::(x.clone()); 125 | Ok(x) 126 | } 127 | Err(e) => { 128 | self.clear_placeholder::(); 129 | Err(e) 130 | } 131 | }, 132 | }, 133 | } 134 | } 135 | /// Get or construct an instance of `T`. Panics if unable. 136 | pub fn obtain(&self) -> T { 137 | unwrap_constructed::(self.try_obtain::()) 138 | } 139 | /// Try to initialize an instance of `T`. Does nothing if `T` is already initialized. 140 | pub fn try_init(&self) -> Result<(), T::Error> { 141 | match self.wait_for_slot::(true) { 142 | Some(_) => Ok(()), 143 | None => match T::construct(self.as_ref()) { 144 | Ok(x) => { 145 | self.fill_placeholder::(x); 146 | Ok(()) 147 | } 148 | Err(e) => { 149 | self.clear_placeholder::(); 150 | Err(e) 151 | } 152 | }, 153 | } 154 | } 155 | /// Initialize an instance of `T`. Does nothing if `T` is already initialized. Panics if unable. 156 | pub fn init(&self) { 157 | unwrap_constructed::(self.try_init::()) 158 | } 159 | 160 | /// Builder method equivalent to calling `try_init()` but can be chained. 161 | pub fn try_with_constructed( 162 | self, 163 | ) -> Result>, T::Error> { 164 | self.try_init::()?; 165 | Ok(Aero { 166 | inner: self.inner, 167 | phantom: PhantomData, 168 | }) 169 | } 170 | 171 | /// Builder method equivalent to calling `try_init()` but can be chained. Panics if construction fails. 172 | pub fn with_constructed(self) -> Aero> { 173 | unwrap_constructed::(self.try_with_constructed()) 174 | } 175 | 176 | /// Convert into a different variant of the Aero type. Any missing required resources 177 | /// will be automatically constructed. 178 | pub fn try_construct_remaining(self) -> anyhow::Result> 179 | where 180 | R2: Sculptor + ResourceList, 181 | >::Remainder: ConstructibleResourceList, 182 | { 183 | <>::Remainder>::construct(&self)?; 184 | Ok(Aero { 185 | inner: self.inner, 186 | phantom: PhantomData, 187 | }) 188 | } 189 | 190 | /// Convert into a different variant of the Aero type. Any missing required resources 191 | /// will be automatically constructed. Panics if construction of any missing resource fails. 192 | pub fn construct_remaining(self) -> Aero 193 | where 194 | R2: Sculptor + ResourceList, 195 | >::Remainder: ConstructibleResourceList, 196 | { 197 | unwrap_constructed_hlist::<>::Remainder, _>( 198 | self.try_construct_remaining(), 199 | ) 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use std::{convert::Infallible, thread::scope, time::Duration}; 206 | 207 | use crate::Aero; 208 | 209 | use super::*; 210 | 211 | #[derive(Debug, Clone)] 212 | struct Dummy; 213 | 214 | impl Constructible for Dummy { 215 | type Error = Infallible; 216 | 217 | fn construct(_app_state: &Aero) -> Result { 218 | std::thread::sleep(Duration::from_millis(100)); 219 | Ok(Self) 220 | } 221 | } 222 | 223 | #[test] 224 | fn obtain() { 225 | let state = Aero::new(); 226 | state.obtain::(); 227 | } 228 | 229 | #[test] 230 | fn obtain_race() { 231 | let state = Aero::new(); 232 | scope(|s| { 233 | for _ in 0..100 { 234 | s.spawn(|| state.obtain::()); 235 | } 236 | }); 237 | } 238 | 239 | #[derive(Debug, Clone)] 240 | struct DummyRecursive; 241 | 242 | impl Constructible for DummyRecursive { 243 | type Error = Infallible; 244 | 245 | fn construct(aero: &Aero) -> Result { 246 | aero.obtain::(); 247 | Ok(Self) 248 | } 249 | } 250 | 251 | #[test] 252 | fn obtain_recursive() { 253 | let state = Aero::new(); 254 | state.obtain::(); 255 | } 256 | 257 | #[test] 258 | fn obtain_recursive_race() { 259 | let state = Aero::new(); 260 | scope(|s| { 261 | for _ in 0..100 { 262 | s.spawn(|| state.obtain::()); 263 | } 264 | }); 265 | } 266 | 267 | #[derive(Debug, Clone)] 268 | struct DummyCyclic; 269 | 270 | impl Constructible for DummyCyclic { 271 | type Error = Infallible; 272 | 273 | fn construct(aero: &Aero) -> Result { 274 | aero.obtain::(); 275 | Ok(Self) 276 | } 277 | } 278 | 279 | #[test] 280 | #[should_panic(expected = "Cycle detected")] 281 | fn obtain_cyclic() { 282 | let state = Aero::new(); 283 | state.obtain::(); 284 | } 285 | 286 | #[derive(Debug)] 287 | struct DummyNonClone; 288 | 289 | impl Constructible for DummyNonClone { 290 | type Error = Infallible; 291 | 292 | fn construct(_app_state: &Aero) -> Result { 293 | std::thread::sleep(Duration::from_millis(100)); 294 | Ok(Self) 295 | } 296 | } 297 | 298 | #[test] 299 | fn obtain_non_clone() { 300 | let state = Aero::new(); 301 | state.obtain::>(); 302 | } 303 | 304 | trait DummyTrait: Send + Sync {} 305 | 306 | #[derive(Debug)] 307 | struct DummyImpl; 308 | 309 | impl DummyTrait for DummyImpl {} 310 | 311 | impl Constructible for DummyImpl { 312 | type Error = Infallible; 313 | 314 | fn construct(_app_state: &Aero) -> Result { 315 | Ok(Self) 316 | } 317 | 318 | fn after_construction( 319 | this: &(dyn Any + Send + Sync), 320 | aero: &Aero, 321 | ) -> Result<(), Self::Error> { 322 | if let Some(arc) = this.downcast_ref::>() { 323 | aero.insert(arc.clone() as Arc) 324 | } 325 | Ok(()) 326 | } 327 | } 328 | 329 | #[test] 330 | fn obtain_impl() { 331 | let state = Aero::new(); 332 | state.init::>(); 333 | state.try_get::>().unwrap(); 334 | } 335 | 336 | #[test] 337 | fn with_constructed() { 338 | let state = Aero::new().with(42).with_constructed::().with("hi"); 339 | state.get::(); 340 | } 341 | 342 | #[test] 343 | fn construct_remaining() { 344 | let state: Aero![i32, Dummy, DummyRecursive, &str] = 345 | Aero::new().with(42).with("hi").construct_remaining(); 346 | state.get::(); 347 | state.get::(); 348 | } 349 | } 350 | --------------------------------------------------------------------------------