auto-di 0.3.2

Ergonomic async-aware automatic dependency injection for Rust
Documentation
# auto-di

Async-aware automatic dependency injection for Rust. Providers register through
`inventory`; constructor parameters form the dependency graph, and instances
are initialized safely with Tokio `OnceCell`.

The public model is intentionally small:

- `#[singleton]` registers a constructor-managed dependency.
- `#[provider]` registers a factory function or factory method.

## Installation

```toml
[dependencies]
auto-di = "0.3.1"
```

## Quick start

```rust
use std::sync::Arc;
use auto_di::{application, resolve, singleton};

struct Database;

#[singleton]
async fn database() -> Database {
    Database
}

struct UserService {
    database: Arc<Database>,
}

#[singleton]
impl UserService {
    fn new(database: Arc<Database>) -> Self {
        Self { database }
    }
}

#[application]
async fn main() -> Result<(), auto_di::DiError> {
    let service = resolve::<UserService>().await?;
    Ok(())
}
```

No registration list or manual container wiring is required.

## Providers

A provider can be a standalone sync, async, or fallible function:

```rust
#[provider]
fn config() -> AppConfig {
    AppConfig::default()
}

#[provider]
async fn database(config: Arc<AppConfig>) -> Result<Database, DatabaseError> {
    Database::connect(&config.database_url).await
}
```

A singleton can expose additional providers directly:

```rust
#[singleton]
impl Application {
    fn new(database: Arc<Database>) -> Self {
        Self { database }
    }

    #[provider]
    fn cache(&self) -> Cache {
        Cache::new()
    }
}
```

If a provider creates a dependency required by its owning singleton, make it an
associated function without `&self`. Otherwise the graph would be circular:

```rust
#[singleton]
impl Application {
    fn new(config: Arc<AppConfig>) -> Self {
        Self { config }
    }

    #[provider]
    fn config() -> AppConfig {
        AppConfig::default()
    }
}
```

Direct calls such as `self.cache()` bypass the container and are rejected by
the macro. Inject the provided type as a parameter instead.

## Injection forms

Constructors and providers support:

```rust
Arc<T>                   // required dependency
Option<Arc<T>>           // optional dependency
Vec<Arc<T>>              // all implementations
Arc<dyn Trait>           // selected trait implementation
Vec<Arc<dyn Trait>>      // all trait implementations
Provider<T>              // resolve later
Lazy<T>                  // resolve once on first access
```

`Provider<T>` and `Lazy<T>` preserve the originating container, active profile,
and request context.

## Multiple implementations

```rust
trait Payments: Send + Sync {}

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

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

#[singleton]
impl Checkout {
    fn new(
        default: Arc<dyn Payments>,
        #[qualifier("razorpay")] indian: Arc<dyn Payments>,
    ) -> Self {
        Self { default, indian }
    }
}
```

Duplicate names, multiple primary implementations, and unresolved ambiguity are
reported as DI errors.

## Scopes

The default scope is singleton:

```rust
#[singleton]
fn shared_client() -> Client { Client::new() }

#[singleton(scope = "prototype")]
fn job() -> Job { Job::new() }

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

Resolve request-scoped dependencies through a request context:

```rust
let request = auto_di::global_container()?.request_context();
let metadata = request.resolve::<RequestMetadata>().await?;
```

A singleton is not allowed to capture a request-scoped dependency.

## Constructor options

`#[singleton]` and `#[provider]` accept:

- `name = "..."`
- `primary`
- `scope = "singleton" | "prototype" | "request"`
- `eager`
- `blocking` for expensive synchronous work
- `profile = "development"`
- `condition = "ENV_KEY"` or `condition = "ENV_KEY=value"`
- `post_construct = "async_method"`
- `pre_destroy = "async_method"`

Lifecycle methods may return `()` or `Result<(), E>`.

## Profiles and conditions

Active profiles come from comma-separated `APP_PROFILES`:

```rust
#[singleton(profile = "development")]
fn development_database() -> Database { /* ... */ }

#[singleton(condition = "CACHE_ENABLED=true")]
fn cache() -> Cache { /* ... */ }
```

A local container can select profiles explicitly:

```rust
let container = auto_di::Container::with_profiles(["test"])?;
```

## Configuration properties

```rust
#[configuration_properties("database")]
struct DatabaseProperties {
    url: String,       // DATABASE_URL
    pool_size: usize,  // DATABASE_POOL_SIZE
}
```

## Validation and lifecycle

`Container::validate()` initializes and validates every active singleton. It
surfaces missing dependencies, ambiguous providers, circular graphs, and scope
violations during startup. `#[application]` runs validation automatically and
runs shutdown hooks in dependency-safe topological order.

The resolver also detects cycles formed concurrently by different Tokio tasks,
so they return an error instead of waiting on each other's `OnceCell` forever.

## Renaming the dependency

Cargo aliases are supported:

```toml
[dependencies]
di = { package = "auto-di", version = "0.3.1" }
```

```rust
#[di::singleton]
fn dependency() -> Dependency { Dependency }
```

## Example

Run the complete example from this repository:

```bash
cargo run -p auto-di --example showcase
```