Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/udoprog/async-injector

Reactive dependency injection for Rust.
https://github.com/udoprog/async-injector

async dependency-injection reconfigure rust

Last synced: about 2 months ago
JSON representation

Reactive dependency injection for Rust.

Awesome Lists containing this project

README

        

# 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