validator crate for constraint checking. The #[dto] macro derives both automatically; ValidationPipe or ValidatedBody<T> runs the validation before your handler receives the data. Invalid payloads return 422 Unprocessable Entity with a structured error body.
#[dto]
Derives serde::Deserialize, validator::Validate, and NestDto on a struct. By default it also emits #[serde(deny_unknown_fields)] so any JSON key not in the struct definition causes a 400 Bad Request (deserialization fails inside the JSON extractor, before validation runs).
#[dto(allow_unknown_fields)]
Opts out of deny_unknown_fields. Use this when you intentionally accept JSON payloads from forward-compatible clients that may include extra fields.
NestDto trait
NestDto is a marker trait generated by #[dto]. The validation extractors (ValidatedBody<T>, ValidatedQuery<T>, ValidatedPath<T>) and ValidationPipe do not require it — they accept any T that is DeserializeOwned (or Validate for the pipe), so hand-written structs work too. NestDto marks #[dto]-derived types for macros that require them, such as #[crud], which asserts at compile time that its generated DTOs came from #[dto].
NestDto manually; #[dto] handles it.
Using DTOs in handlers
- ValidatedBody extractor
- #[param::body] + ValidationPipe
- ValidatedQuery extractor
Field validation attributes
All field attributes are applied inside a#[dto] struct. They expand to validator crate constraint annotations.
String constraints
attribute
Readability marker — string-ness is enforced by the Rust field type. On a
String (or Option<String>) field it emits no validation code; on any other field type it is a compile-time error. Use #[IsNotEmpty] for a non-empty/presence check.attribute
Validates that the field is a well-formed email address.
attribute
Validates that the field is a well-formed URL.
attribute
Validates string length. Both
min and max are optional.Numeric constraints
attribute
Compile-checked: the field must be an integer type (
i8–i128, u8–u128, isize, usize). Adds no runtime validation — the field type already guarantees it; a non-integer field is a compile-time error.attribute
Compile-checked: the field must be a numeric type (integers, or
f32/f64). Adds no runtime validation; a non-numeric field is a compile-time error.attribute
Validates that the numeric field is greater than or equal to
N.attribute
Validates that the numeric field is less than or equal to
N.Boolean and optional
attribute
Compile-checked: the field must be a
bool. Adds no runtime validation; a non-bool field is a compile-time error.attribute
Stripped at compile time — optionality comes entirely from the field type. Wrap the field in
Option<T> and a missing JSON key deserializes as None, skipping the field’s other validators. The attribute exists to document intent for readers coming from NestJS’s @IsOptional().Nested DTOs
attribute
Runs validation recursively on a nested struct field. The nested type must also derive
Validate (which #[dto] provides).Comprehensive example
The following DTO covers string, numeric, boolean, optional, and nested validation in a single type:deny_unknown_fields default behavior
#[dto] applies #[serde(deny_unknown_fields)] by default. This means any JSON key in the request body that is not a field on the struct causes deserialization to fail with a 400 Bad Request before validation even runs.
ValidationPipe behavior with whitelist: true, forbidNonWhitelisted: true (unknown keys are rejected, not silently stripped). Use #[dto(allow_unknown_fields)] when you need to accept extra fields from clients on a different schema version.