Expand description
§validatrix
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:
- possibly-valid types are built up of other possibly-valid types
- there is additional schema-level validation
- data is modelled as JSON-like, where sequences are ordered and maps’ keys are stringy
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.
§Usage
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.
§Why not
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.
§Development
Make releases using cargo-release.
Use prek to manage pre-commit hooks.
§MSRV
validatrix should run on the rust version specified in Cargo.toml.
However, a more recent version may be required by certain development dependencies.
§To do
- use
Cow<str>(or alternative like hipstr, ecow etc.) forFailure::message; alternatively, useBox<dyn Error>(but then people have to write their own validation errors, althoughStrings would still work) Accumulatorcould have a fail-fast mode- could cap the number of errors at a given value, which might be 1
- methods would return
Results (Errif fail-fast istrue, otherwiseOk) so they can be?’d and propagate - this would cause weirdness in the
&mut selfmethods which would then need to cede their failures to the returned errors
Key::Fieldcould use aCowso that custom field names can be used e.g. for maps (or a third variant likeKey::MapKey(String))
Re-exports§
pub use synch::Validate;pub use synch::ValidateContext;
Modules§
Structs§
- Accumulator
- Type used to build up a list of validation failures.
- Error
- Validation error type wrapping a list of Failures.
- Failure
- Struct representing a single validation failure. Used to build informative error messages for Error.
- Valid
- Wrapper type containing a value which must have been validated.