Find all configuration errors before your application starts, not one at a time in production
Links: GitHub | Crates.io | Documentation
Overview#
Premortem is a Rust configuration library that performs comprehensive validation before your application starts. Instead of discovering configuration errors one at a time during execution, premortem reveals all fatal issues upfront - a “premortem” for your configuration.
The Problem#
Configuration errors are a leading cause of production outages:
- Sequential failure discovery - Traditional config loading fails on the first error, requiring multiple deploy cycles to find all issues
- Runtime surprises - Missing or invalid configuration values surface only when code paths are executed
- Lost provenance - When values come from multiple sources (files, env vars, CLI), it’s unclear where a bad value originated
- Testing complexity - Configuration loading with I/O makes testing difficult
- No hot-reload - Configuration changes require application restarts
The Solution#
Premortem validates your entire configuration upfront and tells you everything that’s wrong in one pass:
use premortem::{Config, Validate};
#[derive(Config, Validate)]
struct AppConfig {
#[validate(min = 1, max = 65535)]
port: u16,
#[validate(non_empty)]
database_url: String,
#[validate(min = 1)]
max_connections: u32,
#[config(default = 30)]
timeout_seconds: u32,
}
fn main() {
// Validates ALL fields, reports ALL errors at once
match AppConfig::load() {
Ok(config) => run_app(config),
Err(errors) => {
// See every configuration problem, not just the first
for error in errors {
eprintln!("{}: {} (from {})",
error.field,
error.message,
error.source);
}
std::process::exit(1);
}
}
}
Key Features#
Error Accumulation#
Collect all validation errors in a single pass:
// Instead of:
// Error: port must be positive
// [fix and redeploy]
// Error: database_url cannot be empty
// [fix and redeploy]
// Error: max_connections must be at least 1
// You get:
// Errors:
// - port: must be between 1 and 65535 (from: environment)
// - database_url: cannot be empty (from: config.toml)
// - max_connections: must be at least 1 (from: default)
Value Provenance Tracking#
Know exactly where each configuration value came from:
let config = AppConfig::load_traced()?;
// See the source of any value
println!("port = {} (from {})", config.port.value, config.port.source);
// Output: port = 8080 (from environment:PORT)
Layered Configuration#
Compose configuration from multiple sources with clear precedence:
let config = AppConfig::builder()
.with_defaults()
.with_file("config.toml")?
.with_file("config.local.toml").optional()
.with_env_prefix("APP")
.with_cli_args()
.build()?;
Declarative Validation#
Express validation rules directly in your type definitions:
#[derive(Config, Validate)]
struct DatabaseConfig {
#[validate(url, scheme = "postgres")]
connection_string: String,
#[validate(range = 1..=100)]
pool_size: u32,
#[validate(duration, min = "1s", max = "30s")]
connect_timeout: Duration,
#[validate(custom = "validate_ssl_mode")]
ssl_mode: SslMode,
}
Hot-Reload Support#
Optionally watch for configuration changes:
let config = AppConfig::load_watched(|new_config| {
// Called when config files change
update_runtime_settings(new_config);
})?;
Testable Configuration#
Abstract I/O through ConfigEnv for easy testing:
#[test]
fn test_config_validation() {
let env = MockConfigEnv::new()
.with_var("PORT", "invalid")
.with_var("DATABASE_URL", "");
let result = AppConfig::load_with_env(&env);
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 2);
}
Technical Highlights#
- Language: Rust
- Derive Macros:
Config,Validatefor declarative configuration - Format Support: TOML, JSON, YAML (via feature flags)
- Serialization: Built on serde for broad compatibility
- Testing: Mockable I/O layer for unit tests
Installation#
# Add to Cargo.toml
cargo add premortem
# With format support
cargo add premortem --features toml,json,yaml
# With hot-reload
cargo add premortem --features watch
Quick Start#
use premortem::{Config, Validate};
#[derive(Config, Validate)]
struct ServerConfig {
#[validate(min = 1024, max = 65535)]
port: u16,
#[validate(non_empty)]
host: String,
#[config(default = false)]
debug: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ServerConfig::builder()
.with_file("server.toml")?
.with_env_prefix("SERVER")
.build()?;
println!("Starting server on {}:{}", config.host, config.port);
Ok(())
}
Project Status#
Active development, available on crates.io.
Links#
- GitHub: github.com/iepathos/premortem
- Documentation: docs.rs/premortem
- Crates.io: Available via
cargo add premortem