1use axum::{
32 http::StatusCode,
33 response::{IntoResponse, Response},
34 Json,
35};
36use serde::Serialize;
37use thiserror::Error;
38
39#[derive(Debug, Error)]
41pub enum AppError {
42 #[error("Not Found: {0}")]
43 NotFound(String),
44
45 #[error("Validation Error: {0}")]
46 ValidationError(String),
47
48 #[error("Unauthorized: {0}")]
49 Unauthorized(String),
50
51 #[error("Internal Error: {0}")]
52 InternalError(String),
53
54 #[error("Internal Server Error")]
55 InternalServerError(#[source] Box<dyn std::error::Error + Send + Sync>),
56
57 #[error("Database Error: {0}")]
58 DatabaseError(String),
59}
60
61#[derive(Serialize)]
63pub struct ErrorResponse {
64 pub status: u16,
65 pub message: String,
66}
67
68impl IntoResponse for AppError {
69 fn into_response(self) -> Response {
70 let (status, message) = match &self {
71 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
72 AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
73 AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
74 AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
75 AppError::InternalServerError(err) => {
76 eprintln!("Internal Server Error: {}", err);
77 (
78 StatusCode::INTERNAL_SERVER_ERROR,
79 "Internal Server Error".to_string(),
80 )
81 }
82 AppError::DatabaseError(msg) => {
83 eprintln!("Database Error: {}", msg);
84 (
85 StatusCode::INTERNAL_SERVER_ERROR,
86 "Database Error".to_string(),
87 )
88 }
89 };
90
91 let body = Json(ErrorResponse {
92 status: status.as_u16(),
93 message,
94 });
95
96 (status, body).into_response()
97 }
98}
99
100pub fn setup() {
101 println!("ferrox-errors initialized: Provides global AppError and IntoResponse for Axum.");
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use axum::response::IntoResponse;
108 use axum::http::StatusCode;
109
110 #[test]
111 fn test_error_formatting() {
112 let err = AppError::NotFound("User".into());
113 assert_eq!(err.to_string(), "Not Found: User");
114
115 let err = AppError::ValidationError("Invalid email".into());
116 assert_eq!(err.to_string(), "Validation Error: Invalid email");
117 }
118
119 #[test]
120 fn test_into_response() {
121 let err = AppError::Unauthorized("Invalid token".into());
122 let response = err.into_response();
123 assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
124
125 let err = AppError::DatabaseError("Connection lost".into());
126 let response = err.into_response();
127 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
128 }
129}