# async-injector [github](https://github.com/udoprog/async-injector) [crates.io](https://crates.io/crates/async-injector) [docs.rs](https://docs.rs/async-injector) [build status](https://github.com/udoprog/async-injector/actions?query=branch%3Amain) Asynchronous dependency injection for Rust. This library provides the glue which allows for building robust decoupled applications that can be reconfigured dynamically while they are running. For a real world example of how this is used, see [`OxidizeBot`] for which it was written.
## Usage Add `async-injector` to your `Cargo.toml`. ```toml [dependencies] async-injector = "0.19.3" ```
## Example In the following we'll showcase the injection of a *fake* `Database`. The idea here would be that if something about the database connection changes, a new instance of `Database` would be created and cause the application to reconfigure itself. ```rust use async_injector::{Key, Injector, Provider}; #[derive(Debug, Clone)] struct Database; #[derive(Debug, Provider)] struct Service { #[dependency] database: Database, } async fn service(injector: Injector) -> Result<(), Box> { let mut provider = Service::provider(&injector).await?; let Service { database } = provider.wait().await; println!("Service got initial database {database:?}!"); let Service { database } = provider.wait().await; println!("Service got new database {database:?}!"); Ok(()) } ``` > **Note:** This is available as the `database` example: > ```sh > cargo run --example database > ``` The [`Injector`] above provides a structured broadcasting system that allows for configuration updates to be cleanly integrated into asynchronous contexts. The update itself is triggered by some other component that is responsible for constructing the `Database` instance. Building up the components of your application like this means that it can be reconfigured without restarting it. Providing a much richer user experience.
## Injecting multiple things of the same type In the previous section you might've noticed that the injected value was solely discriminated by its type: `Database`. In this example we'll show how [`Key`] can be used to *tag* values of the same type with different names to discriminate them. This can be useful when dealing with overly generic types like [`String`]. The tag used must be serializable with [`serde`]. It must also not use any components which [cannot be hashed], like `f32` and `f64`.
### A simple greeter The following example showcases the use of `Key` to injector two different values into an asynchronous `greeter`. ```rust use async_injector::{Key, Injector}; async fn greeter(injector: Injector) -> Result<(), Box> { let name = Key::::tagged("name")?; let fun = Key::::tagged("fun")?; let (mut name_stream, mut name) = injector.stream_key(name).await; let (mut fun_stream, mut fun) = injector.stream_key(fun).await; loop { tokio::select! { update = name_stream.recv() => { name = update; } update = fun_stream.recv() => { fun = update; } } let (Some(name), Some(fun)) = (&name, &fun) else { continue; }; println!("Hi {name}! I see you do \"{fun}\" for fun!"); return Ok(()); } } ``` > **Note:** you can run this using: > ```sh > cargo run --example greeter > ``` The loop above can be implemented more easily using the [`Provider`] derive, so let's do that. ```rust use async_injector::{Injector, Provider}; #[derive(Provider)] struct Dependencies { #[dependency(tag = "name")] name: String, #[dependency(tag = "fun")] fun: String, } async fn greeter(injector: Injector) -> Result<(), Box> { let mut provider = Dependencies::provider(&injector).await?; let Dependencies { name, fun } = provider.wait().await; println!("Hi {name}! I see you do \"{fun}\" for fun!"); Ok(()) } ``` > **Note:** you can run this using: > ```sh > cargo run --example greeter_provider > ```
## The `Provider` derive The [`Provider`] derive can be used to conveniently implement the mechanism necessary to wait for a specific set of dependencies to become available. It builds a companion structure next to the type being provided called `Provider` which in turn implements the following set of methods: ```rust use async_injector::{Error, Injector}; impl Dependencies { /// Construct a new provider. async fn provider(injector: &Injector) -> Result } struct DependenciesProvider { /* private fields */ } impl DependenciesProvider { /// Try to construct the current value. Returns [None] unless all /// required dependencies are available. fn build(&mut self) -> Option /// Wait until we can successfully build the complete provided /// value. async fn wait(&mut self) -> Dependencies /// Wait until the provided value has changed. Either some /// dependencies are no longer available at which it returns `None`, /// or all dependencies are available after which we return the /// build value. async fn wait_for_update(&mut self) -> Option } ```
### Fixed arguments to `Provider` Any arguments which do not have the `#[dependency]` attribute are known as "fixed" arguments. These must be passed in when calling the `provider` constructor. They can also be used during tag construction. ```rust use async_injector::{Injector, Key, Provider}; #[derive(Provider)] struct Dependencies { name_tag: &'static str, #[dependency(tag = name_tag)] name: String, } async fn greeter(injector: Injector) -> Result<(), Box> { let mut provider = Dependencies::provider(&injector, "name").await?; let Dependencies { name, .. } = provider.wait().await; println!("Hi {name}!"); Ok(()) } ``` [`OxidizeBot`]: https://github.com/udoprog/OxidizeBot [cannot be hashed]: https://internals.rust-lang.org/t/f32-f64-should-implement-hash/5436 [`Injector`]: https://docs.rs/async-injector/0/async_injector/struct.Injector.html [`Key`]: https://docs.rs/async-injector/0/async_injector/struct.Key.html [`Provider`]: https://docs.rs/async-injector/0/async_injector/derive.Provider.html [`serde`]: https://serde.rs [`Stream`]: https://docs.rs/futures-core/0/futures_core/stream/trait.Stream.html [`String`]: https://doc.rust-lang.org/std/string/struct.String.html