aerosol/
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//! ### `aide`
31//!
32//! Provides integrations with the `aide` library. Implements the `OperationInput` trait
33//! for `Dep<T>` and `Obtain<T>` types, allowing them to be used in `aide`-generated OpenAPI
34//! documentation.
35//!
36//! ## Example usage
37//!
38//! ```rust
39//! use std::{sync::Arc, any::Any};
40//!
41//! # struct PostmarkClient;
42//! # #[derive(Clone)]
43//! # struct ConnectionPool;
44//! # #[derive(Clone)]
45//! # struct MessageQueue;
46//! # #[derive(Clone)]
47//! # struct MagicNumber(i32);
48//! # trait EmailSender: Send + Sync { fn send(&self) {} }
49//! # impl EmailSender for PostmarkClient {}
50//! # impl PostmarkClient { fn new() -> anyhow::Result<Self> { Ok(Self) }}
51//! use aerosol::{Aero, Constructible};
52//!
53//! // Here, we can list all the things we want to guarantee are in
54//! // our app state. This is entirely optional, we could also just
55//! // use the `Aero` type with default arguments and check that
56//! // resources are present at runtime.
57//! type AppState = Aero![
58//!     Arc<PostmarkClient>,
59//!     Arc<dyn EmailSender>,
60//!     ConnectionPool,
61//!     MessageQueue,
62//!     MagicNumber,
63//! ];
64//!
65//! fn main() {
66//!     let app_state: AppState = Aero::new()
67//!         // Directly add a resource which doesn't implement `Constructible`.
68//!         .with(MagicNumber(42))
69//!         // Construct an `Arc<PostmarkClient>` resource in the AppState
70//!         .with_constructed::<Arc<PostmarkClient>>()
71//!         // Check that an implementation of `EmailSender` was added as a result
72//!         .assert::<Arc<dyn EmailSender>>()
73//!         // Automatically construct anything else necessary for our AppState
74//!         // (in this case, `ConnectionPool` and `MessageQueue`)
75//!         .construct_remaining();
76//!
77//!     // Add an extra resource
78//!     app_state.insert("Hello, world");
79//!
80//!     run(app_state);
81//! }
82//!
83//! fn run(app_state: AppState) {
84//!     // The `get()` method is infallible because the `Arc<dyn EmailSender>` was
85//!     // explicitly listed when defining our `AppState`.
86//!     let email_sender: Arc<dyn EmailSender> = app_state.get();
87//!     email_sender.send(/* email */);
88//!
89//!     // We have to use `try_get()` here because a `&str` is not guaranteed to
90//!     // exist on our `AppState`.
91//!     let hello_message: &str = app_state.try_get().unwrap();
92//!     println!("{hello_message}");
93//!
94//!     // ... more application logic
95//! }
96//!
97//! // The `Constructible` trait can be implemented to allow resources to be automatically
98//! // constructed.
99//! impl Constructible for PostmarkClient {
100//!     type Error = anyhow::Error;
101//!
102//!     fn construct(aero: &Aero) -> Result<Self, Self::Error> {
103//!         PostmarkClient::new(/* initialize using environment variables */)
104//!     }
105//!
106//!     fn after_construction(this: &(dyn Any + Send + Sync), aero: &Aero) -> Result<(), Self::Error> {
107//!         // We can use this to automatically populate extra resources on the context.
108//!         // For example, in this case we can make it so that if an `Arc<PostmarkClient>` gets
109//!         // constructed, we also provide `Arc<dyn EmailSender>`.
110//!         if let Some(arc) = this.downcast_ref::<Arc<Self>>() {
111//!             aero.insert(arc.clone() as Arc<dyn EmailSender>)
112//!         }
113//!         Ok(())
114//!     }
115//! }
116//!
117//! impl Constructible for ConnectionPool {
118//!     type Error = anyhow::Error;
119//!     fn construct(aero: &Aero) -> Result<Self, Self::Error> {
120//!         // ...
121//! #       Ok(ConnectionPool)
122//!     }
123//! }
124//!
125//! impl Constructible for MessageQueue {
126//!     type Error = anyhow::Error;
127//!     fn construct(aero: &Aero) -> Result<Self, Self::Error> {
128//!         // ...
129//! #       Ok(MessageQueue)
130//!     }
131//! }
132//! ```
133//!
134//! ## Implementation details
135//!
136//! The `Aero` type manages shared ownership of a map from resource types to "slots".
137//! For a given resource type, the corresponding "slot" can be in one of three state:
138//! 1) Absent.
139//!    No instance of this resource is present in the map.
140//! 2) Present.
141//!    An instance of this resource exists in the map and can be accessed immediately.
142//! 3) Under construction.
143//!    An instance of this resource is currently under construction, and may be accessed
144//!    once construction has finished.
145//!    The slot maintains a list of threads or tasks waiting for this resource to be
146//!    constructed, and will wake them when the resource becomes available.
147//!
148//! Resources can be constructed synchronously, or (when the feature is enabled) asynchronously.
149//!
150//! If a resource is accessed whilst under construction, the caller will wait for construction
151//! to complete. The caller determines whether the wait occurs synchronously or asynchronously,
152//! depending on whether `obtain()` or `obtain_async()` is used.
153//!
154//! It is possible (and allowed) for a thread to synchronously wait on a resource being constructed
155//! asynchronously in a task, or for a task to asynchronously wait on a resource being synchronously
156//! constructed on a thread.
157//!
158
159pub use frunk;
160
161#[cfg(feature = "async")]
162mod async_;
163#[cfg(feature = "async")]
164mod async_constructible;
165#[cfg(feature = "axum")]
166pub mod axum;
167mod macros;
168mod resource;
169mod slot;
170mod state;
171mod sync;
172mod sync_constructible;
173
174pub use resource::{Resource, ResourceList};
175pub use state::Aero;
176
177pub use sync_constructible::{
178    Constructible, ConstructibleResource, ConstructibleResourceList, IndirectlyConstructible,
179};
180
181#[cfg(feature = "async")]
182pub use async_constructible::{
183    AsyncConstructible, AsyncConstructibleResource, AsyncConstructibleResourceList,
184    IndirectlyAsyncConstructible,
185};