Layer defaults, a file, and environment variables
Most applications resolve their configuration from several places: defaults that
ship with the binary, a config file an operator edits, and environment variables
set at deploy time. config builds one typed value from those layers,
where each source overrides the one before it.
ConfigBuilder collects sources in precedence order. set_default seeds the
baseline values, File loads config.toml (marked required(false) so a
missing file falls back to the defaults), and Environment reads APP_-prefixed
variables. APP_PORT=9090 sets the port key. build merges them and
try_deserialize produces the typed Settings.
use std::path::Path;
use config::{Config, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Settings {
host: String,
port: u16,
log_level: String,
}
fn load(config_path: &Path) -> Result<Settings, config::ConfigError> {
Config::builder()
.set_default("host", "127.0.0.1")?
.set_default("port", 8080)?
.set_default("log_level", "info")?
.add_source(File::from(config_path).required(false))
.add_source(Environment::with_prefix("APP"))
.build()?
.try_deserialize()
}
fn main() -> Result<(), config::ConfigError> {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config.toml");
let settings = load(&path)?;
println!("listening on {}:{}", settings.host, settings.port);
println!("log level: {}", settings.log_level);
Ok(())
}
The bundled config.toml supplies the file layer:
host = "0.0.0.0"
port = 3000
log_level = "debug"