use skp_validator::{
Validate, ValidationContext, ValidationErrors, ValidationResult,
};
use skp_validator_rules::custom::{
contextual::ContextualRule,
dependency::{DependencyCondition, DependencyRule},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Validate)]
struct PasswordChange {
#[validate(length(min = 8))]
new_password: String,
#[validate(must_match(other = "new_password", message = "Passwords do not match"))]
confirm_password: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Order {
shipping_method: String,
address: Option<String>,
}
impl Validate for Order {
fn validate_with_context(&self, ctx: &ValidationContext) -> ValidationResult<()> {
let mut errors = ValidationErrors::new();
let local_ctx = ctx.clone().with_field("shipping_method", &self.shipping_method);
let rule = DependencyRule::<String>::new("address")
.depends_on("shipping_method", DependencyCondition::Equals("delivery".to_string()))
.then_required()
.message("Address is required for delivery");
let val_to_check = self.address.as_deref().unwrap_or("").to_string();
if let Err(mut e) = skp_validator_core::Rule::validate(&rule, &val_to_check, &local_ctx) {
let err = if !e.errors.is_empty() {
e.errors.pop()
} else if let Some(field_errors) = e.fields.get_mut("") {
if let skp_validator::FieldErrors::Simple(vec) = field_errors {
vec.first().cloned()
} else { None }
} else { None };
if let Some(mut err_obj) = err {
err_obj.message = "Address is required for delivery".to_string(); errors.add_field_error("address", err_obj);
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct FeatureFlag {
name: String,
enabled: bool,
}
impl Validate for FeatureFlag {
fn validate_with_context(&self, ctx: &ValidationContext) -> ValidationResult<()> {
let mut errors = ValidationErrors::new();
let rule = ContextualRule::<str>::new("env_check")
.when(|ctx| ctx.get_meta("env") == Some("production"))
.validate_with(|val, _| !val.starts_with("beta_"))
.message("Beta features are not allowed in production");
if let Err(mut e) = skp_validator_core::Rule::validate(&rule, self.name.as_str(), ctx) {
let err = if !e.errors.is_empty() {
e.errors.pop()
} else if let Some(field_errors) = e.fields.get_mut("") {
if let skp_validator::FieldErrors::Simple(vec) = field_errors {
vec.first().cloned()
} else { None }
} else { None };
if let Some(err_obj) = err {
errors.add_field_error("name", err_obj);
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
fn main() {
println!("--- Scenario 1: Simple Dependency (Derive) ---");
let invalid_pass = PasswordChange {
new_password: "secure_password".to_string(),
confirm_password: "wrong_password".to_string(),
};
match invalid_pass.validate() {
Ok(_) => println!("Password matched!"),
Err(e) => println!("Validation error: {}", e),
}
println!("\n--- Scenario 2: Advanced Dependency (Manual) ---");
let invalid_order = Order {
shipping_method: "delivery".to_string(),
address: None, };
match invalid_order.validate() {
Ok(_) => println!("Order valid!"),
Err(e) => println!("Order invalid: {}", e),
}
let valid_order = Order {
shipping_method: "delivery".to_string(),
address: Some("123 Main St".to_string()),
};
match valid_order.validate() {
Ok(_) => println!("Order valid!"),
Err(e) => println!("Order invalid: {}", e),
}
println!("\n--- Scenario 3: Context-Aware Validation ---");
let feature = FeatureFlag {
name: "beta_new_ui".to_string(),
enabled: true,
};
let dev_ctx = ValidationContext::new().with_meta("env", "dev");
match feature.validate_with_context(&dev_ctx) {
Ok(_) => println!("Dev: Feature allowed"),
Err(e) => println!("Dev: {}", e),
}
let prod_ctx = ValidationContext::new().with_meta("env", "production");
match feature.validate_with_context(&prod_ctx) {
Ok(_) => println!("Prod: Feature allowed"),
Err(e) => println!("Prod: Blocked - {}", e),
}
}