premortem/
lib.rs

1// Allow large error types - detailed config errors are expected
2#![allow(clippy::result_large_err)]
3
4//! Premortem: A configuration library that performs a premortem on your app's config.
5//!
6//! Premortem validates your configuration before your application runs, finding all
7//! the ways it could die from bad config upfront. It uses stillwater's functional
8//! patterns for error accumulation and composable validation.
9//!
10//! # Core Concepts
11//!
12//! - **Error Accumulation**: Find ALL configuration errors, not just the first one
13//! - **Source Layering**: Merge config from files and environment variables
14//! - **Required Variables**: Declarative validation of required environment variables
15//! - **Testable I/O**: Dependency injection via `ConfigEnv` trait
16//! - **Type Safety**: Deserialize into your types with full validation
17//!
18//! # Quick Start
19//!
20//! ```ignore
21//! use premortem::prelude::*;
22//! use serde::Deserialize;
23//!
24//! #[derive(Debug, Deserialize)]
25//! struct AppConfig {
26//!     host: String,
27//!     port: u16,
28//! }
29//!
30//! impl Validate for AppConfig {
31//!     fn validate(&self) -> ConfigValidation<()> {
32//!         if self.port > 0 {
33//!             Validation::Success(())
34//!         } else {
35//!             Validation::fail_with(ConfigError::ValidationError {
36//!                 path: "port".to_string(),
37//!                 source_location: None,
38//!                 value: Some(self.port.to_string()),
39//!                 message: "port must be positive".to_string(),
40//!             })
41//!         }
42//!     }
43//! }
44//!
45//! fn main() -> Result<(), ConfigErrors> {
46//!     let config = Config::<AppConfig>::builder()
47//!         .source(Toml::file("config.toml"))
48//!         .source(Env::new().prefix("APP"))
49//!         .build()?;
50//!
51//!     println!("Running on {}:{}", config.host, config.port);
52//!     Ok(())
53//! }
54//! ```
55//!
56//! # Import Patterns
57//!
58//! ## Quick Start (Recommended)
59//!
60//! For most users, import the prelude:
61//!
62//! ```ignore
63//! use premortem::prelude::*;
64//! ```
65//!
66//! ## Selective Imports
67//!
68//! Import only what you need:
69//!
70//! ```ignore
71//! use premortem::{Config, Validate};
72//! use premortem::error::ConfigErrors;
73//! ```
74//!
75//! ## Advanced: Direct Stillwater Access
76//!
77//! For custom sources or advanced patterns:
78//!
79//! ```ignore
80//! use premortem::prelude::*;
81//! use stillwater::Effect;  // Direct stillwater access
82//! ```
83//!
84//! # Required Environment Variables
85//!
86//! Mark environment variables as required at the source level with error accumulation:
87//!
88//! ```ignore
89//! use premortem::prelude::*;
90//!
91//! let config = Config::<AppConfig>::builder()
92//!     .source(
93//!         Env::prefix("APP_")
94//!             .require_all(&["JWT_SECRET", "DATABASE_URL", "API_KEY"])
95//!     )
96//!     .build()?;
97//! ```
98//!
99//! All missing required variables are reported together:
100//!
101//! ```text
102//! Configuration errors (3):
103//!   [env:APP_JWT_SECRET] Missing required field: jwt.secret
104//!   [env:APP_DATABASE_URL] Missing required field: database.url
105//!   [env:APP_API_KEY] Missing required field: api.key
106//! ```
107//!
108//! This separates **presence validation** (does the variable exist?) from
109//! **value validation** (does it meet constraints?).
110//!
111//! # Architecture
112//!
113//! Premortem follows the "pure core, imperative shell" pattern:
114//!
115//! - **Pure Core**: Value merging, deserialization, and validation are pure functions
116//! - **Imperative Shell**: I/O operations use the `ConfigEnv` trait for dependency injection
117//!
118//! This architecture enables:
119//! - Easy unit testing with `MockEnv`
120//! - Composable validation with error accumulation
121//! - Clear separation of concerns
122//!
123//! # Module Structure
124//!
125//! - [`prelude`]: Convenient re-exports for common usage
126//! - [`config`]: `Config` and `ConfigBuilder` for loading configuration
127//! - [`error`]: Error types (`ConfigError`, `ConfigErrors`, `ConfigValidation`)
128//! - [`value`]: `Value` enum for intermediate representation
129//! - [`source`]: `Source` trait and `ConfigValues` container
130//! - [`mod@env`]: `ConfigEnv` trait and `MockEnv` for testing
131//! - [`validate`]: `Validate` trait for custom validation
132//!
133//! # Stillwater Integration
134//!
135//! Premortem uses these stillwater types:
136//!
137//! | Type | Usage |
138//! |------|-------|
139//! | `Validation<T, E>` | Error accumulation for config errors |
140//! | `NonEmptyVec<T>` | Guaranteed non-empty error lists |
141//! | `Semigroup` | Combining errors from multiple sources |
142//!
143//! These are re-exported from the prelude for convenience.
144
145pub mod config;
146pub mod env;
147pub mod error;
148pub mod prelude;
149pub mod pretty;
150pub mod source;
151pub mod sources;
152pub mod trace;
153pub mod validate;
154pub mod value;
155#[cfg(feature = "watch")]
156pub mod watch;
157
158// Re-exports for convenience
159pub use config::{Config, ConfigBuilder};
160pub use env::{ConfigEnv, MockEnv, RealEnv};
161pub use error::{
162    group_by_source, ConfigError, ConfigErrors, ConfigValidation, ConfigValidationExt,
163    SourceErrorKind, SourceLocation,
164};
165pub use pretty::{ColorOption, PrettyPrintOptions, ValidationExt};
166pub use source::{merge_config_values, ConfigValues, Source};
167pub use trace::{TraceBuilder, TracedConfig, TracedValue, ValueTrace};
168pub use validate::validators;
169pub use validate::{
170    current_source_location, custom, from_predicate, validate_field, validate_nested,
171    validate_optional_nested, validate_with_predicate, with_validation_context, SourceLocationMap,
172    Validate, ValidationContext, Validator, When,
173};
174pub use value::{ConfigValue, Value};
175
176// Re-export sources
177pub use sources::Env;
178#[cfg(feature = "json")]
179pub use sources::Json;
180#[cfg(feature = "toml")]
181pub use sources::Toml;
182#[cfg(feature = "yaml")]
183pub use sources::Yaml;
184pub use sources::{Defaults, PartialDefaults};
185
186// Re-export watch types
187#[cfg(feature = "watch")]
188pub use watch::{ConfigEvent, ConfigWatcher, WatchedConfig};
189
190// Re-export stillwater types that are commonly used
191pub use stillwater::{NonEmptyVec, Semigroup, Validation};
192
193// Re-export stillwater predicate module and types (0.13.0+)
194pub use stillwater::predicate::{self, Predicate, PredicateExt};
195
196// Re-export derive macro when the feature is enabled
197#[cfg(feature = "derive")]
198pub use premortem_derive::Validate as DeriveValidate;
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_reexports() {
206        // Ensure all re-exports are accessible
207        let _: ConfigValidation<()> = Validation::Success(());
208    }
209}