Composable validation rules for input validation.
This crate provides a serializable, composable validation rule system based on
the Rule<T> enum, along with core validation traits.
The Rule enum provides built-in validation for common constraints:
Rule::Required- Value must not be emptyRule::MinLength/Rule::MaxLength- Length constraintsRule::Min/Rule::Max- Range constraintsRule::Pattern- Regex pattern matchingRule::Email- Email format validationRule::Step- Step/multiple validationRule::Hostname- Configurable hostname validation (DNS/IP/local/public IPv4)Rule::Date- Date format validation (ISO 8601, US, EU, RFC 2822, custom)Rule::DateRange- Date range validation with min/max boundsRule::Custom- Custom closure-based validation
Rules can be composed using methods on Rule:
.and()- Both rules must pass (AND logic, producesRule::All).or()- At least one rule must pass (OR logic, producesRule::Any).not()- Negates a rule (producesRule::Not).when()/.when_else()- Conditional validation (producesRule::When)
Add to your Cargo.toml:
[dependencies]
walrs_validation = { path = "../validation" }use walrs_validation::{Rule, Validate, ValidateRef};
fn main() {
// Length validation
let length_rule = Rule::<String>::MinLength(3).and(Rule::MaxLength(20));
assert!(length_rule.validate_ref("hello").is_ok());
assert!(length_rule.validate_ref("hi").is_err());
// Range validation
let range_rule = Rule::<i32>::Min(0).and(Rule::Max(100));
assert!(range_rule.validate(50).is_ok());
assert!(range_rule.validate(-1).is_err());
}The crate provides two main validation traits:
// For Copy types (numbers, etc.)
pub trait Validate<T> {
fn validate(&self, value: T) -> ValidatorResult;
}
// For referenced types (str, slices, etc.)
pub trait ValidateRef<T: ?Sized> {
fn validate_ref(&self, value: &T) -> ValidatorResult;
}This crate also provides shared foundation types used across form-related crates:
Value- Native form value enum with distinct numeric variants (I64,U64,F64),Str,Bool,Array,Object(HashMap<String, Value>), andNullValueExt- Extension trait with form-specific helper methods (e.g.,is_empty_value())value!- Convenience macro for constructingValueliteralsAttributes- HTML attributes storage and rendering
The serde_json_bridge feature (enabled by default) provides From<serde_json::Value> for Value and vice-versa for interoperability.
The indexmap feature provides From<IndexMap<String, V>> for Value for constructing Value::Object from an IndexMap.
Date validation requires enabling one of the date crate features:
# Using chrono (most popular, widest ecosystem)
walrs_validation = { path = "../validation", features = ["chrono"] }
# Using jiff (modern API, best timezone handling)
walrs_validation = { path = "../validation", features = ["jiff"] }String-based validation — validate date strings with Rule::Date and Rule::DateRange:
use walrs_validation::{Rule, DateOptions, DateRangeOptions, DateFormat};
// Validate ISO 8601 date strings
let rule = Rule::<String>::Date(DateOptions::default());
assert!(rule.validate_str("2026-02-23").is_ok());
// Validate date range
let rule = Rule::<String>::DateRange(DateRangeOptions {
format: DateFormat::Iso8601,
allow_time: false,
min: Some("2020-01-01".into()),
max: Some("2030-12-31".into()),
});
assert!(rule.validate_str("2025-06-15").is_ok());Native type validation — validate chrono::NaiveDate / jiff::civil::Date directly:
// With chrono feature
use chrono::NaiveDate;
use walrs_validation::Rule;
let min = NaiveDate::from_ymd_opt(2020, 1, 1).unwrap();
let max = NaiveDate::from_ymd_opt(2030, 12, 31).unwrap();
let rule = Rule::<NaiveDate>::Range { min, max };
let date = NaiveDate::from_ymd_opt(2025, 6, 15).unwrap();
assert!(rule.validate_date(&date).is_ok());When both features are enabled, chrono takes precedence for string parsing.
MIT & Apache-2.0