Concepts

Validation / Pipes

Validation pipes, DTO validation, and stable 422 responses.

Pipes

Pipes transform or validate request data. The validation crate integrates with garde.

let input = ValidationPipe::new().transform(input)?;

Custom pipes implement the typed Pipe<Input> trait:

struct TrimName;

impl Pipe<CreateUser> for TrimName {
    type Output = CreateUser;
    type Error = std::convert::Infallible;

    fn transform(&self, mut input: CreateUser) -> Result<Self::Output, Self::Error> {
        input.name = input.name.trim().to_owned();
        Ok(input)
    }
}

Validation errors expose field-level context so applications can return useful client responses:

let error = ValidationPipe::new().transform(input).unwrap_err();
for field in error.field_errors() {
    println!("{} failed {}", field.field(), field.code());
}

Nested validation errors are flattened into deterministic field paths such as profile.display_name or members[0].email.

ValidationPipeError implements Axum's IntoResponse. The default response is HTTP 422 with a stable validation_failed code and deterministic field-level error details, so route handlers can return Result<T, ValidationPipeError> when the framework JSON shape is acceptable.

For JSON request bodies, use ValidatedJson<T> to deserialize with Axum and validate with ValidationPipe before the handler runs:

async fn create(ValidatedJson(input): ValidatedJson<CreateUser>) -> Json<UserDto> {
    Json(create_user(input))
}