1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde::Serialize;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum AppError {
12 #[error("Not Found: {0}")]
13 NotFound(String),
14
15 #[error("Validation Error: {0}")]
16 ValidationError(String),
17
18 #[error("Unauthorized: {0}")]
19 Unauthorized(String),
20
21 #[error("Internal Error: {0}")]
22 InternalError(String),
23
24 #[error("Internal Server Error")]
25 InternalServerError(#[source] Box<dyn std::error::Error + Send + Sync>),
26
27 #[error("Database Error: {0}")]
28 DatabaseError(String),
29}
30
31#[derive(Serialize)]
33pub struct ErrorResponse {
34 pub status: u16,
35 pub message: String,
36}
37
38impl IntoResponse for AppError {
39 fn into_response(self) -> Response {
40 let (status, message) = match &self {
41 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
42 AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
43 AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
44 AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
45 AppError::InternalServerError(err) => {
46 eprintln!("Internal Server Error: {}", err);
47 (
48 StatusCode::INTERNAL_SERVER_ERROR,
49 "Internal Server Error".to_string(),
50 )
51 }
52 AppError::DatabaseError(msg) => {
53 eprintln!("Database Error: {}", msg);
54 (
55 StatusCode::INTERNAL_SERVER_ERROR,
56 "Database Error".to_string(),
57 )
58 }
59 };
60
61 let body = Json(ErrorResponse {
62 status: status.as_u16(),
63 message,
64 });
65
66 (status, body).into_response()
67 }
68}
69
70pub fn setup() {
71 println!("ferrox-errors initialized: Provides global AppError and IntoResponse for Axum.");
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use axum::response::IntoResponse;
78 use axum::http::StatusCode;
79
80 #[test]
81 fn test_error_formatting() {
82 let err = AppError::NotFound("User".into());
83 assert_eq!(err.to_string(), "Not Found: User");
84
85 let err = AppError::ValidationError("Invalid email".into());
86 assert_eq!(err.to_string(), "Validation Error: Invalid email");
87 }
88
89 #[test]
90 fn test_into_response() {
91 let err = AppError::Unauthorized("Invalid token".into());
92 let response = err.into_response();
93 assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
94
95 let err = AppError::DatabaseError("Connection lost".into());
96 let response = err.into_response();
97 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
98 }
99}