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§
- Valid
Rejection - 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.