serverust_core/validation.rs
1use axum::extract::{FromRequest, Request};
2use axum::response::{IntoResponse, Response};
3use serde::de::DeserializeOwned;
4use validator::Validate;
5
6use crate::error::validation_error_response;
7
8/// Extractor JSON com validação automática.
9///
10/// Deserializa o body como `T` e, se a validação via `validator::Validate`
11/// falhar, devolve HTTP 422 com payload padronizado **antes** do handler ser
12/// invocado:
13///
14/// ```json
15/// { "error": "validation_error", "fields": { "title": ["length"] } }
16/// ```
17///
18/// `T` precisa derivar `Deserialize` **e** `Validate`. Se você não tem regras
19/// de validação, o derive `Validate` continua sendo no-op:
20///
21/// ```ignore
22/// use serde::Deserialize;
23/// use validator::Validate;
24/// use serverust_core::extract::Json;
25/// use serverust_macros::post;
26///
27/// #[derive(Deserialize, Validate)]
28/// struct CreateTask {
29/// #[validate(length(min = 1, max = 200))]
30/// title: String,
31/// }
32///
33/// #[post("/tasks")]
34/// async fn create(Json(task): Json<CreateTask>) -> &'static str {
35/// // `task.title` já passou pela validação aqui
36/// "created"
37/// }
38/// ```
39///
40/// `Json<T>` também implementa [`IntoResponse`] para `T: Serialize`, então o
41/// mesmo tipo serve para entrada e saída do handler.
42///
43/// **Posicionamento na assinatura**: como `Json<T>` consome o body, ele tem
44/// que ser o **último parâmetro** do handler. Extractors como `Path`, `Query`,
45/// `State` (que só leem partes do request) vêm antes.
46#[derive(Debug, Clone, Copy, Default)]
47pub struct Json<T>(pub T);
48
49impl<T, S> FromRequest<S> for Json<T>
50where
51 T: DeserializeOwned + Validate,
52 S: Send + Sync,
53{
54 type Rejection = Response;
55
56 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
57 let axum::Json(value) = axum::Json::<T>::from_request(req, state)
58 .await
59 .map_err(IntoResponse::into_response)?;
60
61 match value.validate() {
62 Ok(()) => Ok(Json(value)),
63 Err(errors) => Err(validation_error_response(&errors)),
64 }
65 }
66}
67
68impl<T: serde::Serialize> IntoResponse for Json<T> {
69 fn into_response(self) -> Response {
70 axum::Json(self.0).into_response()
71 }
72}