Skip to main content

Module validate

Module validate 

Source
Expand description

Request-body validation (gaps3 #29 item 4).

#[umbral(trim, lowercase, max_length = N, email, ...)] has always worked on a Model — but a great many request bodies are not models. They are DTOs: a #[derive(Deserialize)] struct that gets checked, then turned into something else. Until now those had no story at all, so every handler re-implemented the same four rules by hand.

#[derive(Deserialize, Validate)]
struct CreateGoal {
    #[umbral(trim, min_length = 1, max_length = 80)]
    scorer: String,
    #[umbral(choices = ["home", "away"])]
    side: String,
    #[umbral(min = 0, max = 120)]
    minute: i64,
}

async fn create_goal(Valid(body): Valid<CreateGoal>) -> impl IntoResponse {
    // `body` is normalised AND checked. There is no path into this function
    // where it is not.
}

Two properties are doing the work here.

The vocabulary is the one you already know. These are the same attribute names, with the same meanings, as on a Model — and email / url / slug call the very same validate_text_format the ORM’s write path calls. A string a DTO accepts is a string the model accepts. Two validators that “both check emails” is how you end up with a row your own API cannot round-trip.

The gate is in the signature. Valid<T> is an extractor, so a handler that forgot to validate does not compile into existence. A validate() helper you must remember to call is a gate you can forget.

Re-exports§

pub use crate::forms::ValidationErrors;

Structs§

Valid
Extractor: deserialize a JSON body, normalise it, validate it — or reject with the same structured 400 the REST plugin emits for a model write.

Enums§

ValidRejection
Why a Valid<T> extraction failed.

Traits§

Validate
Normalise a value in place, then check it.

Functions§

check_choices
#[umbral(choices = ["home", "away"])] — an enum-of-strings, without the enum.
check_max
See check_min.
check_max_length
#[umbral(max_length = N)] — rejects rather than truncating. Silently cutting a user’s input to length is data loss that looks like success.
check_min
#[umbral(min = N)] / #[umbral(max = N)] on a numeric field.
check_min_length
#[umbral(min_length = N)] — counts CHARACTERS, not bytes. "é" is one character and two bytes; a byte-length limit would reject a name a user can legitimately have.
check_text_format
#[umbral(email)] / #[umbral(url)] / #[umbral(slug)] — delegates to the ORM’s validator so a DTO and a model agree on what a valid email is.