Skip to content

Typed Config Binding

Setup

Enable the macros feature in Cargo.toml:

[dependencies]
rust-web-server = { version = "17", features = ["macros"] }

Quick start

use rust_web_server::Config;
#[derive(rust_web_server::Config)]
#[config(prefix = "APP_")]
struct AppConfig {
#[config(env = "PORT", default = "8080")]
port: u16,
#[config(env = "DATABASE_URL")]
database_url: String,
#[config(env = "DEBUG")]
debug: Option<bool>,
}
fn main() {
let cfg = AppConfig::load().expect("invalid config");
println!("listening on port {}", cfg.port);
}

#[derive(Config)]

The derive macro generates a single method on the struct:

impl AppConfig {
pub fn load() -> Result<Self, String> { ... }
}

load() reads every field from environment variables and returns Err(String) with a descriptive message on the first parse failure.

Struct-level attribute

#[config(prefix = "APP_")]

When set, every field’s environment variable key is prefix + key. With prefix = "APP_" and a field named port, the default env var is APP_PORT.

Field-level attribute

#[config(env = "MY_PORT", default = "8080")]
OptionMeaning
env = "KEY"Explicit env var name. The struct prefix is still prepended.
default = "v"Fallback string when the env var is absent. Parsed with the same FromEnvStr impl as a present value.

If env is omitted the field name is uppercased (pool_sizePOOL_SIZE), then the prefix is prepended.

Field derivation rules

Field declarationEnv var absentEnv var present
field: T with defaultparse default valueparse env var
field: T without defaultErr — requiredparse env var
field: Option<T>Ok(None)parse env var, wrap in Some

FromEnvStr trait

All primitive Rust scalar types implement FromEnvStr out of the box:

String, bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64

bool accepts "true", "1", "yes"true; "false", "0", "no"false.

Implement FromEnvStr on your own type to use it as a config field:

use rust_web_server::config_binding::FromEnvStr;
#[derive(Debug)]
enum LogLevel { Error, Warn, Info, Debug }
impl FromEnvStr for LogLevel {
fn from_env_str(s: &str) -> Result<Self, String> {
match s.trim().to_ascii_lowercase().as_str() {
"error" => Ok(LogLevel::Error),
"warn" => Ok(LogLevel::Warn),
"info" => Ok(LogLevel::Info),
"debug" => Ok(LogLevel::Debug),
other => Err(format!("unknown log level: {other}")),
}
}
}

Complete example

use rust_web_server::config_binding::FromEnvStr;
use rust_web_server::Config;
#[derive(Debug)]
enum LogLevel { Error, Warn, Info, Debug }
impl FromEnvStr for LogLevel {
fn from_env_str(s: &str) -> Result<Self, String> {
match s.trim().to_ascii_lowercase().as_str() {
"error" => Ok(LogLevel::Error),
"warn" => Ok(LogLevel::Warn),
"info" => Ok(LogLevel::Info),
"debug" => Ok(LogLevel::Debug),
other => Err(format!("unknown log level: {other}")),
}
}
}
#[derive(Debug, rust_web_server::Config)]
#[config(prefix = "APP_")]
struct AppConfig {
/// APP_HOST — defaults to "0.0.0.0"
#[config(env = "HOST", default = "0.0.0.0")]
host: String,
/// APP_PORT — defaults to 8080
#[config(env = "PORT", default = "8080")]
port: u16,
/// APP_DB_HOST — required, no default
#[config(env = "DB_HOST")]
db_host: String,
/// APP_DB_PORT — defaults to 5432
#[config(env = "DB_PORT", default = "5432")]
db_port: u16,
/// APP_DB_POOL_SIZE — defaults to 10
#[config(env = "DB_POOL_SIZE", default = "10")]
db_pool_size: u32,
/// APP_LOG_LEVEL — optional; None if not set
#[config(env = "LOG_LEVEL")]
log_level: Option<LogLevel>,
}
fn main() {
let cfg = AppConfig::load().unwrap_or_else(|e| {
eprintln!("Config error: {e}");
std::process::exit(1);
});
println!("starting on {}:{}", cfg.host, cfg.port);
println!("db: {}:{} pool={}", cfg.db_host, cfg.db_port, cfg.db_pool_size);
if let Some(level) = cfg.log_level {
println!("log level: {:?}", level);
}
}

Set environment variables before running:

Terminal window
export APP_DB_HOST=postgres.internal
export APP_LOG_LEVEL=info
cargo run