karbon_framework/error/
app_error.rs1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use serde::Serialize;
4
5#[derive(Debug, thiserror::Error)]
7pub enum AppError {
8 #[error("Not found: {0}")]
9 NotFound(String),
10
11 #[error("Bad request: {0}")]
12 BadRequest(String),
13
14 #[error("Unauthorized: {0}")]
15 Unauthorized(String),
16
17 #[error("Forbidden: {0}")]
18 Forbidden(String),
19
20 #[error("Conflict: {0}")]
21 Conflict(String),
22
23 #[error("Validation error: {0}")]
24 Validation(String),
25
26 #[error("Internal error: {0}")]
27 Internal(String),
28
29 #[error("Database error: {0}")]
30 Database(#[from] sqlx::Error),
31
32 #[error("JWT error: {0}")]
33 Jwt(#[from] jsonwebtoken::errors::Error),
34
35 #[error("JSON error: {0}")]
36 Json(#[from] serde_json::Error),
37}
38
39pub type AppResult<T> = Result<T, AppError>;
40
41#[derive(Serialize)]
42struct ErrorResponse {
43 error: String,
44 message: String,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 details: Option<serde_json::Value>,
47}
48
49impl AppError {
50 fn status_code(&self) -> StatusCode {
51 match self {
52 Self::NotFound(_) => StatusCode::NOT_FOUND,
53 Self::BadRequest(_) | Self::Validation(_) => StatusCode::BAD_REQUEST,
54 Self::Unauthorized(_) | Self::Jwt(_) => StatusCode::UNAUTHORIZED,
55 Self::Forbidden(_) => StatusCode::FORBIDDEN,
56 Self::Conflict(_) => StatusCode::CONFLICT,
57 Self::Internal(_) | Self::Database(_) | Self::Json(_) => {
58 StatusCode::INTERNAL_SERVER_ERROR
59 }
60 }
61 }
62
63 fn error_type(&self) -> &str {
64 match self {
65 Self::NotFound(_) => "not_found",
66 Self::BadRequest(_) => "bad_request",
67 Self::Unauthorized(_) | Self::Jwt(_) => "unauthorized",
68 Self::Forbidden(_) => "forbidden",
69 Self::Conflict(_) => "conflict",
70 Self::Validation(_) => "validation_error",
71 Self::Internal(_) | Self::Database(_) | Self::Json(_) => "internal_error",
72 }
73 }
74}
75
76impl IntoResponse for AppError {
77 fn into_response(self) -> Response {
78 let status = self.status_code();
79 let error_type = self.error_type().to_string();
80
81 if status.is_server_error() {
83 tracing::error!(%status, error = %self, "Server error");
84 }
85
86 let body = ErrorResponse {
87 error: error_type,
88 message: self.to_string(),
89 details: None,
90 };
91
92 (status, axum::Json(body)).into_response()
93 }
94}