garde
garde copied to clipboard
A powerful validation library for Rust
Garde

A Rust validation library
- Basic usage example
- Validation rules
- Length modes
- Inner type validation
- Newtypes
- Handling Option
- Custom validation
- Context/Self access
- Implementing rules
- Implementing
Validate - Rule adapters
- Integration with web frameworks
- Feature flags
- Why
garde?
Basic usage example
To get started, install garde:
cargo add garde -F full
And attach the Validate derive to your type. garde will generate an implementation of the Validate trait for you,
allowing you to call the validate method.
Here's what that looks like in full:
use garde::{Validate, Valid};
#[derive(Validate)]
struct User<'a> {
#[garde(ascii, length(min=3, max=25))]
username: &'a str,
#[garde(length(min=15))]
password: &'a str,
}
let user = User {
username: "test",
password: "not_a_very_good_password",
};
if let Err(e) = user.validate() {
println!("invalid user: {e}");
}
Garde can also validate enums:
use garde::{Validate, Valid};
#[derive(Validate)]
enum Data {
Struct {
#[garde(range(min=-10, max=10))]
field: i32,
},
Tuple(
#[garde(ascii)]
String
),
}
let data = Data::Struct { field: 100 };
if let Err(e) = data.validate() {
println!("invalid data: {e}");
}
Available validation rules
| name | format | validation | feature flag |
|---|---|---|---|
| required | #[garde(required)] |
is value set | - |
| ascii | #[garde(ascii)] |
only contains ASCII | - |
| alphanumeric | #[garde(alphanumeric)] |
only letters and digits | - |
#[garde(email)] |
an email according to the HTML5 spec[^1] | email |
|
| url | #[garde(url)] |
a URL | url |
| ip | #[garde(ip)] |
an IP address (either IPv4 or IPv6) | - |
| ipv4 | #[garde(ipv4)] |
an IPv4 address | - |
| ipv6 | #[garde(ipv6)] |
an IPv6 address | - |
| credit card | #[garde(credit_card)] |
a credit card number | credit-card |
| phone number | #[garde(phone_number)] |
a phone number | phone-number |
| length | #[garde(length(<mode>, min=<usize>, max=<usize>)] |
a container with length in min..=max |
- |
| matches | #[garde(matches(<field>))] |
a field matches another field | - |
| range | #[garde(range(min=<expr>, max=<expr>))] |
a number in the range min..=max |
- |
| contains | #[garde(contains(<string>))] |
a string-like value containing a substring | - |
| prefix | #[garde(prefix(<string>))] |
a string-like value prefixed by some string | - |
| suffix | #[garde(suffix(<string>))] |
a string-like value suffixed by some string | - |
| pattern | #[garde(pattern("<regex>"))] |
a string-like value matching some regular expression | regex |
| pattern | #[garde(pattern(<matcher>))] |
a string-like value matched by some Matcher | - |
| dive | #[garde(dive)] |
nested validation, calls validate on the value |
- |
| skip | #[garde(skip)] |
skip validation | - |
| custom | #[garde(custom(<function or closure>))] |
a custom validator | - |
Additional notes:
requiredis only available forOptionfields.- For
lengthandrange, eitherminormaxmay be omitted, but not both. lengthandrangeuse an inclusive upper bound (min..=max).- The
<mode>argument forlengthis explained here - For
contains,prefix, andsuffix, the pattern must be a string literal, because thePatternAPI is currently unstable. - Garde does not enable the default features of the
regexcrate - if you need extra regex features (e.g. Unicode) or better performance, add a dependency onregex = "1"to yourCargo.toml.
If most of the fields on your struct are annotated with #[garde(skip)], you may use #[garde(allow_unvalidated)] instead:
#[derive(garde::Validate)]
struct Foo<'a> {
#[garde(length(min = 1))]
a: &'a str,
#[garde(skip)]
b: &'a str, // this field will not be validated
}
#[derive(garde::Validate)]
#[garde(allow_unvalidated)]
struct Bar<'a> {
#[garde(length(min = 1))]
a: &'a str,
b: &'a str, // this field will not be validated
// note the lack of `#[garde(skip)]`
}
Length modes
The length rule accepts an optional mode argument, which determines what kind of length it will validate.
The options are:
simplebytesgraphemesutf16chars
The simple is the default used when the mode argument is omitted. The meaning of "simple length"
depends on the type. It is currently implemented for strings, where it validates the number of bytes,
and std::collections, where it validates the number of items.
#[derive(garde::Validate)]
struct Foo {
#[garde(length(min = 1, max = 100))]
string: String,
#[garde(length(min = 1, max = 100))]
collection: Vec<u32>
}
The bytes, graphemes, utf16, and chars exist mostly for string validation:
bytesvalidates the number of bytesgraphemensuses theunicode-segmentationcrate, and validates the number of graphemesutf16usesencode_utf16, and validates the number of UTF-16 code pointscharsuseschars, and validates the number of unicode scalar values
#[derive(garde::Validate)]
struct Foo {
#[garde(length(bytes, min = 1, max = 100))]
a: String, // `a.len()`
#[garde(length(graphemes, min = 1, max = 100))]
b: String, // `b.graphemes().count()`
#[garde(length(utf16, min = 1, max = 100))]
c: String, // `c.encode_utf16().count()`
#[garde(length(chars, min = 1, max = 100))]
d: String, // `d.chars().count()`
}
Inner type validation
If you need to validate the "inner" type of a container, such as the String in Vec<String>, then use the inner modifier:
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1),
inner(ascii, length(min = 1)), // wrap the rule in `inner`
)]
items: Vec<String>,
}
The above type would fail validation if:
- the
Vecis empty - any of the inner
Stringelements is empty - any of the inner
Stringelements contains non-ASCII characters
To validate a deeply-nested type, such as Vec<Option<String>>, the inner modifier must be nested for each level of generics:
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1), // applies to `Vec`
inner(inner(ascii, length(min = 1))), // applies to `String`
)]
items: Vec<Option<String>>,
}
You can apply separate rules to every level of the nested type:
#[derive(garde::Validate)]
struct Test {
#[garde(
length(min = 1), // applies to `Vec`
inner(required), // applies to `Option`
inner(inner(ascii, length(min = 1))), // applies to `String`
)]
items: Vec<Option<String>>,
}
Newtypes
The best way to re-use validation rules on a field is to use the newtype idiom
with #[garde(transparent)]:
#[derive(garde::Validate)]
#[garde(transparent)]
struct Username(#[garde(length(min = 3, max = 20))] String);
#[derive(garde::Validate)]
struct User {
// later used with `dive`:
#[garde(dive)]
username: Username,
}
The username field in the above example will inherit all the validation rules from the String field on Username. The result is that the error path will be flattened by one level, resulting in cleaner error messages:
User {
username: Username("")
}.validate()
"username: length is lower than 3"
Without the #[garde(transparent)] attribute, it would instead be:
User {
username: Username("")
}.validate()
"username[0]: length is lower than 3"
Structs with the #[garde(transparent)] attribute may have more than one field, but there must be only one unskipped field. That means every field other than the one you wish to validate must be #[garde(skip)].
Handling Option
Every rule works on Option<T> fields. The field will only be validated if it is Some. If you additionally want to validate that the Option<T> field is Some, use the required rule:
#[derive(garde::Validate)]
struct Test {
#[garde(required, ascii, length(min = 1))]
value: Option<String>,
}
The above type would fail validation if:
valueisNone- the inner
valueis empty - the inner
valuecontains non-ASCII characters
Custom validation
Validation may be customized via the custom rule, and the context attribute.
The context may be any type without generic parameters. By default, the context is ().
#[derive(garde::Validate)]
#[garde(context(PasswordContext))]
struct User {
#[garde(custom(is_strong_password))]
password: String,
}
struct PasswordContext {
min_entropy: f32,
entropy: cracken::password_entropy::EntropyEstimator,
}
fn is_strong_password(value: &str, context: &PasswordContext) -> garde::Result {
let bits = context.entropy.estimate_password_entropy(value.as_bytes())
.map(|e| e.mask_entropy)
.unwrap_or(0.0);
if bits < context.min_entropy {
return Err(garde::Error::new("password is not strong enough"));
}
Ok(())
}
let ctx = PasswordContext { /* ... */ };
let user = User { /* ... */ };
user.validate(&ctx)?;
The validator function may accept the value as a reference to any type which it derefs to.
In the above example, it is possible to use &str, because password is a String, and String derefs to &str.
The #[garde(custom(...))] attribute accepts any expression which evalutes to a something which implements the following trait:
FnOnce(&T, &<T as Validate>::Context) -> garde::Result
That means it's possible to use higher order functions:
// Returns a function which does the actual validation.
fn my_equals(other: &str) -> impl FnOnce(&str, &()) -> garde::Result + '_ {
move |value, _| {
if value != other {
return Err(garde::Error::new(format!("not equal to {other}")));
}
Ok(())
}
}
#[derive(garde::Validate)]
struct User {
#[garde(length(min = 1, max = 255))]
password: String,
// Combined with `self` access in rules:
#[garde(custom(my_equals(&self.password2)))]
password2: String,
}
Context/Self access
It's generally possible to also access the context and self, because they are in scope in the output of the proc macro:
struct Limits {
min: usize,
max: usize,
}
struct Config {
username: Limits,
}
#[derive(garde::Validate)]
#[garde(context(Config as ctx))]
struct User {
#[garde(length(min = ctx.username.min, max = ctx.username.max))]
username: String,
}
Implementing rules
Say you want to implement length checking for a custom string-like type.
To do this, you would implement one of the length traits for it, depending
on what kind of validation you are looking for.
#[repr(transparent)]
pub struct MyString(String);
impl garde::rules::length::HasSimpleLength for MyString {
fn length(&self) -> usize {
self.0.len()
}
}
#[derive(garde::Validate)]
struct Foo {
// Now the `length` check may be used with `MyString`
#[garde(length(min = 1, max = 1000))]
field: MyString,
}
Each rule comes with its own trait that may be implemented by custom types in your code.
They are all available under garde::rules.
Implementing Validate
In case you have a container type for which you'd like to support nested validation (using the #[garde(dive)] rule),
you may implement Validate for it:
#[repr(transparent)]
struct MyVec<T>(Vec<T>);
impl<T: garde::Validate> garde::Validate for MyVec<T> {
type Context = T::Context;
fn validate_into(
&self,
ctx: &Self::Context,
mut parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report
) {
for (index, item) in self.0.iter().enumerate() {
let mut path = garde::util::nested_path!(parent, index);
item.validate_into(ctx, &mut path, report);
}
}
}
#[derive(garde::Validate)]
struct Foo {
#[garde(dive)]
field: MyVec<Bar>,
}
#[derive(garde::Validate)]
struct Bar {
#[garde(range(min = 1, max = 10))]
value: u32,
}
Rule adapters
Adapters allow you to implement validation for third-party types without using a newtype.
An adapter may look like this:
mod my_str_adapter {
#![allow(unused_imports)]
pub use garde::rules::*; // re-export garde's rules
pub mod length {
pub use garde::rules::length::*; // re-export `length` rules
pub mod simple {
// re-implement `simple`, but _only_ for the concrete type &str!
pub fn apply(v: &str, (min, max): (usize, usize)) -> garde::Result {
if !(min..=max).contains(&v.len()) {
Err(garde::Error::new("my custom error message"))
} else {
Ok(())
}
}
}
}
}
You create a module, add a public glob re-export of garde::rules inside of it,
and then re-implement the specific rule you're interested in. This is a form of
duck typing. Any rule which you have
not re-implemented is simply delegated to garde's impl.
It's quite verbose, but in exchange it is maximally flexible. To use the adapter,
add an adapt attribute to a field:
#[derive(garde::Validate)]
struct Stuff<'a> {
#[garde(
adapt(my_str_adapter),
length(min = 1),
ascii,
)]
v: &'a str,
}
The length rule will now use your custom implementation, but the ascii rule
will continue to use garde's implementation.
Integration with web frameworks
Feature flags
| name | description | extra dependencies |
|---|---|---|
derive |
Enables the usage of the derive(Validate) macro |
garde_derive |
url |
Validation of URLs via the url crate. |
url |
email |
Validation of emails according to HTML5 | regex, once_cell |
email-idna |
Support for Internationalizing Domain Names for Applications in email addresses | idna |
regex |
Support for regular expressions in pattern via the regex crate |
regex, once_cell |
credit-card |
Validation of credit card numbers via the card-validate crate |
card-validate |
phone-number |
Validation of phone numbers via the phonenumber crate |
phonenumber |
unicode |
Validation of grapheme count via the unicode-segmentation crate |
unicode-segmentation |
Why garde?
Garde means guard in French. I am not French, nor do I speak the language, but guard was taken, and this is close enough :).
Development
Contributing to garde only requires a somewhat recent version of Rust.
This repository also makes use of the following tools, but they are optional:
instafor snapshot testing (tests/rules).justfor running recipes defined in thejustfile. Runjust -lto see what recipes are available.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Acknowledgements
This crate is heavily inspired by the validator crate. It is essentially a full rewrite of validator.
The creation of this crate was prompted by this comment
and a few others talking about a potential rewrite.