premortem_derive/
lib.rs

1//! Derive macros for the premortem configuration validation library.
2//!
3//! This crate provides the `#[derive(Validate)]` macro that generates
4//! `Validate` trait implementations using stillwater's `Validation::all()`
5//! pattern for error accumulation.
6//!
7//! # Basic Usage
8//!
9//! ```ignore
10//! use premortem::Validate;
11//!
12//! #[derive(Validate)]
13//! struct ServerConfig {
14//!     #[validate(non_empty)]
15//!     host: String,
16//!
17//!     #[validate(range(1..=65535))]
18//!     port: u16,
19//! }
20//! ```
21//!
22//! # Available Validators
23//!
24//! ## String Validators
25//! - `non_empty` - Value cannot be empty
26//! - `min_length(n)` - Minimum string length
27//! - `max_length(n)` - Maximum string length
28//! - `length(n..=m)` - String length in range
29//! - `pattern("regex")` - Match regex pattern
30//! - `email` - Valid email format
31//! - `url` - Valid URL format
32//! - `ip` - Valid IP address
33//! - `uuid` - Valid UUID format
34//!
35//! ## Numeric Validators
36//! - `range(n..=m)` - Value in range
37//! - `positive` - Value > 0
38//! - `negative` - Value < 0
39//! - `non_zero` - Value != 0
40//!
41//! ## Path Validators
42//! - `file_exists` - File exists at path
43//! - `dir_exists` - Directory exists at path
44//! - `parent_exists` - Parent directory exists
45//! - `extension("ext")` - File has extension
46//!
47//! ## Collection Validators
48//! - `each(validator)` - Apply validator to each element
49//!
50//! ## Structural Validators
51//! - `nested` - Validate nested struct
52//! - `skip` - Skip validation for this field
53//! - `custom = "fn_name"` - Call custom validation function
54//!
55//! ## Control Flow
56//! - `when = "condition"` - Conditional validation
57//! - `message = "custom message"` - Override error message
58//!
59//! # Sensitive Fields
60//!
61//! Use `#[sensitive]` to redact field values in error messages:
62//!
63//! ```ignore
64//! #[derive(Validate)]
65//! struct Credentials {
66//!     #[sensitive]
67//!     #[validate(min_length(8))]
68//!     password: String,
69//! }
70//! ```
71//!
72//! # Struct-Level Validation
73//!
74//! Use `#[validate(custom = "fn_name")]` on the struct for cross-field validation:
75//!
76//! ```ignore
77//! #[derive(Validate)]
78//! #[validate(custom = "validate_config")]
79//! struct Config {
80//!     start_port: u16,
81//!     end_port: u16,
82//! }
83//!
84//! fn validate_config(cfg: &Config) -> ConfigValidation<()> {
85//!     if cfg.start_port < cfg.end_port {
86//!         Validation::Success(())
87//!     } else {
88//!         Validation::fail_with(ConfigError::CrossFieldError {
89//!             paths: vec!["start_port".into(), "end_port".into()],
90//!             message: "start_port must be less than end_port".into(),
91//!         })
92//!     }
93//! }
94//! ```
95
96extern crate proc_macro;
97
98mod codegen;
99mod parse;
100mod validate;
101mod validators;
102
103use proc_macro::TokenStream;
104use syn::{parse_macro_input, DeriveInput};
105
106/// Derive the `Validate` trait for a struct.
107///
108/// This macro generates an implementation of `premortem::Validate` that
109/// validates all fields according to their `#[validate(...)]` attributes.
110///
111/// The generated code uses stillwater's `Validation::all()` to run all
112/// validations in parallel and accumulate ALL errors, not just the first one.
113///
114/// # Example
115///
116/// ```ignore
117/// use premortem::Validate;
118///
119/// #[derive(Validate)]
120/// struct DatabaseConfig {
121///     #[validate(non_empty, message = "Host is required")]
122///     host: String,
123///
124///     #[validate(range(1..=65535))]
125///     port: u16,
126///
127///     #[validate(positive)]
128///     pool_size: u32,
129///
130///     #[validate(nested)]
131///     tls: Option<TlsConfig>,
132/// }
133/// ```
134#[proc_macro_derive(Validate, attributes(validate, sensitive))]
135pub fn derive_validate(input: TokenStream) -> TokenStream {
136    let input = parse_macro_input!(input as DeriveInput);
137
138    match validate::derive_validate(input) {
139        Ok(tokens) => tokens.into(),
140        Err(err) => err.to_compile_error().into(),
141    }
142}