provcfg
A Rust config loader that tracks where each value came from.
provcfg (provenance config) layers configuration sources (compiled-in
defaults, files, environment variables, CLI flags) into one struct, and keeps
the provenance of every leaf field: which source set the active value, and
which earlier sources it overrode.
Choosing between provcfg and config
For most projects the config crate is the
better fit. It is mature, supports more formats, and is less ceremony if all
you need is the merged result.
Use provcfg when you need to know which source set each value, for example:
- a settings page that labels each field "from file" / "from env" / "default";
- diagnosing "why is this value set to that?" across layered sources;
- an admin UI that renders the effective config together with its origin.
If you never ask "where did this come from?", prefer config.
What it does
Derive Configurable on a plain config struct. Config::build returns a
companion *Prov struct whose every leaf is a ValueHistory: the value paired
with the Source it came from.
use ;
// `APP_HOST=db.internal` is set in the environment; `APP_PORT` is not.
let settings = new
.add_env
.
.unwrap;
assert_eq!;
assert_eq!;
// `port` was set by nobody, so it falls back to the compiled-in default.
assert_eq!;
assert_eq!;
Layering sources
Sources are applied in the order they are added. A later source overrides an earlier one for any leaf it sets. Unset leaves keep the earlier value, and the overridden values stay in each leaf's history.
// `from_cli` is a `SettingsPartial` (every field an `Option`) produced by a
// CLI parser; here it carries only `port = 9090`.
let settings = new
.add_toml_str
.add_env // environment has APP_HOST=db.internal
.add_cli // command line had --port 9090
.
.unwrap;
assert_eq!; // env overrode the file
assert_eq!;
assert_eq!; // cli overrode the file
assert_eq!;
Inspecting provenance
sources_map returns a flat dotted.path -> Category map for a settings UI:
let map = settings.sources_map;
assert_eq!;
assert_eq!;
walk_leaves visits every leaf with its value, category, and a secret flag, so
an effective-config view can redact sensitive fields:
settings.walk_leaves;
Field attributes
Custom sources
To support a format the built-ins don't cover (XML, a database, a secret
store), implement the Source trait. Most sources build something that
implements serde::Deserializer (a serde_json::Value works fine) and erase
it through erased_serde:
use erased_serde;
use ;
let cfg = new
.add_source
.
.unwrap;
Two contract notes:
Source::deserializeis called synchronously fromConfig::build. I/O and async work belong in the source's constructor: fetch onnew, store the materialized data, hand it out on demand.- Convert your own error into
erased_serde::Errorvia<erased_serde::Error as serde::de::Error>::custom(my_error).Config::buildwraps that inError::Deserializewith the source'sname().
See the Source trait's rustdoc for the full doctested example.
Cargo features
Only env is enabled by default; opt into the file and CLI sources you need.
| Feature | Enables | Extra dependency |
|---|---|---|
env |
EnvSource / add_env (default) |
serde_json |
json |
JsonStr / add_json_* |
serde_json |
toml |
TomlStr / add_toml_* |
toml |
cli |
CliSource / add_cli |
serde_json |
clap-derive |
ClapArgs derive (provcfg-clap) |
clap |
[]
= { = "0.1", = ["toml", "cli"] }
clap integration
With the clap-derive feature, derive ClapArgs alongside Configurable to
generate a clap-compatible args struct that flows straight into a CLI source.
See the provcfg-clap crate.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.