Scrutiny
A powerful, Laravel-inspired validation library for Rust. Uses derive macros and field-level attributes to declaratively validate structs — no runtime string parsing.
Correct by default
Format rules delegate to dedicated, standards-compliant crates rather than hand-rolled regexes:
| Rule | Standard | Crate | Feature |
|---|---|---|---|
email |
RFC 5321 | email_address |
email |
url |
WHATWG URL | url |
url-parse |
uuid |
RFC 4122 | uuid |
uuid-parse |
ulid |
ULID spec | ulid |
ulid-parse |
date / datetime |
ISO 8601 | chrono |
chrono |
timezone |
IANA tz database | chrono-tz |
timezone |
ip / ipv4 / ipv6 |
RFC 791 / 2460 | std::net |
— |
All enabled by default. Disable default features for a minimal build and opt in to what you need.
Quick Start
use Validate;
use Validate as _;
let user = CreateUser ;
assert!;
Custom Error Messages
Every rule has a sensible default message. Override per-rule with message:
use Validate;
use Validate as _;
let p = Profile ;
let err = p.validate.unwrap_err;
assert_eq!;
assert_eq!;
Nested & Vec Validation
Use nested to recursively validate nested structs and Vec elements.
Errors use dot-notation paths: address.city, members.0.email.
use Validate;
use Validate as _;
let order = Order ;
let err = order.validate.unwrap_err;
assert!;
Tuple Structs
Newtypes get validation for free — encode your invariants in the type system:
use Validate;
use Validate as _;
] String);
] i32);
assert!;
assert!;
Enums
Validate fields per variant. Unit variants always pass.
use Validate;
use Validate as _;
let c = Email ;
assert!;
let c = None;
assert!;
Restricting Enum Variants
Use in_list/not_in with strum's AsRefStr
to restrict which variants are accepted:
Works on any type implementing AsRef<str>.
Conditional Validation
use Validate;
use Validate as _;
// admin_code only required when role is "admin"
let reg = Registration ;
assert!;
Available Rules
Presence & Meta
required, filled, nullable, sometimes, bail, prohibited,
prohibited_if, prohibited_unless
Type & Format
string, integer, numeric, boolean, email, url, uuid, ulid,
ip, ipv4, ipv6, mac_address, json, ascii, hex_color, timezone
String
alpha, alpha_num, alpha_dash, uppercase, lowercase,
starts_with, ends_with, doesnt_start_with, doesnt_end_with,
contains, doesnt_contain, regex, not_regex
Size, Length & Range
min, max, between, size — type-aware: compares numeric values
for number fields, string length for String, and item count for Vec.
digits, digits_between, decimal, multiple_of
Comparison
same, different, confirmed, gt, gte, lt, lte,
in_list, not_in, in_array, distinct
Conditional
required_if, required_unless, required_with, required_without,
required_with_all, required_without_all, accepted, accepted_if,
declined, declined_if
Date (ISO 8601 strict)
date, datetime, date_equals, before, after,
before_or_equal, after_or_equal
Structural
nested (alias: dive), custom
Typed Fields & Deserialization Errors
Use actual types like uuid::Uuid or chrono::NaiveDate instead of validating
strings manually. Deserialization errors become field-level validation errors
automatically.
Axum users — Valid<T> handles this out of the box. Just use typed fields.
Everyone else — use [deserialize::from_json] to get unified errors:
use from_json;
// id: uuid::Uuid — if "not-a-uuid" is sent, you get:
// {"id": ["invalid type: expected UUID"]}
match
Error Serialization
With the serde feature (default), ValidationErrors serializes to: