koruma
A per-field validation library for Rust with struct-based errors.
Features
- Per-field validation with strongly-typed error structs
- Multiple validators per field
- Generic validator support with type inference
- Optional field support (skips validation when
None) - Nested struct validation with
#[koruma(nested)]
koruma-collection
provides a collection of common validators, with partial i18n support (en, fr).
Installation
[]
= { = "*", = ["derive"] }
Examples
Quick Start
Defining Validators
Use #[koruma::validator] to define validation rules. Each validator must have a field marked with #[koruma(value)] to capture the validated value:
use ;
Generic Validators
For validators that work with multiple types, use generics with a blanket impl:
// Use a blanket impl with trait bounds
Validating Structs
Apply validators to struct fields using #[derive(Koruma)] and the #[koruma(...)] attribute:
use Koruma;
// Use <_> to infer the type from the field
Accessing Validation Errors
The generated error struct provides typed access to each field's validation errors:
let user = User ;
match user.validate
Multiple Validators Per Field
Apply multiple validators to a single field by separating them with commas:
// Access individual validators
let err = item.validate.unwrap_err;
if let Some = err.value.number_range_validation
if let Some = err.value.even_number_validation
// Or get all failed validators at once
let all_errors = err.value.all; // Vec<ItemValueValidator>
Collection Validation
Use the each(...) syntax to validate each element in a Vec:
// Errors include the index of the failing element
let order = Order ;
let err = order.validate.unwrap_err;
// Returns &[(usize, OrderScoresError)]
for in err.scores
Optional Field Validation
Fields of type Option<T> are automatically handled:
None: Validation is skipped entirelySome(value): The inner value is validated
// None fields are skipped
let profile = UserProfile ;
assert!;
// Some fields are validated
let profile = UserProfile ;
let err = profile.validate.unwrap_err;
// Error captures the inner value
let bio_err = err.bio.string_length_validation.unwrap;
assert_eq!;
Nested Struct Validation
For fields that are themselves structs deriving Koruma, use #[koruma(nested)] to automatically validate them:
// Validation cascades through nested structs
let customer = Customer ;
match customer.validate
Nested validation also works with optional fields:
// None is skipped
let customer = CustomerWithOptionalAddress ;
assert!;
Nesting can be arbitrarily deep - nested structs can themselves contain nested structs:
// Access deeply nested errors
let err = employee.validate.unwrap_err;
if let Some = err.employer
Error Messages
Basic String Messages
For simple error messages, implement Display or a custom method on your validators:
// Usage
if let Some = errors.age.number_range_validation
Fluent Integration
For internationalized error messages, use es-fluent:
[]
= { = "0.1" }
= "0.4"
Derive EsFluent on your validators:
use EsFluent;
Create corresponding Fluent files:
# locales/en/main.ftl
number-range-validation = Value { $actual } must be between { $min } and { $max }
Use to_fluent_string() to get localized messages:
use ToFluentString;
if let Some = errors.age.number_range_validation
Fluent with all() Method
When using the all() method to get all failed validators, you can derive KorumaFluentEnum on the generated enum to implement ToFluentString:
use ToFluentString;
use KorumaFluentEnum;
// Derive KorumaFluentEnum on the generated validator enum
// This requires all inner validators to implement ToFluentString
// Now you can iterate over all errors
for validator in errors.value.all
Note: KorumaFluentEnum requires the fluent feature to be enabled and all variant types must implement ToFluentString.