use premortem::prelude::*;
use premortem::validate::{custom, validate_field};
use serde::Deserialize;
use stillwater::Validation;
#[derive(Debug, Deserialize, DeriveValidate)]
struct StringConfig {
#[validate(non_empty)]
name: String,
#[validate(min_length(8), max_length(128))]
api_key: String,
#[validate(pattern(r"^[a-z][a-z0-9_]*$"))]
username: String,
}
#[derive(Debug, Deserialize, DeriveValidate)]
struct NumericConfig {
#[validate(range(1..=65535))]
port: u16,
#[validate(positive)]
max_connections: i32,
#[validate(non_zero)]
retry_count: u32,
#[validate(range(1..=3600))]
timeout_secs: u64,
}
#[derive(Debug, Deserialize, DeriveValidate)]
struct CollectionConfig {
#[validate(each(non_empty))]
allowed_hosts: Vec<String>,
#[validate(each(non_empty))]
tags: Vec<String>,
}
#[derive(Debug, Deserialize, DeriveValidate)]
struct ServerConfig {
#[validate(non_empty)]
host: String,
#[validate(range(1..=65535))]
port: u16,
}
#[derive(Debug, Deserialize, DeriveValidate)]
struct DatabaseConfig {
#[validate(non_empty)]
url: String,
#[validate(range(1..=100))]
pool_size: u32,
}
#[derive(Debug, Deserialize, DeriveValidate)]
struct AppConfig {
#[validate(nested)]
server: ServerConfig,
#[validate(nested)]
database: DatabaseConfig,
}
#[derive(Debug, Deserialize)]
struct RangeConfig {
min_value: i32,
max_value: i32,
}
impl Validate for RangeConfig {
fn validate(&self) -> ConfigValidation<()> {
if self.min_value >= self.max_value {
Validation::Failure(ConfigErrors::single(ConfigError::CrossFieldError {
paths: vec!["min_value".to_string(), "max_value".to_string()],
message: "min_value must be less than max_value".to_string(),
}))
} else {
Validation::Success(())
}
}
}
#[derive(Debug, Deserialize)]
struct CustomConfig {
buffer_size: i32,
}
impl Validate for CustomConfig {
fn validate(&self) -> ConfigValidation<()> {
let even_validator = custom(|value: &i32, path: &str| {
if value % 2 == 0 {
Validation::Success(())
} else {
Validation::Failure(ConfigErrors::single(ConfigError::ValidationError {
path: path.to_string(),
source_location: None,
value: Some(value.to_string()),
message: "value must be even".to_string(),
}))
}
});
validate_field(&self.buffer_size, "buffer_size", &[&even_validator])
}
}
fn main() {
println!("=== Validation Examples ===\n");
demo_valid_config();
demo_invalid_config();
demo_cross_field_validation();
demo_custom_validator();
}
fn demo_valid_config() {
println!("--- Valid Configuration ---");
let valid_toml = r#"
[server]
host = "localhost"
port = 8080
[database]
url = "postgres://localhost/mydb"
pool_size = 10
"#;
let result = Config::<AppConfig>::builder()
.source(Toml::string(valid_toml))
.build();
match result {
Ok(config) => {
println!("Config loaded successfully!");
println!(" Server: {}:{}", config.server.host, config.server.port);
println!(
" Database: {} (pool: {})",
config.database.url, config.database.pool_size
);
}
Err(errors) => {
println!("Unexpected errors: {:?}", errors);
}
}
println!();
}
fn demo_invalid_config() {
println!("--- Invalid Configuration (Multiple Errors) ---");
let invalid_toml = r#"
[server]
host = ""
port = 0
[database]
url = ""
pool_size = 200
"#;
let result = Config::<AppConfig>::builder()
.source(Toml::string(invalid_toml))
.build();
match result {
Ok(_) => println!("Unexpected success"),
Err(errors) => {
println!("Found {} validation errors:", errors.len());
for (i, error) in errors.iter().enumerate() {
println!(" {}. {}", i + 1, error);
}
}
}
println!();
}
fn demo_cross_field_validation() {
println!("--- Cross-Field Validation ---");
let invalid_range = r#"
min_value = 100
max_value = 50
"#;
let result = Config::<RangeConfig>::builder()
.source(Toml::string(invalid_range))
.build();
match result {
Ok(_) => println!("Unexpected success"),
Err(errors) => {
println!("Cross-field validation caught error:");
for error in errors.iter() {
println!(" {}", error);
}
}
}
println!();
}
fn demo_custom_validator() {
println!("--- Custom Validator ---");
let odd_config = r#"buffer_size = 7"#;
let result = Config::<CustomConfig>::builder()
.source(Toml::string(odd_config))
.build();
match result {
Ok(_) => println!("Unexpected success"),
Err(errors) => {
println!("Custom validator caught error:");
for error in errors.iter() {
println!(" {}", error);
}
}
}
let even_config = r#"buffer_size = 8"#;
let result = Config::<CustomConfig>::builder()
.source(Toml::string(even_config))
.build();
match result {
Ok(config) => println!("Even value accepted: buffer_size = {}", config.buffer_size),
Err(errors) => println!("Unexpected errors: {:?}", errors),
}
println!();
}