[][src]Derive Macro async_injector_derive::Provider

#[derive(Provider)]
{
    // Attributes available to this derive:
    #[provider]
    #[dependency]
}

Helper derive to implement a provider.

The Provider derive can only be used on struct. Each field designates a value that must either be injected, or provided during construction.

Container attributes

The #[provider] attribute is an attribute that must be used.

The available attributes are:

  • #[provider(output = "...")] - which is used to specify the type of the provided value.
  • #[provider(build = "...")] (optional) - which can be used to specify a custom build function. If no build function is specified, the value will always be cleared.
  • #[provider(clear = "...")] (optional) - which can be used to specify a custom clear function.

We must specify the output type of the Provider using #[provider(output = "...")] like this:

use async_injector::Provider;

#[derive(Provider)]
#[provider(output = "Database")]
struct DatabaseProvider {
    #[dependency(tag = "\"url\"")]
    url: String,
    #[dependency]
    connection_limit: u32,
}

#[derive(Debug, Clone)]
struct Database;

Field attributes

The #[dependency] attribute can be used to mark fields which need to be injected. It takes an optional #[dependency(tag = "..")], which allows you to specify the tag to use when constructing the injected Key.

use async_injector::Provider;

#[derive(Provider)]
#[provider(output = "Database", build = "Database::build", clear = "Database::clear")]
struct DatabaseProvider {
    #[dependency(tag = "\"url\"")]
    url: String,
    #[dependency]
    connection_limit: u32,
}

#[derive(Debug, Clone)]
struct Database {
    url: String,
}

impl Database {
    /// Logic for how to build the injected value with all dependencies available.
    async fn build(provider: DatabaseProvider) -> Option<Self> {
        Some(Self {
            url: provider.url,
        })
    }

    /// Logic for how to clear the injected value.
    async fn clear() -> Option<Self> {
        None
    }
}

Examples

use async_injector::{Injector, Key, Provider};
use serde::Serialize;
use tokio_stream::StreamExt as _;

/// Fake database connection.
#[derive(Clone, Debug, PartialEq, Eq)]
struct Database {
    url: String,
    connection_limit: u32,
}

impl Database {
    async fn build(provider: DatabaseProvider2) -> Option<Self> {
        Some(Self {
            url: provider.url,
            connection_limit: provider.connection_limit,
        })
    }
}

/// Provider that describes how to construct a database.
#[derive(Serialize)]
pub enum Tag {
    DatabaseUrl,
    ConnectionLimit,
}

#[derive(Provider)]
#[provider(output = "Database", build = "Database::build")]
struct DatabaseProvider2 {
    #[dependency(tag = "Tag::DatabaseUrl")]
    url: String,
    #[dependency(tag = "Tag::ConnectionLimit")]
    connection_limit: u32,
}

let db_url_key = Key::<String>::tagged(Tag::DatabaseUrl)?;
let conn_limit_key = Key::<u32>::tagged(Tag::ConnectionLimit)?;

let injector = Injector::new();
tokio::spawn(DatabaseProvider2::run(injector.clone()));

let (mut database_stream, database) = injector.stream::<Database>().await;

// None of the dependencies are available, so it hasn't been constructed.
assert!(database.is_none());

assert!(injector
    .update_key(&db_url_key, String::from("example.com"))
    .await
    .is_none());

assert!(injector.update_key(&conn_limit_key, 5).await.is_none());

let new_database = database_stream
    .next()
    .await
    .expect("unexpected end of stream");

// Database instance is available!
assert_eq!(
    new_database,
    Some(Database {
        url: String::from("example.com"),
        connection_limit: 5
    })
);

Ok(())