stano-di 0.2.0

Lightweight dependency injection container with lazy singleton resolution, cycle detection, and async-safe validation
Documentation
# stano-di

Lightweight dependency injection container with lazy singleton resolution, cycle detection, and environment variable loading. Pairs with `stano-di-macros` for automatic boilerplate generation.

## Install

```toml
[dependencies]
stano-di = { path = "../stano-di" }
```

## API

### Container

- **`Container`** — service locator registry (TypeId-keyed internal storage).
  - `new() -> Self` — create an empty container.
  - `register<T: Send+Sync+'static>(&mut self, factory: fn(&Container) -> Arc<T>)` — register a concrete type with its factory. Lazily resolves on first access via `OnceLock`.
  - `register_trait<T: DynComponent + ?Sized>(&mut self, factory: fn(&Container) -> Arc<T>) where Arc<T>: Send+Sync` — register a trait object (e.g., `Arc<dyn MyTrait>`).
  - `register_instance<T: Send+Sync+'static>(&mut self, instance: Arc<T>)` — register a pre-built instance (no factory needed).
  - `register_component<T: Component>(&mut self)` — register a type that implements the `Component` trait (generated by `#[service]`).
  - `get<T: Send+Sync+Clone+'static>(&self) -> Arc<T>` — retrieve a registered type; panics if not found.
  - `try_get<T: Send+Sync+Clone+'static>(&self) -> Result<Arc<T>, ContainerError>` — safe retrieval.
  - `get_trait<T: DynComponent + ?Sized>(&self) -> Arc<T> where Arc<T>: Clone` — retrieve a trait object; panics if not found.
  - `try_get_trait<T: DynComponent + ?Sized>(&self) -> Result<Arc<T>, ContainerError> where Arc<T>: Clone` — safe trait retrieval.
  - `has<T: 'static>(&self) -> bool` — check if a concrete type is registered.
  - `has_trait<T: DynComponent + ?Sized>(&self) -> bool` — check if a trait is registered.
  - `validate(&self) -> Result<(), Vec<ContainerError>>` — eagerly resolve all singletons and detect cyclic dependencies. Call after all registrations and before serving traffic.
  - `dependency_graph(&self) -> DependencyGraph` — returns a displayable graph of registered types and their dependencies.

### Application Context

- **`ApplicationContext`** — facade over `Container` that also carries an `Arc<dyn Environment>`.
  - Same registration and retrieval methods as `Container`.
  - `environment(&self) -> &Arc<dyn Environment>` — access the environment.
  - `register_all(&mut self)` — auto-register all `#[service]`-annotated structs discovered via the `inventory` crate.
  - `container_mut(&mut self) -> &mut Container` — direct access to the inner container if needed.

### Environment

- **`Environment`** trait — loads config values by key.
  - `fn get(&self, key: &str) -> Option<String>` — retrieve an environment variable or config value.

- **`OsEnvironment`** — loads from actual OS environment variables (and `.env` files via `dotenvy`).
  - `fn new() -> Self` — creates and auto-loads `.env` if present.

### Error Type

- **`ContainerError`**:
  - `NotRegistered(&'static str)` — type was not registered.
  - `DowncastFailed(&'static str)` — failed to downcast a trait object.
  - `FactoryPanic(&'static str)` — factory function panicked during validation.
  - `CyclicDependency(Vec<&'static str>)` — cyclic dependency detected (e.g., A → B → A).

### Traits (Generated by Macros)

- **`Component`** — contract for types that can be auto-registered. Generated by `#[service]`.
  - `fn component_type_name() -> &'static str` — name of this type.
  - `fn dependency_ids() -> Vec<TypeId>` — IDs of types this type depends on.
  - `fn build(container: &Container) -> Arc<Self>` — construct an instance.
  - `fn register(container: &mut Container)` — register the factory.

- **`Injectable`** — trait for types that can be retrieved from container. Generated by `#[component]` on traits.
  - `fn get_from(container: &Container) -> Arc<Self>` — retrieve from container.

- **`DynComponent`** — marker trait for trait objects. Generated by `#[component]`.

## Usage Example

```rust
use stano_di::{Container, ApplicationContext};
use std::sync::Arc;

// Define a trait (mark with #[component]).
#[component]
pub trait MyService: Send + Sync {
    fn do_work(&self) -> String;
}

// Implement it (mark with #[service(dyn MyService)]).
pub struct MyServiceImpl;
impl MyService for MyServiceImpl {
    fn do_work(&self) -> String { "work done".into() }
}

// Manual registration (without the #[service] macro, for this example).
// register_trait requires the trait to implement DynComponent (via #[component]).
let mut container = Container::new();
container.register_trait::<dyn MyService>(|_| Arc::new(MyServiceImpl));

// Or via ApplicationContext with environment:
use stano_di::OsEnvironment;
let env = Arc::new(OsEnvironment::new());
let mut ctx = ApplicationContext::new(env);
ctx.register_all(); // auto-register all #[service] structs

// Validate (detects cycles, ensures all factories work).
ctx.validate()?;

// Retrieve.
let svc: Arc<dyn MyService> = ctx.get_trait();
println!("{}", svc.do_work()); // "work done"
```

## Notes

- **Lazy resolution** — singletons are resolved on first access, not at registration time.
- **Cycle detection**`validate()` detects circular dependencies (A → B → C → A) before they cause infinite loops at runtime.
- **`#[service]` and `#[component]` generation** — see `stano-di-macros` for the macro syntax. These generate `Component` trait impls and auto-register via `inventory::submit!`.
- **Thread-safe** — all types must be `Send + Sync` for use in async contexts; factory functions run synchronously and must not block.
- **No feature flags** — all APIs available.