Start with one struct. The prefix is used for environment variables and for
the default file-discovery names. A trailing underscore is conventional and
keeps names such as ACME_PORT easy to read.
use ortho_config::{OrthoConfig, OrthoResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, OrthoConfig)]
#[ortho_config(prefix = "ACME_")]
struct Config {
#[ortho_config(default = String::from("127.0.0.1"))]
host: String,
#[ortho_config(default = 8080, cli_short = 'p')]
port: u16,
#[ortho_config(default = String::from("info"))]
log_level: String,
}
fn main() -> OrthoResult<()> {
let config = Config::load()?;
println!(
"host={} port={} log_level={}",
config.host, config.port, config.log_level
);
Ok(())
}
That one definition provides three spellings for each field:
| Rust field | Command line | Environment | TOML |
|---|---|---|---|
host |
--host |
ACME_HOST |
host |
port |
--port or -p |
ACME_PORT |
port |
log_level |
--log-level |
ACME_LOG_LEVEL |
log_level |
Table 1: Rust fields and their command-line, environment, and TOML names.
Values are merged from lowest to highest precedence:
#[ortho_config(default = ...)]values;- configuration files;
- environment variables; and
- command-line arguments.
This means a checked-in file can provide team defaults, an environment variable can adapt them for a deployment, and a one-off CLI option can override both.