# tanzim-source
Declarative configuration source: where to load from, loader options, and address.
## Format
```text
SOURCE [(OPTIONS)] [?] [:RESOURCE]
```
| `SOURCE` | Loader kind (opaque string, e.g. `env`, `file`, `http`) |
| `(OPTIONS)` | Optional loader options as `key=value` pairs |
| `?` | Skip errors when loading this source (non-fatal loader failures) |
| `:RESOURCE` | Optional address (path, URL, …); may be empty |
### String examples
```text
env
env(prefix=APP_)
file:/etc/app/config.json
file?:.env
file?
env:
http(headers=(Authorization="TOKEN"),timeout=3s)?:https://example.com/config.yml
env(kv=salam,h=(o=b,z=[1,2,3.14,""]))?:oops
```
### Rules
- **`SOURCE` and option keys** — ASCII letters, digits, `-`, `_`, `.` (non-empty).
- **No whitespace** anywhere except inside `"quoted"` strings.
- **Option values** — try, in order: boolean, integer, float, list, map; otherwise unquoted string.
- Booleans: `true` / `false` (case-insensitive).
- Integers: base-10, optional leading `-` (no `+`).
- Floats: digits, `.`, optional leading `-` (e.g. `3.14`; not `.5`).
- Lists: `[value,value,…]`
- Maps: `(key=value,…)` (same value grammar).
- Unquoted strings: letters, digits, `-`, `_`, `.` only (e.g. `APP_`, `3s`).
- Quoted strings: `"…"` with escapes `\"`, `\\`, `\n`, `\r`, `\t`.
- **Empty value** — error; use `""`.
- **Trailing commas** — error (`(a=1,)` / `[1,]`).
- **Duplicate keys** in `(OPTIONS)` — last wins.
- **`?` (skip errors)** — must come after `SOURCE` or after the closing `)` of `(OPTIONS)`; never before `(OPTIONS)` (wrong: `env?(prefix=APP)`, right: `env(prefix=APP)?`).
- **Errors** — [`ParseError`](https://docs.rs/tanzim-source/latest/tanzim_source/enum.ParseError.html) messages refer to *configuration source*. Use `{error:#}` for snippet + caret.
## Parsing
```rust
use tanzim_source::Source;
let env = Source::parse("env(prefix=APP_)")?;
let file: Source = "file:/etc/app/config.json".parse()?;
let dotenv = Source::try_from("file?:.env")?;
assert_eq!(env.source(), "env");
assert_eq!(env.to_string(), "env(prefix=APP_)");
assert_eq!(file.resource(), "/etc/app/config.json");
assert!(dotenv.skip_errors());
# Ok::<(), tanzim_source::ParseError>(())
```
Build programmatically with [`SourceBuilder`](https://docs.rs/tanzim-source/latest/tanzim_source/struct.SourceBuilder.html):
```rust
use tanzim_source::SourceBuilder;
let source = SourceBuilder::new()
.with_source("env")
.with_option("prefix", "APP")
.with_skip_errors(false)
.build()?;
# Ok::<(), tanzim_source::Error>(())
```
Try sources from the command line:
```shell
cargo run -p tanzim-source --example parse_sources -- \
'env(prefix=APP_)' \
'file:/etc/app/config.json'
```
## Serde
Enable the `serde` feature to embed [`Source`](https://docs.rs/tanzim-source/latest/tanzim_source/struct.Source.html) in config files as a **string** (canonical form round-trips):
```toml
[dependencies]
tanzim-source = { version = "0.1", features = ["serde"] }
```
```rust
# #[cfg(feature = "serde")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use tanzim_source::Source;
use serde::Deserialize;
#[derive(Deserialize)]
struct App {
sources: Vec<Source>,
}
let app: App = serde_json::from_str(r#"{ "sources": ["env(prefix=APP_)", "file:/etc/app/config.json"] }"#)?;
assert_eq!(app.sources[0].source(), "env");
# Ok(())
# }
# #[cfg(not(feature = "serde"))]
# fn main() {}
```
Invalid strings surface parse errors through serde with *configuration source* in the message:
```rust
# #[cfg(feature = "serde")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use tanzim_source::Source;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct App { source: Source }
let error = serde_json::from_str::<App>(r#"{ "source": "env(prefix=)" }"#).unwrap_err();
assert!(error.to_string().contains("configuration source option value"));
# Ok(())
# }
# #[cfg(not(feature = "serde"))]
# fn main() {}
```
## Cargo features
| `serde` | `Serialize` / `Deserialize` for `Source` as its canonical string |
## Relations
- Used by all other tanzim crates to represent where to load from.
- [`tanzim-load`](../tanzim-load/) consumes `Source` fields (`source`, `options`, `resource`) in its loaders.
- Full pipeline wired in [`tanzim`](../tanzim/).