Skip to main content

ferrox_errors/
lib.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde::Serialize;
7use thiserror::Error;
8
9/// A standard global error type for the application.
10#[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/// Standardized JSON response format for errors
32#[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}