3 releases
Uses new Rust 2024
| 0.2.2 | Sep 30, 2025 |
|---|---|
| 0.2.1 | Sep 20, 2025 |
| 0.2.0 | Sep 20, 2025 |
#63 in Parser tooling
119 downloads per month
53KB
1K
SLoC
Rod Validation
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
regexfeature) - 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 validationUrl- URL validationUuid- UUID validationIpv4- IPv4 address validationIpv6- IPv6 address validationDateTime- DateTime validationRegex("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 0Negative- Less than 0NonPositive- Less than or equal to 0NonNegative- 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 infiniteInfinite- Must be infiniteNan- Must be NaNNormal- Must be normalSubnormal- 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:
- Type Safety: Validation attributes must match the field type
- Nested Types: Custom types must implement
RodValidate - Attribute Validation: Invalid attribute combinations are caught at compile time
Technical Details
Code Generation Strategy
The macro generates validation code that:
- Traverses struct or enumeration fields recursively
- Applies validation rules in the order they appear in attributes
- Handles early return (fail-fast) or error collection (validate-all) modes
- 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
- Union types: Not supported (will generate
todo!()panic) - Double references:
&&Ttypes are not allowed - Custom validation closures: Must return
booltype - Regex features: Require the
regexcrate 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.
Dependencies
~2.1–3.5MB
~65K SLoC