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 Server Error")]
22    InternalServerError(#[source] Box<dyn std::error::Error + Send + Sync>),
23
24    #[error("Database Error: {0}")]
25    DatabaseError(String),
26}
27
28/// Standardized JSON response format for errors
29#[derive(Serialize)]
30pub struct ErrorResponse {
31    pub status: u16,
32    pub message: String,
33}
34
35impl IntoResponse for AppError {
36    fn into_response(self) -> Response {
37        let (status, message) = match &self {
38            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
39            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
40            AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
41            AppError::InternalServerError(err) => {
42                // In production, we might not want to expose the internal error details
43                eprintln!("Internal Server Error: {}", err);
44                (
45                    StatusCode::INTERNAL_SERVER_ERROR,
46                    "Internal Server Error".to_string(),
47                )
48            }
49            AppError::DatabaseError(msg) => {
50                eprintln!("Database Error: {}", msg);
51                (
52                    StatusCode::INTERNAL_SERVER_ERROR,
53                    "Database Error".to_string(),
54                )
55            }
56        };
57
58        let body = Json(ErrorResponse {
59            status: status.as_u16(),
60            message,
61        });
62
63        (status, body).into_response()
64    }
65}
66
67pub fn setup() {
68    println!("ferrox-errors initialized: Provides global AppError and IntoResponse for Axum.");
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use axum::response::IntoResponse;
75    use axum::http::StatusCode;
76
77    #[test]
78    fn test_error_formatting() {
79        let err = AppError::NotFound("User".into());
80        assert_eq!(err.to_string(), "Not Found: User");
81
82        let err = AppError::ValidationError("Invalid email".into());
83        assert_eq!(err.to_string(), "Validation Error: Invalid email");
84    }
85
86    #[test]
87    fn test_into_response() {
88        let err = AppError::Unauthorized("Invalid token".into());
89        let response = err.into_response();
90        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
91
92        let err = AppError::DatabaseError("Connection lost".into());
93        let response = err.into_response();
94        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
95    }
96}