use domainstack::Validate;
use domainstack_derive::ToSchema;
use domainstack_schema::OpenApiBuilder;
#[derive(Debug, Validate, ToSchema)]
#[schema(description = "User registration request")]
#[allow(dead_code)]
struct UserRegistration {
#[validate(email)]
#[validate(max_len = 255)]
#[schema(description = "Valid email address", example = "user@example.com")]
email: String,
#[validate(range(min = 18, max = 120))]
#[schema(description = "User age (must be 18+)")]
age: u8,
#[validate(alphanumeric)]
#[validate(min_len = 3)]
#[validate(max_len = 20)]
#[schema(description = "Unique username", example = "alice123")]
username: String,
#[schema(description = "Optional display name")]
display_name: Option<String>,
}
#[derive(Debug, Validate, ToSchema)]
#[schema(description = "Physical address")]
struct Address {
#[validate(min_len = 1)]
#[validate(max_len = 200)]
#[schema(description = "Street address", example = "123 Main St")]
street: String,
#[validate(min_len = 1)]
#[validate(max_len = 100)]
#[schema(description = "City", example = "San Francisco")]
city: String,
#[validate(alphanumeric)]
#[validate(min_len = 5)]
#[validate(max_len = 10)]
#[schema(description = "Postal code", example = "94102")]
postal_code: String,
}
#[derive(Debug, Validate, ToSchema)]
#[schema(description = "Complete user profile")]
#[allow(dead_code)]
struct UserProfile {
#[validate(email)]
#[validate(max_len = 255)]
email: String,
#[validate(nested)]
#[schema(description = "Primary address")]
address: Address,
#[schema(description = "Billing address (optional)")]
billing_address: Option<Address>,
}
#[derive(Debug, Validate, ToSchema)]
#[schema(description = "Content tag")]
struct Tag {
#[validate(alphanumeric)]
#[validate(min_len = 1)]
#[validate(max_len = 50)]
#[schema(description = "Tag name", example = "rust")]
name: String,
}
#[derive(Debug, Validate, ToSchema)]
#[schema(description = "Blog post with tags")]
#[allow(dead_code)]
struct BlogPost {
#[validate(min_len = 1)]
#[validate(max_len = 200)]
#[schema(description = "Post title", example = "Getting Started with Rust")]
title: String,
#[validate(min_len = 1)]
#[validate(max_len = 10000)]
#[schema(description = "Post content")]
content: String,
#[validate(each_nested)]
#[schema(description = "Content tags")]
tags: Vec<Tag>,
}
fn main() {
println!("=== Unified Validation & Schema Generation ===\n");
println!("1. Testing Runtime Validation:\n");
let valid_user = UserRegistration {
email: "alice@example.com".to_string(),
age: 25,
username: "alice123".to_string(),
display_name: Some("Alice Smith".to_string()),
};
match valid_user.validate() {
Ok(_) => println!(" [ok] Valid user passed validation"),
Err(e) => println!(" [error] Validation failed: {:?}", e),
}
let invalid_user = UserRegistration {
email: "not-an-email".to_string(), age: 15, username: "ab".to_string(), display_name: None,
};
match invalid_user.validate() {
Ok(_) => println!(" [error] Invalid user should have failed!"),
Err(e) => {
println!(
" [ok] Invalid user rejected with {} errors:",
e.violations.len()
);
for v in &e.violations {
println!(" - {}: {}", v.path, v.message);
}
}
}
println!("\n2. Generating OpenAPI Schemas:\n");
let spec = OpenApiBuilder::new("User Management API", "1.0.0")
.description("API with unified validation and schema generation")
.register::<UserRegistration>()
.register::<Address>()
.register::<UserProfile>()
.register::<Tag>()
.register::<BlogPost>()
.build();
match spec.to_json() {
Ok(json) => {
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json) {
if let Some(schemas) = parsed["components"]["schemas"].as_object() {
if let Some(user_schema) = schemas.get("UserRegistration") {
println!(" Generated schema for UserRegistration:");
println!("{}", serde_json::to_string_pretty(user_schema).unwrap());
}
}
}
}
Err(e) => println!(" [error] Schema generation failed: {}", e),
}
println!("\n3. Validation Rule → OpenAPI Schema Mapping:\n");
println!(" #[validate(email)]");
println!(" → format: 'email'\n");
println!(" #[validate(max_len = 255)]");
println!(" → maxLength: 255\n");
println!(" #[validate(range(min = 18, max = 120))]");
println!(" → minimum: 18, maximum: 120\n");
println!(" #[validate(alphanumeric)]");
println!(" → pattern: '^[a-zA-Z0-9]*$'\n");
println!(" #[validate(min_len = 3)], #[validate(max_len = 20)]");
println!(" → minLength: 3, maxLength: 20\n");
println!(" display_name: Option<String>");
println!(" → excluded from required array\n");
println!(" #[validate(nested)]");
println!(" → $ref: '#/components/schemas/Address'\n");
println!(" #[validate(min_items = 1)], #[validate(max_items = 10)]");
println!(" → minItems: 1, maxItems: 10\n");
println!("\n4. Benefits of Unified Approach:\n");
println!(" [ok] Write validation rules ONCE");
println!(" [ok] Get runtime validation automatically");
println!(" [ok] Get OpenAPI schemas automatically");
println!(" [ok] No duplication between code and docs");
println!(" [ok] Single source of truth");
println!(" [ok] Changes propagate automatically");
println!(" [ok] Same rich syntax for both macros");
println!("\n==============================================");
println!(
"Example complete! Both validation and schema generation work from the same attributes."
);
}