premortem 0.1.1

A configuration library that performs a premortem on your app's config—finding all the ways it would die before it ever runs
Documentation

premortem

Know how your app will die—before it does.

CI Coverage Security Crates.io Documentation License

A configuration library that performs a premortem on your app's config—finding all the ways it would die before it ever runs.

Why "premortem"?

The name is a bit tongue-in-cheek—but only a bit. Configuration errors are one of the leading causes of production outages. Bad config doesn't just cause bugs; it causes incidents, pages, and 3am debugging sessions.

A postmortem is what you do after something dies—gathering everyone to analyze what went wrong. Traditional config libraries give you the postmortem experience:

$ ./myapp
Error: missing field `database.host`

$ ./myapp  # fixed it, try again
Error: invalid port value

$ ./myapp  # fixed that too
Error: pool_size must be positive

# Three deaths to find three problems

premortem gives you all the fatal issues upfront:

$ ./myapp
Configuration errors (3):
  [config.toml:8] missing required field 'database.host'
  [env:APP_PORT] value "abc" is not a valid integer
  [config.toml:10] 'pool_size' value -5 must be >= 1

One run. All errors. Know how your app would die—before it does.

Features

  • Accumulate all errors — Never stop at the first problem
  • Trace value origins — Know exactly which source provided each value
  • Multi-source loading — Files, environment, CLI args, remote sources
  • Holistic validation — Type, range, format, cross-field, and business rules
  • Derive macro — Declarative validation with #[derive(Validate)]
  • Hot reload — Watch for config changes (optional feature)

Quick Start

use premortem::{Config, Toml, Env, Validate};
use serde::Deserialize;

#[derive(Debug, Deserialize, Validate)]
struct AppConfig {
    #[validate(non_empty)]
    pub host: String,

    #[validate(range(1..=65535))]
    pub port: u16,

    #[validate(range(1..=100))]
    pub pool_size: u32,
}

fn main() {
    let config = Config::<AppConfig>::builder()
        .source(Toml::file("config.toml"))
        .source(Env::prefix("APP_"))
        .build()
        .unwrap_or_else(|errors| {
            eprintln!("Configuration errors ({}):", errors.len());
            for e in &errors {
                eprintln!("  {}", e);
            }
            std::process::exit(1);
        });

    println!("Starting server on {}:{}", config.host, config.port);
}

Installation

[dependencies]
premortem = "0.1"

With optional features:

[dependencies]
premortem = { version = "0.1", features = ["json", "yaml", "watch"] }

Feature Flags

Feature Description
toml TOML file support (default)
json JSON file support
yaml YAML file support
watch Hot reload / file watching
remote Remote sources (Consul, etcd, Vault, etc.)
full All features

Examples

See the examples/ directory for runnable examples:

Example Description
basic Minimal configuration loading
validation Comprehensive validation patterns
testing Configuration testing with MockEnv
layered Multi-source environment-specific config
tracing Value origin debugging

Run an example:

cd examples/basic
cargo run

Documentation

Core Concepts

Source Layering

Sources are applied in order, with later sources overriding earlier ones:

let config = Config::<AppConfig>::builder()
    .source(Defaults::from(AppConfig::default()))  // Lowest priority
    .source(Toml::file("config.toml"))
    .source(Env::prefix("APP_"))                   // Highest priority
    .build()?;

Testable I/O

All I/O is abstracted through ConfigEnv, enabling testing with MockEnv:

let env = MockEnv::new()
    .with_file("config.toml", "port = 8080")
    .with_env("APP_HOST", "localhost");

let config = Config::<AppConfig>::builder()
    .source(Toml::file("config.toml"))
    .source(Env::prefix("APP_"))
    .build_with_env(&env)?;

Value Tracing

Debug where configuration values came from:

let traced = Config::<AppConfig>::builder()
    .source(Defaults::from(AppConfig::default()))
    .source(Toml::file("config.toml"))
    .source(Env::prefix("APP_"))
    .build_traced()?;

// Check what was overridden
for path in traced.overridden_paths() {
    let trace = traced.trace(path).unwrap();
    println!("{}: {:?} from {}", path, trace.final_value.value, trace.final_value.source);
}

License

MIT Glen Baker iepathos@gmail.com