rod_validation

Crates.iorod_validation
lib.rsrod_validation
version0.2.2
created_at2025-09-20 07:41:38.97826+00
updated_at2025-09-30 10:10:15.503723+00
descriptionA lightweight and ergonomic validation library for Rust
homepage
repositoryhttps://github.com/kommade/rod
max_upload_size
id1847497
size82,936
(kommade)

documentation

README

Rod Validation

Crates.io Documentation License: GPL v3

A powerful and flexible compile-time validation library for Rust structs and enums. Rod provides declarative validation through derive macros, allowing you to specify validation rules directly in your type definitions.

Features

  • Compile-time validation: Validation rules are checked at compile time for type safety
  • Declarative syntax: Define validation rules using intuitive attribute macros
  • Comprehensive type support: Validate strings, integers, floats, options, tuples, and custom types
  • Flexible error handling: Choose between fail-fast or collect-all error reporting
  • Custom validation: Add custom validation logic with closures
  • Regex support: Built-in regex validation for strings (with optional regex feature)
  • Nested validation: Support for complex nested data structures
  • Per-validation custom errors: Attach tailored messages to individual validation rules for precise feedback

Quick Start

Add Rod Validation to your Cargo.toml:

[dependencies]
rod_validation = "0.2.2"

For regex support:

[dependencies]
rod_validation = { version = "0.2.2", features = ["regex"] }

Basic Usage

use rod_validation::prelude::*;

#[derive(RodValidate)]
struct User {
    #[rod(String {
        length: 3..=50,
        format: Email,
    })]
    email: String,
    
    #[rod(String {
        length: 8..=100,
        includes: "@",
    })]
    password: String,
    
    #[rod(i32 {
        size: 18..=120,
        sign: Positive,
    })]
    age: i32,
    
    #[rod(Option {
        String {
            length: 1..=100,
        }
    })]
    bio: Option<String>,
}

fn main() {
    let user = User {
        email: "user@example.com".to_string(),
        password: "secure@password123".to_string(),
        age: 25,
        bio: Some("Software developer".to_string()),
    };
    
    // Validate and get first error (fail-fast)
    match user.validate() {
        Ok(_) => println!("User is valid!"),
        Err(e) => println!("Validation error: {}", e),
    }
    
    // Validate and collect all errors
    match user.validate_all() {
        Ok(_) => println!("User is valid!"),
        Err(errors) => {
            println!("Found {} validation errors:", errors.len());
            for error in errors {
                println!("  - {}", error);
            }
        }
    }
}

Validation Types

String Validation

#[derive(RodValidate)]
struct StringExample {
    #[rod(String {
        length: 5..=20,           // Length between 5 and 20 characters
        format: Email,            // Built-in email format (requires regex feature)
        starts_with: "user_",     // Must start with "user_"
        ends_with: "@domain.com", // Must end with "@domain.com"
        includes: "test",         // Must contain "test"
    })]
    field: String,
}

Available string formats (with regex feature):

  • Email - Email address validation
  • Url - URL validation
  • Uuid - UUID validation
  • Ipv4 - IPv4 address validation
  • Ipv6 - IPv6 address validation
  • DateTime - DateTime validation
  • Regex("pattern") - Custom regex pattern

Integer Validation

#[derive(RodValidate)]
struct IntegerExample {
    #[rod(i32 {
        size: 1..=100,    // Value between 1 and 100
        sign: Positive,   // Must be positive
        step: 5,          // Must be multiple of 5
    })]
    field: i32,
}

Number signs:

  • Positive - Greater than 0
  • Negative - Less than 0
  • NonPositive - Less than or equal to 0
  • NonNegative - Greater than or equal to 0

Float Validation

#[derive(RodValidate)]
struct FloatExample {
    #[rod(f64 {
        size: 0.0..=100.0,  // Value between 0.0 and 100.0
        sign: NonNegative,  // Must be non-negative
        type: Finite,       // Must be finite (not NaN or infinite)
    })]
    field: f64,
}

Float types:

  • Finite - Not NaN or infinite
  • Infinite - Must be infinite
  • Nan - Must be NaN
  • Normal - Must be normal
  • Subnormal - Must be subnormal

Option Validation

#[derive(RodValidate)]
struct OptionExample {
    // Validate the inner value if Some
    #[rod(Option {
        String {
            length: 5,
        }
    })]
    optional_field: Option<String>,
    
    // Require the field to be None
    #[rod(Option {})]
    must_be_none: Option<String>,
}

Tuple Validation

#[derive(RodValidate)]
struct TupleExample {
    #[rod(Tuple (
        i32 {
            size: 1..=10,
            sign: Positive,
        },
        String {
            length: 5,
        },
        f64 {
            size: 0.0..=1.0,
        }
    ))]
    coordinates: (i32, String, f64),
}

Literal Validation

#[derive(RodValidate)]
struct LiteralExample {
    #[rod(Literal {
        value: "expected_value",
    })]
    field: String,
}

Custom Validation

#[derive(RodValidate)]
struct CustomExample {
    #[rod(
        i32 {
            size: 1..=100,
        },
        check = |x| x % 2 == 0  // Custom validation: must be even
    )]
    even_number: i32,
}

Iterable Validation

#[derive(RodValidate)]
struct IterableExample {
    #[rod(Iterable {
        length: 1..=10,
        String {
            length: 3..=20,
        }
    })]
    tags: Vec<String>,
}

Error Handling

Rod provides two validation methods:

  • validate() - Returns the first validation error encountered (fail-fast)
  • validate_all() - Collects and returns all validation errors
// Fail-fast validation
match user.validate() {
    Ok(_) => println!("Valid!"),
    Err(error) => println!("Error: {}", error),
}

// Collect all errors
match user.validate_all() {
    Ok(_) => println!("Valid!"),
    Err(errors) => {
        for error in errors.iter() {
            println!("Error: {}", error);
        }
    }
}

Per-Validation Custom Errors

Attach bespoke error messages to any validation rule using the ? "<error>" syntax. Messages placed immediately before a rule override its default error output.

#[derive(RodValidate)]
struct SignupForm {
    #[rod(String {
        ? "Password must be at least eight characters long.",
        length: 8..,
        ? "Password must include the @ symbol for legacy compatibility.",
        includes: "@",
    })]
    password: String,
}

The custom messages are surfaced by both validate and validate_all, making it straightforward to deliver user-friendly, context-aware feedback.

You may also override the error messages for all validation rules of a type with the message: "<error>" syntax.

#[derive(RodValidate)]
struct SignupForm {
    #[rod(
        String {
            length: 8..,
            includes: "@",
        },
        message: "Password must be 8 characters AND have an @"
    )]
    password: String,
}

If both error message syntaxes are attached, messages attached to specific rules will be preferred.

## Nested Structures

Rod supports validation of nested structures that implement `RodValidate`:

```rust
#[derive(RodValidate)]
struct Address {
    #[rod(String { length: 1..=100 })]
    street: String,
    
    #[rod(String { length: 2..=50 })]
    city: String,
}

#[derive(RodValidate)]
struct Person {
    #[rod(String { length: 1..=50 })]
    name: String,
    
    // No #[rod] attribute needed for custom types
    address: Address,
}

Enums

Rod supports validation of enumeration variants:

#[derive(RodValidate)]
enum Status {
    #[rod(String { length: 1..=100 })]
    Active(String),
    
    Inactive,
    
    #[rod(i32 { size: 1..=30 })]
    Pending { days: i32 },
}

The RodValidate Derive Macro

The #[derive(RodValidate)] macro generates two validation methods for your types:

  • validate(&self) -> Result<(), RodValidateError> - Fail-fast validation (returns on first error)
  • validate_all(&self) -> Result<(), RodValidateErrorList> - Collect all errors before returning

Error Messages

The generated validation code produces detailed error messages with field paths:

// Example error: "Expected `user.email` to be a string with length 3..=50, got 2"

Compilation Guarantees

The macro provides several compile-time guarantees:

  1. Type Safety: Validation attributes must match the field type
  2. Nested Types: Custom types must implement RodValidate
  3. Attribute Validation: Invalid attribute combinations are caught at compile time

Technical Details

Code Generation Strategy

The macro generates validation code that:

  1. Traverses struct or enumeration fields recursively
  2. Applies validation rules in the order they appear in attributes
  3. Handles early return (fail-fast) or error collection (validate-all) modes
  4. Provides detailed error context with complete field paths

Performance Considerations

  • All validation logic is generated at compile time
  • No runtime reflection or dynamic dispatch overhead
  • Validation performance is equivalent to hand-written code
  • Zero-cost abstractions for unused validation paths

Limitations

  1. Union types: Not supported (will generate todo!() panic)
  2. Double references: &&T types are not allowed
  3. Custom validation closures: Must return bool type
  4. Regex features: Require the regex crate feature to be enabled

Rust Features

  • Default features: ["regex"]
  • regex: Enables regex-based string format validation

Documentation

For detailed documentation and examples, visit docs.rs/rod.

License

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a pull request.

Commit count: 23

cargo fmt