# 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();
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.