{"id":36956568,"url":"https://github.com/iepathos/premortem","last_synced_at":"2026-01-13T14:01:13.940Z","repository":{"id":326590452,"uuid":"1104212713","full_name":"iepathos/premortem","owner":"iepathos","description":"Premortem for your app's config—finds all the ways it would die before it ever runs.","archived":false,"fork":false,"pushed_at":"2025-12-15T04:58:24.000Z","size":866,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-17T23:14:51.626Z","etag":null,"topics":["config","configuration","error-handling","functional-programming","rust","serde","settings","type-safety","validation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iepathos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-25T22:59:57.000Z","updated_at":"2025-12-15T04:54:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/iepathos/premortem","commit_stats":null,"previous_names":["iepathos/premortem"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/iepathos/premortem","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iepathos%2Fpremortem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iepathos%2Fpremortem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iepathos%2Fpremortem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iepathos%2Fpremortem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iepathos","download_url":"https://codeload.github.com/iepathos/premortem/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iepathos%2Fpremortem/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28387596,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T13:42:20.960Z","status":"ssl_error","status_checked_at":"2026-01-13T13:42:03.276Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config","configuration","error-handling","functional-programming","rust","serde","settings","type-safety","validation"],"created_at":"2026-01-13T14:01:12.505Z","updated_at":"2026-01-13T14:01:13.924Z","avatar_url":"https://github.com/iepathos.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# premortem\n\n\u003e Know how your app will die—before it does.\n\n[![Crates.io](https://img.shields.io/crates/v/premortem.svg)](https://crates.io/crates/premortem)\n[![Downloads](https://img.shields.io/crates/d/premortem)](https://crates.io/crates/premortem)\n[![CI](https://github.com/iepathos/premortem/actions/workflows/ci.yml/badge.svg)](https://github.com/iepathos/premortem/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-MIT)](LICENSE)\n\nA configuration library that performs a **premortem** on your app's config—finding all the ways it would die before it ever runs.\n\n## Why \"premortem\"?\n\nThe 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*.\n\nA **postmortem** is what you do *after* something dies—gathering everyone to analyze what went wrong. Traditional config libraries give you the postmortem experience:\n\n```\n$ ./myapp\nError: missing field `database.host`\n\n$ ./myapp  # fixed it, try again\nError: invalid port value\n\n$ ./myapp  # fixed that too\nError: pool_size must be positive\n\n# Three deaths to find three problems\n```\n\n**premortem** gives you all the fatal issues upfront:\n\n```\n$ ./myapp\nConfiguration errors (3):\n  [config.toml:8] missing required field 'database.host'\n  [env:APP_PORT] value \"abc\" is not a valid integer\n  [config.toml:10] 'pool_size' value -5 must be \u003e= 1\n```\n\nOne run. All errors. Know how your app would die—before it does.\n\nTry it yourself: `cargo run --example error-demo`\n\n## Features\n\n- **Accumulate all errors** — Never stop at the first problem\n- **Trace value origins** — Know exactly which source provided each value\n- **Multi-source loading** — Files, environment, CLI args, remote sources\n- **Holistic validation** — Type, range, format, cross-field, and business rules\n- **Derive macro** — Declarative validation with `#[derive(Validate)]`\n- **Hot reload** — Watch for config changes (optional feature)\n\n## Quick Start\n\n```rust\nuse premortem::{Config, Toml, Env, Validate};\nuse serde::Deserialize;\n\n#[derive(Debug, Deserialize, Validate)]\nstruct AppConfig {\n    #[validate(non_empty)]\n    pub host: String,\n\n    #[validate(range(1..=65535))]\n    pub port: u16,\n\n    #[validate(range(1..=100))]\n    pub pool_size: u32,\n}\n\nfn main() {\n    let config = Config::\u003cAppConfig\u003e::builder()\n        .source(Toml::file(\"config.toml\"))\n        .source(Env::prefix(\"APP_\"))\n        .build()\n        .unwrap_or_else(|errors| {\n            eprintln!(\"Configuration errors ({}):\", errors.len());\n            for e in \u0026errors {\n                eprintln!(\"  {}\", e);\n            }\n            std::process::exit(1);\n        });\n\n    println!(\"Starting server on {}:{}\", config.host, config.port);\n}\n```\n\n## Installation\n\n```toml\n[dependencies]\npremortem = \"0.6\"\n```\n\nWith optional features:\n\n```toml\n[dependencies]\npremortem = { version = \"0.6\", features = [\"json\", \"watch\"] }\n```\n\n## Feature Flags\n\n| Feature | Description |\n|---------|-------------|\n| `toml` | TOML file support (default) |\n| `json` | JSON file support |\n| `yaml` | YAML file support |\n| `watch` | Hot reload / file watching |\n| `remote` | Remote sources (planned) |\n| `full` | All features |\n\n## Examples\n\nSee the [`examples/`](./examples/) directory for runnable examples:\n\n| Example | Description |\n|---------|-------------|\n| [error-demo](./examples/error-demo/) | Error output with source location tracking |\n| [env-validation](./examples/env-validation/) | Required environment variables with error accumulation |\n| [layered-config](./examples/layered-config/) | Multi-source config with value tracing |\n| [layered](./examples/layered/) | Environment-based layered configuration |\n| [basic](./examples/basic/) | Minimal configuration loading |\n| [validation](./examples/validation/) | Comprehensive validation patterns |\n| [testing](./examples/testing/) | Configuration testing with MockEnv |\n| [tracing](./examples/tracing/) | Value origin tracing demonstration |\n| [watch](./examples/watch/) | Hot reload with automatic file watching |\n| [web-server](./examples/web-server/) | Axum web server configuration |\n| [yaml](./examples/yaml/) | YAML configuration file loading |\n\nRun an example:\n\n```bash\ncargo run --example error-demo\ncargo run --example layered-config\ncargo run --example yaml --features yaml\n```\n\n## Documentation\n\n- [Common Patterns](./docs/PATTERNS.md) — Layered config, secrets, nested structs\n- [Testing Guide](./docs/TESTING.md) — Testing with MockEnv\n\n## Core Concepts\n\n### Source Layering\n\nSources are applied in order, with later sources overriding earlier ones:\n\n```rust\nlet config = Config::\u003cAppConfig\u003e::builder()\n    .source(Defaults::from(AppConfig::default()))  // Lowest priority\n    .source(Toml::file(\"config.toml\"))\n    .source(Env::prefix(\"APP_\"))                   // Highest priority\n    .build()?;\n```\n\n### Required Environment Variables\n\nMark environment variables as required at the source level with error accumulation:\n\n```rust\nlet config = Config::\u003cAppConfig\u003e::builder()\n    .source(\n        Env::prefix(\"APP_\")\n            .require_all(\u0026[\"JWT_SECRET\", \"DATABASE_URL\", \"API_KEY\"])\n    )\n    .build()?;\n```\n\nAll missing required variables are reported together:\n\n```\nConfiguration errors (3):\n  [env:APP_JWT_SECRET] Missing required field: jwt.secret\n  [env:APP_DATABASE_URL] Missing required field: database.url\n  [env:APP_API_KEY] Missing required field: api.key\n```\n\nThis separates **presence validation** (does the variable exist?) from **value validation** (does it meet constraints?).\n\n### Testable I/O\n\nAll I/O is abstracted through `ConfigEnv`, enabling testing with `MockEnv`:\n\n```rust\nlet env = MockEnv::new()\n    .with_file(\"config.toml\", \"port = 8080\")\n    .with_env(\"APP_HOST\", \"localhost\");\n\nlet config = Config::\u003cAppConfig\u003e::builder()\n    .source(Toml::file(\"config.toml\"))\n    .source(Env::prefix(\"APP_\"))\n    .build_with_env(\u0026env)?;\n```\n\n### Value Tracing\n\nDebug where configuration values came from:\n\n```rust\nlet traced = Config::\u003cAppConfig\u003e::builder()\n    .source(Defaults::from(AppConfig::default()))\n    .source(Toml::file(\"config.toml\"))\n    .source(Env::prefix(\"APP_\"))\n    .build_traced()?;\n\n// Check what was overridden\nfor path in traced.overridden_paths() {\n    let trace = traced.trace(path).unwrap();\n    println!(\"{}: {:?} from {}\", path, trace.final_value.value, trace.final_value.source);\n}\n```\n\n## License\n\nMIT Glen Baker \u003ciepathos@gmail.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiepathos%2Fpremortem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiepathos%2Fpremortem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiepathos%2Fpremortem/lists"}