auto-di 0.3.1

Ergonomic async-aware automatic dependency injection for Rust
Documentation
use auto_di::{
    Lazy, Provider, application, configuration, global_container, provider, resolve, singleton,
};
use std::{
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
    },
    time::Duration,
};

// ---------- Spring-style @Configuration + @Bean ----------

#[derive(Debug)]
struct AppConfig {
    application_name: String,
    database_url: String,
}

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

// A provider can live anywhere.
#[provider]
fn build_number() -> u64 {
    42
}

#[derive(Default)]
struct ApplicationBeans;

#[configuration]
impl ApplicationBeans {
    #[provider]
    fn app_config(&self) -> AppConfig {
        AppConfig {
            application_name: "Khatarnak DI".into(),
            database_url: "postgres://localhost/example".into(),
        }
    }

    #[provider]
    async fn database(&self, config: Arc<AppConfig>) -> Database {
        println!("connecting database asynchronously...");
        tokio::time::sleep(Duration::from_millis(20)).await;
        Database {
            url: config.database_url.clone(),
        }
    }
}

// ---------- Trait injection, primary dependency and qualifier ----------

trait PaymentGateway: Send + Sync {
    fn name(&self) -> &'static str;
}

struct StripeGateway;
impl PaymentGateway for StripeGateway {
    fn name(&self) -> &'static str {
        "stripe"
    }
}

struct RazorpayGateway;
impl PaymentGateway for RazorpayGateway {
    fn name(&self) -> &'static str {
        "razorpay"
    }
}

#[singleton(name = "stripe", primary)]
fn stripe_gateway() -> Arc<dyn PaymentGateway> {
    Arc::new(StripeGateway)
}

#[singleton(name = "razorpay")]
fn razorpay_gateway() -> Arc<dyn PaymentGateway> {
    Arc::new(RazorpayGateway)
}

struct CheckoutService {
    default_gateway: Arc<dyn PaymentGateway>,
    indian_gateway: Arc<dyn PaymentGateway>,
    database: Arc<Database>,
}

#[derive(Debug)]
struct CheckoutChannel(&'static str);

#[singleton(eager, post_construct = "start", pre_destroy = "stop")]
impl CheckoutService {
    fn new(
        default_gateway: Arc<dyn PaymentGateway>,
        #[qualifier("razorpay")] indian_gateway: Arc<dyn PaymentGateway>,
        database: Arc<Database>,
    ) -> Self {
        Self {
            default_gateway,
            indian_gateway,
            database,
        }
    }

    async fn start(&self) {
        println!("CheckoutService post-construct hook");
    }

    async fn stop(&self) {
        println!("CheckoutService pre-destroy hook");
    }

    #[provider]
    fn checkout_channel(&self) -> CheckoutChannel {
        CheckoutChannel("web")
    }
}

// ---------- Prototype and request scopes ----------

static JOB_IDS: AtomicUsize = AtomicUsize::new(1);

#[derive(Debug)]
struct BackgroundJob {
    id: usize,
}

#[singleton(scope = "prototype")]
fn background_job() -> BackgroundJob {
    BackgroundJob {
        id: JOB_IDS.fetch_add(1, Ordering::SeqCst),
    }
}

#[derive(Debug)]
struct RequestMetadata;

#[singleton(scope = "request")]
fn request_metadata() -> RequestMetadata {
    RequestMetadata
}

// ---------- Optional, Provider and Lazy injection ----------

struct MissingCache;

struct ReportService {
    cache: Option<Arc<MissingCache>>,
}

#[singleton]
impl ReportService {
    fn new(cache: Option<Arc<MissingCache>>) -> Self {
        Self { cache }
    }
}

struct Dashboard {
    reports: Provider<ReportService>,
    lazy_reports: Lazy<ReportService>,
}

#[singleton]
impl Dashboard {
    fn new(reports: Provider<ReportService>, lazy_reports: Lazy<ReportService>) -> Self {
        Self {
            reports,
            lazy_reports,
        }
    }
}

#[application]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = resolve::<AppConfig>().await?;
    println!("application: {}", config.application_name);
    println!("standalone provider: build {}", resolve::<u64>().await?);
    println!(
        "provider from singleton impl: {}",
        resolve::<CheckoutChannel>().await?.0
    );

    let checkout = resolve::<CheckoutService>().await?;
    println!("primary gateway: {}", checkout.default_gateway.name());
    println!("qualified gateway: {}", checkout.indian_gateway.name());
    println!("database: {}", checkout.database.url);

    let first_job = resolve::<BackgroundJob>().await?;
    let second_job = resolve::<BackgroundJob>().await?;
    println!("prototype job ids: {}, {}", first_job.id, second_job.id);

    let request = global_container()?.request_context();
    let first_metadata = request.resolve::<RequestMetadata>().await?;
    let second_metadata = request.resolve::<RequestMetadata>().await?;
    println!(
        "same request-scoped instance: {}",
        Arc::ptr_eq(&first_metadata, &second_metadata)
    );

    let dashboard = resolve::<Dashboard>().await?;
    let report = dashboard.reports.get().await?;
    let lazy_report = dashboard.lazy_reports.get().await?;
    println!("optional cache available: {}", report.cache.is_some());
    println!(
        "lazy singleton reused: {}",
        Arc::ptr_eq(&report, lazy_report)
    );

    Ok(())
}