| Crates.io | validatrix |
| lib.rs | validatrix |
| version | 0.3.0 |
| created_at | 2025-09-16 05:27:58.832288+00 |
| updated_at | 2025-09-17 18:15:47.953371+00 |
| description | Composable validation library |
| homepage | |
| repository | https://github.com/clbarnes/validatrix |
| max_upload_size | |
| id | 1840987 |
| size | 44,570 |
A lightweight validator library for rust.
Validatrix contains no built-in validators, just traits and error types for your own custom validation.
Designed for cases where:
The Display implementation of validatrix::Error can list multiple validation errors,
pointing to the location of the errors with JSONPath-like syntax,
although implementors can choose to fail fast instead.
use validatrix::{Validate, Accumulator, Valid};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct A {
/// Must not be divisible by 3.
avalue: u8,
/// Must be valid.
b: B,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct B {
/// Must not be divisible by 5.
bvalue: u8,
/// All must be valid.
cs: Vec<C>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct C {
/// Must not be divisible by 3 and 5.
cvalue: u8,
}
// Implement `validatrix::Validate` on your structs.
impl Validate for A {
// `Accumulator` allows you to continue looking for validation errors after the first.
// But you can return early if you prefer.
fn validate_inner(&self, accum: &mut Accumulator) {
if self.avalue % 3 == 0 {
// Each failure is added with a context: the name of the field
// (or index of a sequence) which failed.
accum.add_failure_at("avalue", "fizz");
}
// Fields implementing validatrix::Validate can have validation errors accumulated too.
accum.validate_member_at("b", &self.b);
}
}
impl Validate for B {
fn validate_inner(&self, accum: &mut Accumulator) {
if self.bvalue % 5 == 0 {
// You can also manually do validation within a prefix context
accum.with_key("bvalue", |a| a.add_failure("buzz"));
}
// Helper method for validating a sequence of validatrix::Validate structs
accum.validate_iter_at("cs", &self.cs);
}
}
impl Validate for C {
fn validate_inner(&self, accum: &mut Accumulator) {
if (self.cvalue % 3 * self.cvalue % 5) == 0 {
accum.add_failure_at("cvalue", "fizzbuzz")
}
}
}
// valid
let valid = A {
avalue: 1,
b: B {
bvalue: 1,
cs: vec![C { cvalue: 1 }, C { cvalue: 1 }],
},
};
valid.validate().unwrap();
// all of the value fields are fizz/buzz, and therefore invalid
let invalid = A {
avalue: 3,
b: B {
bvalue: 5,
cs: vec![C { cvalue: 15 }, C { cvalue: 30 }],
},
};
let err = invalid.validate().unwrap_err();
let validation_report = format!("{err}");
assert_eq!(validation_report, "
Validation failure(s):
$.avalue: fizz
$.b.bvalue: buzz
$.b.cs[0].cvalue: fizzbuzz
$.b.cs[1].cvalue: fizzbuzz
".trim());
// the `Valid` wrapper type enforces validity
let valid_wrapped = Valid::try_new(valid.clone()).expect("is valid");
assert!(Valid::try_new(invalid.clone()).is_err());
// `Valid` implements AsRef, Borrow, and Deref for the contained type
#[cfg(feature = "serde")]
{
// You can also deserialize directly into a Valid;
// validation errors are raised by serde.
let valid_wrapped_deser: Valid<A> = serde_json::from_str(
&serde_json::to_string(&valid).unwrap()
).unwrap();
// serialization is handled transparently
let invalid_str = serde_json::to_string(&invalid).unwrap();
assert!(serde_json::from_str::<Valid<A>>(&invalid_str).is_err());
}
There is also an asynchronous variant in the validatrix::asynch module.
See also validatrix(::asynch)::ValidateContext,
which allows passing a reference to some external data as context for the validation.
Other validation crates have focused on providing validator functions and proc macros to decorate types. I found those validators are often trivial implement yourself, the DSLs for decorating fields just look worse than regular rust code, and composing custom and built-in validators behaved in unclear ways.
JSONSchema-like validators tend not to be good at schema-level validation.
Make releases using cargo-release.
Use prek to manage pre-commit hooks.
Cow<str> (or alternative like hipstr, ecow etc.) for messagesAccumulator could have a fail-fast mode
Results (Err if fail-fast is true, otherwise Ok) so they can be ?'d and propagate&mut self methods which would then need to cede their failures to the returned errorsString in Failure with generic error