Validação de Requisições
Valide entradas HTTP no limite com tipos serde e o crate validator antes que a lógica de negócios seja executada.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
use axum::Json;
use serde::Deserialize;
use validator::{Validate, ValidationError};
#[derive(Debug, Deserialize, Validate)]
struct SignupRequest {
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
async fn signup(Json(body): Json<SignupRequest>) -> Result<(), AppError> {
body.validate().map_err(AppError::from)?;
// lógica de negócios
Ok(())
}Quando usar isso: JSON, parâmetros de consulta ou formulários não confiáveis devem ser verificados antes das operações de banco de dados ou autenticação.
Exemplo de Trabalho
use axum::{routing::post, Json, Router};
use serde::Deserialize;
use validator::{Validate, ValidationError};
#[derive(Deserialize, Validate)]
struct CreateUser {
#[validate(email(message = "email inválido"))]
email: String,
#[validate(length(min = 2, max = 50))]
display_name: String,
#[validate(range(min = 18, max = 120))]
age: u8,
}
#[derive(serde::Serialize)]
struct ErrorBody { fields: Vec<String> }
enum AppError { Validation(Vec<String>) }
impl axum::response::IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
match self {
AppError::Validation(fields) => (
axum::http::StatusCode::UNPROCESSABLE_ENTITY,
Json(ErrorBody { fields }),
).into_response(),
}
}
}
async fn create_user(Json(body): Json<CreateUser>) -> Result<&'static str, AppError> {
body.validate().map_err(|e| {
AppError::Validation(e.field_errors().keys().map(|k| k.to_string()).collect())
})?;
Ok("criado")
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/users", post(create_user));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}O que isso demonstra:
- Atributos
validatorem structsserde. - Chamada explícita
validate()após a desserialização JSON. - Resposta 422 com nomes de campos para formulários do cliente.
- Separação entre análise de transporte (serde) e regras de negócios (validator).
Mergulho Profundo
Como Funciona
serdeanalisa JSON em tipos; JSON malformado nunca chega avalidate().validatorexecuta verificações síncronas: comprimento, e-mail, regex, funções personalizadas.- Retorna 422 (Entidade Não Processável) para falhas de validação semântica.
Camadas de Validação
| Camada | Ferramenta | Captura |
|---|---|---|
| Formato de fio | serde | JSON inválido, tipos errados |
| Regras de campo | validator | E-mail, comprimento, intervalo |
| Regras de negócios | Handler/serviço | E-mail duplicado, domínio banido |
Notas de Rust
fn check_reserved(name: &str) -> Result<(), ValidationError> {
if name == "admin" {
return Err(ValidationError::new("reserved"));
}
Ok(())
}
#[validate(custom(function = "check_reserved"))]
username: String,Armadilhas
- Pular
validate()- DerivarValidatenão executa verificações automaticamente. Correção: Chame.validate()?em cada handler. serdepassa semânticas inválidas -email: "not-an-email"desserializa corretamente. Correção: Adicione#[validate(email)].- Banco de dados como validador - Verificações de exclusividade pertencem após a validação de formato. Correção: Duas fases: validador e depois consulta ao banco de dados.
- Vazamento de erros da biblioteca de validação - String de depuração bruta
ValidationErrorsna resposta. Correção: Mapeie para códigos estáveis em nível de campo. - Validação de parâmetro de consulta esquecida - Apenas validando corpos JSON. Correção: Derive
Validateem structsQuerytambém.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
garde | Derive de validação em tempo de compilação | Equipe já padronizada em validator |
Verificações if manuais | Handlers minúsculos com uma regra | DTOs reutilizáveis entre endpoints |
| JSON Schema | Validação de contrato externa | CRUD simples com tipos Rust |
FAQs
422 vs 400?
422 para JSON bem formado com valores de campo inválidos; 400 para requisições malformadas.
Validar parâmetros de caminho?
Valide após a extração Path com verificações manuais ou um newtype com TryFrom.
Objetos aninhados?
Use #[validate(nested)] em structs filhas.
Regras condicionais?
#[validate(required_if = "...")] ou funções de validação personalizadas.
Integração OpenAPI?
Gere esquemas a partir de tipos com utoipa e documente restrições.
Sanitização vs validação?
Valide primeiro; normalize (trim, minúsculas no e-mail) em uma etapa separada antes de persistir.
Mensagens internacionalizadas?
Mapeie códigos de erro no lado do cliente; o servidor retorna chaves code estáveis.
Desempenho?
O validador é síncrono e rápido; evite chamadas de banco de dados dentro de validadores personalizados.
Atualizações parciais PATCH?
Use campos Option<T> com #[validate(required)] apenas quando presentes.
Testar validação?
POSTe corpos inválidos com tower::ServiceExt::oneshot e afirme os campos 422.
Relacionados
- Extratores e Estado -
JsoneQuery - Tratamento de Erros em Handlers - Mapear erros de validação
- Validação de Entrada e Injeção - Validação focada em segurança
- Noções Básicas de Design de API - Envelopes de erro
- Noções Básicas de Serde - Fundamentos de desserialização
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4 e Polars 0.46+.