1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Layered typed configuration for Rust.
//!
//! `config-kit` centralizes the estate's shared configuration pattern:
//! `serde`-typed settings assembled from layered sources — a base file,
//! then environment variables, then in-process overrides — with
//! unknown-key denial at the top level, redacted secrets, and optional
//! hot reload.
//!
//! # Design
//!
//! - **Typed and layered.** You describe the target type; layers produce
//! values that deep-merge key-by-key in the order added. Driven in the
//! documented order, precedence is **overrides > env > file**: later
//! layers win on colliding keys, and table contents merge so a late
//! layer can override one nested field without clobbering siblings.
//! - **Strict by choice.** [`ConfigBuilder::load`] ignores unknown keys;
//! [`ConfigBuilder::load_strict`] denies unconsumed **top-level** keys
//! with a typed error listing them — container-agnostically, for any
//! `DeserializeOwned` type, without derive support (see
//! [`load_strict`](ConfigBuilder::load_strict) for the exact contract).
//! - **Secrets are redacted by construction.** [`Sensitive<T>`] renders
//! `"[REDACTED]"` for `Debug` and `Display` (bound-free impls, so
//! derived container `Debug`s cannot leak either) and requires an
//! explicit, grep-friendly `.expose()` to read.
//! - **Formats.** TOML is core. JSON (`json` feature) and YAML (`yaml`
//! feature) are optional; format is picked by file extension, defaulting
//! to TOML.
//! - **Hot reload, opt-in.** The `hot-reload` feature adds
//! [`watch`]: a 300 ms-debounced, `notify`-backed watcher that feeds
//! freshly parsed values to your callback and records reload failures
//! instead of panicking.
//! - **Tier-A hygiene.** `#![forbid(unsafe_code)]`, `#![deny(missing_docs)]`,
//! clippy `unwrap_used`/`expect_used`/`panic`/`indexing_slicing` denied —
//! the public surface is fallible and typed, never panicking.
//!
//! # Example
//!
//! File layer omitted here so the example runs stand-alone; combine all
//! three layers for the documented precedence:
//!
//! ```
//! use config_kit::{ConfigBuilder, ConfigLayer, Sensitive};
//! use serde::Deserialize;
//! use std::collections::BTreeMap;
//!
//! #[derive(Deserialize)]
//! struct Service {
//! port: u16,
//! debug: bool,
//! token: Sensitive<String>,
//! }
//!
//! std::env::set_var("CKIT_EXAMPLE_SERVICE_TOKEN", "hunter2");
//! std::env::set_var("CKIT_EXAMPLE_SERVICE_DEBUG", "true");
//! let mut over: BTreeMap<String, toml::Value> = BTreeMap::new();
//! over.insert("port".into(), toml::Value::Integer(9090));
//!
//! let service: Service = ConfigBuilder::new()
//! .layer(ConfigLayer::env_prefix("CKIT_EXAMPLE_SERVICE_"))
//! .layer(ConfigLayer::overrides(over))
//! .load()?;
//!
//! assert_eq!(service.port, 9090); // overrides beat env
//! assert!(service.debug);
//! assert_eq!(service.token.expose(), "hunter2"); // the only way in
//! assert_eq!(format!("{:?}", service.token), "[REDACTED]");
//! # Ok::<(), config_kit::ConfigError>(())
//! ```
//!
//! # Strict loading
//!
//! [`ConfigBuilder::load_strict`] rejects typo'd keys at the top level:
//!
//! ```no_run
//! use config_kit::{ConfigBuilder, ConfigLayer};
//! use serde::Deserialize;
//! use tempfile::TempDir;
//!
//! #[derive(Deserialize, Debug)]
//! struct Db {
//! url: String,
//! }
//! #[derive(Deserialize, Debug)]
//! struct Settings {
//! db: Db,
//! }
//!
//! let dir = TempDir::new().expect("tempdir");
//! let path = dir.path().join("settings.toml");
//! std::fs::write(
//! &path,
//! "db = { url = \"postgres://localhost\" }\ntypo_key = 1\n",
//! )
//! .expect("write");
//!
//! let error = ConfigBuilder::new()
//! .layer(ConfigLayer::file(&path))
//! .load_strict::<Settings>()
//! .expect_err("typo_key is not a field");
//! assert!(error.to_string().contains("typo_key"), "{error}");
//! ```
//!
//! # Environment typing
//!
//! Env values are inferred: `true`/`false` → bool, integer → `i64`, float
//! → `f64`, else verbatim string; keys are prefix-stripped and lowercased
//! (`APP_PORT` → `port`). See [`ConfigLayer::env_prefix`].
//!
//! # Hot reload
//!
//! With the `hot-reload` feature, `watch` spawns a 300 ms-debounced,
//! `notify`-backed watcher that parses the file on settle and hands the
//! fresh value to your callback; reload failures are recorded (never
//! panic) and retrievable via `ConfigWatcher::last_error`, and
//! `ConfigWatcher::stop` joins the worker. See the `watch` function's
//! documentation for a runnable example.
pub use ;
pub use ConfigError;
pub use ConfigLayer;
pub use Sensitive;
pub use ;