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
//! A command-line front end: flags over the environment, `--check` instead of
//! booting.
//!
//! ```text
//! cargo run -p dynamic-config --example cli --features json,clap -- --check
//! cargo run -p dynamic-config --example cli --features json,clap -- --port 9000
//! cargo run -p dynamic-config --example cli --features json,clap -- --set host=0.0.0.0
//! ```
//!
//! Keys are relative to the section this struct maps to, so it is `host`, not
//! `server.host`: each configuration type owns its own flags layer, exactly as
//! it owns its own files and environment prefix.
use clap::{Arg, ArgAction, Command};
use dynamic_config::dynamic_config;
use serde::Deserialize;
#[dynamic_config(files = ["dynamic-config/examples/config.json"], key = "server", env = "APP_")]
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
#[allow(dead_code)]
tags: Vec<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = Command::new("cli")
.arg(Arg::new("port").long("port").help("override server.port"))
.arg(
Arg::new("set")
.long("set")
.action(ArgAction::Append)
.help("key=value relative to the section, repeatable"),
)
.arg(
Arg::new("check")
.long("check")
.action(ArgAction::SetTrue)
.help("report the configuration and exit"),
)
.get_matches();
// Named arguments first, then the escape hatch for anything without a flag
// of its own. Both land in the same layer, above the environment.
ServerConfig::bind_clap(&matches, &[("port", "port")])?;
if let Some(assignments) = matches.get_many::<String>("set") {
ServerConfig::set_assignments(assignments)?;
}
// `--check` has to work when the configuration is broken, which is exactly
// when it is worth running — so it reports rather than loading.
if matches.get_flag("check") {
let report = ServerConfig::check()?;
println!("{report}");
return if report.is_clean() {
Ok(())
} else {
Err("configuration is not clean".into())
};
}
ServerConfig::init()?;
let config = ServerConfig::current();
println!("listening on {}:{}", config.host, config.port);
Ok(())
}