Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5. Validation

Validation is handled by garde. This chapter collects the most used rules and the situations you will run into first.

The rules

Every field of a #[derive(Validate)] struct needs exactly one #[garde(...)] attribute (or skip). Several rules combine with commas.

RuleApplies toExample
length(min = a, max = b)strings, Vec, maps#[garde(length(min = 1, max = 64))]
byte_length(...)strings (bytes instead of chars)#[garde(byte_length(max = 255))]
range(min = a, max = b)numbers#[garde(range(min = 1, max = 100))]
emailstrings#[garde(email)]
urlstrings#[garde(url)]
ip, ipv4, ipv6strings#[garde(ipv4)]
alphanumeric, asciistrings#[garde(alphanumeric)]
contains("x"), prefix("x"), suffix("x")strings#[garde(prefix("sk_"))]
pattern(r"...")strings (regex feature)#[garde(pattern(r"^[a-z]+$"))]
matches(other)equal to another field#[garde(matches(password))]
requiredOption<T> must be Some#[garde(required)]
inner(...)applies the rule to the elements#[garde(inner(length(min = 1)))]
divevalidates a nested struct#[garde(dive)]
custom(fn)your own function#[garde(custom(is_even))]
skipno check#[garde(skip)]

email and url need the garde features of the same name. lesto turns them on through its own email and url features, which are on by default; default-features = false on lesto drops them (and garde’s regex and idna dependencies) when you do not use those rules. pattern needs regex.

A complete example

use lesto::prelude::*;

#[lesto::model]
struct Address {
    #[garde(length(min = 1))]
    street: String,
    #[garde(length(min = 2, max = 2), ascii)]
    country: String,
}

#[lesto::model]
struct SignUp {
    #[garde(length(min = 3, max = 32), alphanumeric)]
    username: String,
    #[garde(email)]
    email: String,
    #[garde(length(min = 8))]
    password: String,
    #[garde(matches(password))]
    password_confirm: String,
    /// Age; optional but, when present, at least 18.
    #[garde(inner(range(min = 18)))]
    age: Option<u8>,
    /// At least one address, each of them valid.
    #[garde(length(min = 1), dive)]
    addresses: Vec<Address>,
    #[garde(custom(no_spaces))]
    display_name: String,
}

fn no_spaces(value: &str, _ctx: &()) -> garde::Result {
    if value.contains(' ') {
        return Err(garde::Error::new("must not contain spaces"));
    }
    Ok(())
}

#[lesto::post("/signup", status = 201)]
async fn signup(Json(body): Json<SignUp>) -> String {
    format!("welcome {}", body.username)
}

A body with two addresses, the second one without a street, produces:

{
  "errors": [
    { "in": "body", "pointer": "/addresses/1/street", "detail": "length is lower than 1", "code": "value_error" }
  ]
}

The pointer walks through structs and arrays: /addresses/1/street is the street field of the second address. It is a JSON Pointer (RFC 6901), so a client can map the error onto the right form field.

What reaches the documentation

schemars knows garde’s attributes and translates them into JSON Schema constraints:

gardeJSON Schema
length(min, max) on a stringminLength, maxLength
length(min, max) on a VecminItems, maxItems
range(min, max)minimum, maximum
emailformat: email
urlformat: uri
pattern(r)pattern
requiredthe field leaves Option and enters required

A custom rule has no translation: if you want it documented, say so in the field’s doc comment.

Structs without rules

If a struct has nothing to validate but still has to go through Json<T> or Query<T>:

#[lesto::model]
#[garde(allow_unvalidated)]
struct Filters {
    tag: Option<String>,
    archived: bool,
}

Validating outside the extractors

Validate is an ordinary trait. You can call it wherever you like:

let report = body.validate();       // Result<(), garde::Report>

and turn the report into an HTTP error with lesto::ValidationError::from_garde("body", &report), which is exactly what Json<T> does.

Recap

  • One #[garde(...)] attribute per field, or allow_unvalidated on the struct.
  • dive for nested structs, inner for the elements of Option and Vec.
  • custom(fn) for your own rules: fn(&T, &()) -> garde::Result.
  • Constraints end up in the OpenAPI schema as well.

Next: Responses.