serverust_core/error.rs
1use axum::Json as AxumJson;
2use axum::response::{IntoResponse, Response};
3use http::StatusCode;
4use serde_json::{Map, Value, json};
5use validator::ValidationErrors;
6
7/// Trait implementada por enums de erro de domínio. Normalmente não é
8/// implementada manualmente — use `#[derive(ApiError)]` do crate
9/// `serverust-macros`, que lê `#[status(N)]` e `#[message("...")]` por variante
10/// e emite simultaneamente `impl ApiError` + `impl IntoResponse`.
11///
12/// Resultado prático: você pode usar `?` em handlers `Result<T, MyError>` e a
13/// falha vira resposta JSON padronizada (`{"error":"<message>"}` com o status
14/// declarado).
15///
16/// ```ignore
17/// use serverust_macros::ApiError;
18///
19/// #[derive(Debug, ApiError)]
20/// pub enum TaskError {
21/// #[status(404)]
22/// #[message("Task não encontrada")]
23/// NotFound,
24///
25/// #[status(409)]
26/// #[message("Título já existe")]
27/// DuplicateTitle,
28/// }
29/// ```
30pub trait ApiError {
31 fn status(&self) -> u16;
32 fn message(&self) -> String;
33}
34
35/// Constrói a resposta HTTP 422 padronizada a partir de erros do validator.
36///
37/// Formato: `{ "error": "validation_error", "fields": { campo: [mensagens] } }`.
38pub fn validation_error_response(errors: &ValidationErrors) -> Response {
39 let mut fields = Map::new();
40
41 for (field, kind) in errors.field_errors() {
42 let messages: Vec<Value> = kind
43 .iter()
44 .map(|e| {
45 let msg = e
46 .message
47 .as_ref()
48 .map(|m| m.to_string())
49 .unwrap_or_else(|| e.code.to_string());
50 Value::String(msg)
51 })
52 .collect();
53 fields.insert((*field).to_string(), Value::Array(messages));
54 }
55
56 let body = json!({
57 "error": "validation_error",
58 "fields": fields,
59 });
60
61 (StatusCode::UNPROCESSABLE_ENTITY, AxumJson(body)).into_response()
62}