# tanzim
Facade crate for a small, composable configuration pipeline: **load → parse → merge**.
`tanzim` lets you describe *where* configuration comes from with short source
strings (environment variables, files, HTTP, …), parse each source into a
typed value tree that remembers its origin, and merge everything into one map
keyed by entry name. Every value keeps its source location, so errors point at
the exact file, line, and column.
## Pipeline
```text
"env(prefix=APP_)" "file:/etc/app" ← source strings
│
▼ load Load::load(source) → Vec<Payload> (raw bytes + maybe name + maybe format)
▼ parse Parse::parse(bytes) → LocatedValue (typed tree + Location)
▼ merge Merge::merge(parsed) → HashMap<name, …> (grouped + combined)
│
▼
merged configuration
```
`PipelineMulti::run()` (or `PipelineSingle::run()` for a unified value) executes all stages. Each stage is also callable on its
own via `load()`, `parse()`, and `merge()` — useful for
inspecting intermediate results or building a custom pipeline.
## Workspace crates
`tanzim` re-exports each stage so you rarely depend on them directly, but they
are independently usable:
| `source` | [`tanzim-source`](crates/tanzim-source/README.md) | Parse the `SOURCE[(OPTIONS)][?][:RESOURCE]` source-string format into a validated [`Source`] |
| `loader` | [`tanzim-load`](crates/tanzim-load/README.md) | `Load` trait + `env`/`file`/`http`/`closure` loaders → `Payload` |
| `parser` | [`tanzim-parse`](crates/tanzim-parse/README.md) | `Parse` trait + `env`/`json`/`yaml`/`toml` parsers → `LocatedValue` |
| `merge` | [`tanzim-merge`](crates/tanzim-merge/README.md) | `Merge` trait + `LastWins`/`DeepMerge` strategies → grouped map |
| — | [`tanzim-value`](crates/tanzim-value/README.md) | Core `Value`, `LocatedValue`, `Map`, `Location`, `Error` types shared by every stage |
## Key concepts
- **Source strings** use the [`tanzim-source`](crates/tanzim-source/README.md) format
`SOURCE [(OPTIONS)] [?] [:RESOURCE]`, e.g. `env(prefix=APP_)`, `file?:.env`.
- **Named entries** — each `Payload` carries an optional `maybe_name`. The merger groups
by name; unnamed payloads (`maybe_name == None`) all share the `None` key.
- **Format auto-detection** — if a payload has no `maybe_format`, parsers are probed via
`is_format_supported`; otherwise the format hint selects the parser.
- **`ignore_errors` (`?`)** — sources marked with `?` swallow load/parse failures
silently instead of aborting the pipeline.
- **Located errors** — [`Error`] renders one line by default; use `{error:#}` for a
source snippet with a caret underline.
- **Result aliases** — `parse()` returns `Vec<Parsed>` and `merge()`/`run()` return
`Merged` (`HashMap<Option<String>, (Vec<Payload>, LocatedValue)>`).
## Features
| `load-env` / `load-file` / `load-http` | env / filesystem / HTTP (closure-based) loaders |
| `parse-env` | env parser |
| `parse-json` / `parse-yaml` / `parse-toml` | format parsers |
| `validate-default` | the std-only validators (no extra dependencies) |
| `validate-schema` | schema machinery (`with_schema`, the validation stage) |
| `validate-<name>` | one validator (e.g. `validate-url`, `validate-regex`), pulling in `validate-schema` |
| `validate-full` | every validator + schema |
| `full` | all loaders + all parsers + `validate-full` (examples, smoke test) |
| `logging` / `tracing` | optional log integration across all crates |
Defaults: `load-env`, `load-file`, `load-http`, `parse-env`, `validate-default`, `validate-schema`.
Use individual workspace crates if you only need one stage — see [tanzim-load/README.md](crates/tanzim-load/README.md), [tanzim-parse/README.md](crates/tanzim-parse/README.md), [tanzim-merge/README.md](crates/tanzim-merge/README.md).
## Quick start
```rust,no_run
use tanzim::multi::PipelineMultiBuilder;
use tanzim::loader::{env::Env, file::File};
use tanzim::parser::{env::Env as EnvParser, json::Json, yaml::Yaml, toml::Toml};
use tanzim::merge::DeepMerge;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let merged = PipelineMultiBuilder::new()
.with_loader(Env::new())
.with_loader(File::new())
.with_parser(EnvParser::new())
.with_parser(Json::new())
.with_parser(Yaml::new())
.with_parser(Toml::new())
.with_merger(DeepMerge)
.with_source("env(prefix=MY_APP_,separator=.)")?
.with_source("file:examples/full/etc")?
.build()?
.run()?;
for (name, (_sources, value)) in &merged {
let display = match name {
None => "(unnamed)",
Some(n) => n.as_str(),
};
println!("{display}: {value}");
}
Ok(())
}
```
## Examples
```shell
make examples # run every example
make example-full # full pipeline over env + file sources
```
`example-full` reads the source strings declared in the `Makefile` and the
sample config under `examples/full/etc/`. The individual stage crates ship their
own examples too (see each crate's `examples/` directory).
## Development
```shell
make all # build every crate
make test # run unit tests + doctests across the workspace
make clippy # lint with `-D warnings` across all targets and features
make check-style # cargo fmt --check
make docs # build rustdoc for the whole workspace
```