noema 0.4.0

Noema IOC and DI framework for Rust
Documentation
# Dependency injection (`di`)

**Compile-time** registration, thread-safe resolution via `Arc<T>`.  
Single/trait bindings: **one macro per type**. Many/keyed: **one batch per macro call** → static `LazyLock<Arc<[...]>>` + trait impl (like legacy `components!`).

## Typical imports

```rust
use noema::core::{Container, Injectable, Resolver, arc_dyn};
use noema::{
    resolve, Keyed, dependency, dependency_as, dependency_as_many, dependency_keyed_as,
};
```

Registrable types must implement `Injectable<Container>` (manually or via `#[derive(Injectable)]`).

---

## Registration: lifecycle × resolution mode

| Macro | Registers | Lifecycle |
|-------|-----------|-----------|
| `dependency!(lifecycle, Type)` | Concrete type | Per call (`singleton` / `transient`) |
| `dependency_as!(lifecycle, Trait: Impl)` | Trait → impl | Per call |
| `dependency_as_many!(Trait: [(lifecycle, Impl), …])` | Batch of impls in a “many” collection | **Per entry** |
| `dependency_keyed_as!(K, Trait: [(key, lifecycle, Impl), …])` | Batch keyed `K → Arc<dyn Trait>` | **Per entry** |

### Exact syntax

```rust
// ── Single (concrete) ─────────────────────────────────────────────
dependency!(singleton, MyService);
dependency!(transient, RequestId);

// ── Single (trait) ────────────────────────────────────────────────
// Trait name only; macro expands to dyn Trait + Send + Sync
dependency_as!(singleton, Logger: ConsoleLogger);
dependency_as!(transient, Logger: FileLogger);

// ── Many (batch in one macro call; lifecycle per entry) ───────────
dependency_as_many!(Validator: [
    (singleton, EmailValidator),
    (transient, SmsValidator),
]);

// ── Keyed (typed keys: K → Arc<dyn Trait>; lifecycle per entry) ─
#[derive(PartialEq)]
enum PaymentKey { Stripe, Paypal }

dependency_keyed_as!(PaymentKey, Payment: [
    (PaymentKey::Stripe, singleton, StripeGateway),
    (PaymentKey::Paypal, transient, PaypalGateway),
]);
```

### Important rules

1. **Single: one macro, one binding** — two `dependency!(singleton, MyService)` calls produce two conflicting `impl Resolver<MyService> for Container`.
2. **Many/keyed: one batch per macro call** — each entry declares its lifecycle explicitly: `(singleton, Impl)` or `(transient, Impl)` for many; `(key, lifecycle, Impl)` for keyed.
3. **Many / keyed are per-crate** — register the full batch in the library crate that owns the trait; the app links that crate to resolve.
4. **Keyed keys are typed** — declare a key type `K` (`enum`, `struct`, …); keys must be `PartialEq`. Lookup: `.one(key_value)`.
5. **Trait objects**`_as` macros take the trait name only (`Logger: Impl`); they expand to `dyn Trait + Send + Sync`. Traits should extend `Send + Sync` (recommended supertraits).

---

## Resolution: `resolve()`

A single function; the **generic type** selects the mode:

```rust
use std::sync::Arc;
use noema::{resolve, Keyed};

// Single concrete → Arc<T>
let svc: Arc<MyService> = resolve::<MyService>();

// Single trait object → Arc<dyn Trait + Send + Sync>
let log = resolve::<dyn Logger + Send + Sync>();

// Many → ordered slice (order = registration order)
let validators = resolve::<Arc<[Arc<dyn Validator + Send + Sync>]>>();
assert_eq!(validators.len(), 2);

// Keyed → wrapper; .one(key) returns Option
let payments = resolve::<Keyed<PaymentKey, dyn Payment + Send + Sync>>();
let stripe = payments.one(PaymentKey::Stripe).expect("registered");
assert!(payments.one("unknown").is_none());

// Empty many → empty slice (no panic)
let empty = resolve::<Arc<[Arc<dyn Unused + Send + Sync>]>>();
assert!(empty.is_empty());
```

| Turbofish | Return type |
|-----------|-------------|
| `resolve::<T>()` | `Arc<T>` |
| `resolve::<dyn Trait + Send + Sync>()` | `Arc<dyn Trait + Send + Sync>` |
| `resolve::<Arc<[Arc<T>]>>()` | `Arc<[Arc<T>]>` |
| `resolve::<Keyed<K, S>>()` | `Keyed<K, S>` |

If there is no registration for a **single** type, the code does not compile (missing `impl Resolver<…> for Container`).

---

## `#[derive(Injectable)]`

Generates `impl<TInjector> Injectable<TInjector>` by resolving fields from the injector:

```rust
use noema::core::{arc_dyn, Injectable, Resolver};

#[derive(Injectable)]
struct AppService {
    repo: Arc<UserRepo>,           // requires TInjector: Resolver<UserRepo>
    logger: arc_dyn!(Logger),      // requires TInjector: Resolver<dyn Logger + Send + Sync>
    validators: Arc<[Arc<dyn Validator + Send + Sync>]>, // TInjector: ManyResolver<Validator>
}

// Resolver and ManyResolver must be in scope where you use the derive:
use noema::core::Resolver;
use noema::di::ManyResolver;

let app = AppService::inject(&Container);
```

Supported fields: `Arc<T>`, `arc_dyn!(Trait)`, `Arc<[Arc<T>]>`.

For framework traits (`Mediator`, `ManyResolver`, `KeyedResolver`) the derive uses `Arc::new(Container)` — the ZST acts as the handle. Item sets are normal singletons: `Arc<ItemSet<C>>` goes through `Resolver`.

---

## Type helpers (`core`)

```rust
use noema::core::Arc;
use noema::{arc_dyn, dyn_sync};

type LoggerArc = arc_dyn!(Logger);   // Arc<dyn Logger + Send + Sync>
type LoggerObj = dyn_sync!(Logger);   // dyn Logger + Send + Sync
```

---

## Cross-crate registration

Register in library crates; resolve in the app crate that depends on them:

```rust
// validators crate (lib.rs)
use noema::core::{Container, Injectable};
use noema::dependency_as_many;

pub trait Validator: Send + Sync { fn id(&self) -> &'static str; }
// … EmailValidator, PhoneValidator, SmsValidator …

dependency_as_many!(Validator: [
    (singleton, EmailValidator),
    (singleton, PhoneValidator),
    (singleton, SmsValidator),
]);
```

```rust
// binary / test (depends on validators)
use std::sync::Arc;
use noema::resolve;
use validators::Validator;

let all = resolve::<Arc<[Arc<dyn Validator + Send + Sync>]>>();
assert_eq!(all.len(), 3);
```

Register the **full batch in one crate** — duplicate `impl ManyResolver` / `KeyedResolver` for the same trait is a compile error.

---

## Minimal complete example

```rust
use std::sync::Arc;
use noema::core::{Container, Injectable, Resolver};
use noema::{resolve, dependency, dependency_as};

struct Config {
    pub host: String,
}
impl Injectable<Container> for Config {
    fn inject(_: &Container) -> Self {
        Config { host: "localhost".into() }
    }
}

trait Greeter: Send + Sync {
    fn greet(&self) -> &str;
}
struct Hello;
impl Greeter for Hello {
    fn greet(&self) -> &str { "hi" }
}
impl Injectable<Container> for Hello {
    fn inject(_: &Container) -> Self { Hello }
}

dependency!(singleton, Config);
dependency_as!(singleton, Greeter: Hello);

fn main() {
    let cfg = resolve::<Config>();
    let greeter = resolve::<dyn Greeter + Send + Sync>();
    assert_eq!(cfg.host, "localhost");
    assert_eq!(greeter.greet(), "hi");
}
```

---

## Feature flag

```toml
[dependencies]
noema = { version = "0.3", default-features = true }  # includes "di"

# or explicit:
noema = { version = "0.3", features = ["di"] }
```