# Configuration (`configurations`)
Typed config structs that resolve like any other singleton. You pick the **source** at runtime (`if`s in `main`); `load(source).await` hydrates every `config!(T)` from any linked crate; later `resolve::<T>()` is `Arc::clone`.
Enable:
```toml
noema = { version = "0.3", features = ["configurations"] }
```
Depends on `di`. Source methods are **async** so a Vault/SSM/HTTP provider can `.await`.
## Quick path
```rust
use noema::config::{load, EnvSource, Configurable};
use noema::resolve;
#[derive(Configurable)]
struct DatabaseConfig {
#[config(name = "DATABASE_URL")]
url: String,
#[config(name = "DATABASE_POOL_SIZE", default = 10)]
pool_size: u32,
#[config(name = "DATABASE_SSL")]
ssl: Option<bool>,
}
noema::config!(DatabaseConfig);
#[tokio::main]
async fn main() {
if std::env::var("CONFIG_FILE").is_ok() {
// your FileSource / VaultSource
load(EnvSource).await.expect("config");
} else {
load(EnvSource).await.expect("config");
}
let db = resolve::<DatabaseConfig>();
println!("{} pool={}", db.url, db.pool_size);
}
```
`config!(T)` can live next to the type in any crate. `main` does **not** list the types. The crate that calls `config!` must be linked into the binary.
## Field attributes
| `name = "KEY"` | Source key (env var or provider path). Default: field name |
| `default = "10"` or `default = 10` | Used when the key is absent |
| field type `Option<T>` | Absent key → `None` (not an error) |
Values parse with `FromStr`.
## Custom provider
```rust
use async_trait::async_trait;
use noema::config::{ConfigSource, ConfigError};
struct VaultSource { /* client */ }
#[async_trait]
impl ConfigSource for VaultSource {
async fn load(&self) -> Result<(), ConfigError> {
// prefetch / login
Ok(())
}
async fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
// HTTP / SDK
let _ = key;
Ok(None)
}
}
```
`get_or_default` has a default impl on the trait (`get` then `default`).
## Rules
- Call `load(source).await` **before** any `resolve` of a config type.
- `load` once per process. A second `load` errors (`AlreadyLoaded`).
- After `load`, two `resolve::<T>()` calls return `Arc` pointer-equal clones.
- Do not also `dependency!(singleton, T)` for the same `T` (duplicate `Resolver`).
- Inject as usual: `#[derive(Injectable)] struct S { db: Arc<DatabaseConfig> }`.
- No `Any`, no `TypeId`. Registry is an `RwLock<Vec<fn>>`; each fn sets a typed `OnceLock`.