elif_http/errors/
responses.rs

1//! HTTP error response formatting
2
3use super::HttpError;
4use crate::response::{ElifResponse, ElifStatusCode, IntoElifResponse};
5use axum::response::{IntoResponse, Response};
6use axum::Json;
7use serde_json::json;
8
9impl HttpError {
10    /// Get the appropriate HTTP status code for this error
11    pub fn status_code(&self) -> ElifStatusCode {
12        match self {
13            HttpError::StartupFailed { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
14            HttpError::ShutdownFailed { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
15            HttpError::ConfigError { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
16            HttpError::ServiceResolutionFailed { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
17            HttpError::RequestTimeout => ElifStatusCode::REQUEST_TIMEOUT,
18            HttpError::RequestTooLarge { .. } => ElifStatusCode::PAYLOAD_TOO_LARGE,
19            HttpError::BadRequest { .. } => ElifStatusCode::BAD_REQUEST,
20            HttpError::InternalError { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
21            HttpError::HealthCheckFailed { .. } => ElifStatusCode::SERVICE_UNAVAILABLE,
22            HttpError::DatabaseError { .. } => ElifStatusCode::INTERNAL_SERVER_ERROR,
23            HttpError::ValidationError { .. } => ElifStatusCode::UNPROCESSABLE_ENTITY,
24            HttpError::NotFound { .. } => ElifStatusCode::NOT_FOUND,
25            HttpError::Conflict { .. } => ElifStatusCode::CONFLICT,
26            HttpError::Unauthorized => ElifStatusCode::UNAUTHORIZED,
27            HttpError::Forbidden { .. } => ElifStatusCode::FORBIDDEN,
28        }
29    }
30
31    /// Get error hint for user guidance
32    pub fn error_hint(&self) -> Option<&'static str> {
33        match &self {
34            HttpError::RequestTooLarge { .. } => Some("Reduce request payload size"),
35            HttpError::RequestTimeout => Some("Retry the request"),
36            HttpError::BadRequest { .. } => Some("Check request format and parameters"),
37            HttpError::HealthCheckFailed { .. } => {
38                Some("Server may be starting up or experiencing issues")
39            }
40            _ => None,
41        }
42    }
43}
44
45// Implement IntoElifResponse for HttpError
46impl IntoElifResponse for HttpError {
47    fn into_response(self) -> ElifResponse {
48        let body = json!({
49            "error": {
50                "code": self.error_code(),
51                "message": self.to_string(),
52                "hint": self.error_hint()
53            }
54        });
55
56        ElifResponse::with_status(self.status_code()).json_value(body)
57    }
58}
59
60// Implement IntoResponse for automatic HTTP error responses (Axum compatibility)
61impl IntoResponse for HttpError {
62    fn into_response(self) -> Response {
63        let status = self.status_code().to_axum(); // Convert to Axum StatusCode
64        let body = json!({
65            "error": {
66                "code": self.error_code(),
67                "message": self.to_string(),
68                "hint": self.error_hint()
69            }
70        });
71
72        (status, Json(body)).into_response()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_error_status_codes() {
82        assert_eq!(
83            HttpError::bad_request("test").status_code(),
84            crate::response::status::ElifStatusCode::BAD_REQUEST
85        );
86        assert_eq!(
87            HttpError::RequestTimeout.status_code(),
88            crate::response::status::ElifStatusCode::REQUEST_TIMEOUT
89        );
90        assert_eq!(
91            HttpError::RequestTooLarge {
92                size: 100,
93                limit: 50
94            }
95            .status_code(),
96            crate::response::status::ElifStatusCode::PAYLOAD_TOO_LARGE
97        );
98        assert_eq!(
99            HttpError::health_check("Database unavailable").status_code(),
100            crate::response::status::ElifStatusCode::SERVICE_UNAVAILABLE
101        );
102    }
103
104    #[test]
105    fn test_validation_error_status_code() {
106        let validation_error = HttpError::validation_error("Field is required");
107        assert_eq!(
108            validation_error.status_code(),
109            crate::response::status::ElifStatusCode::UNPROCESSABLE_ENTITY
110        );
111    }
112
113    #[test]
114    fn test_error_hints() {
115        let timeout_error = HttpError::RequestTimeout;
116        assert_eq!(timeout_error.error_hint(), Some("Retry the request"));
117
118        let large_request_error = HttpError::RequestTooLarge {
119            size: 100,
120            limit: 50,
121        };
122        assert_eq!(
123            large_request_error.error_hint(),
124            Some("Reduce request payload size")
125        );
126
127        let not_found_error = HttpError::not_found("User");
128        assert_eq!(not_found_error.error_hint(), None);
129    }
130
131    #[test]
132    fn test_error_response_format_consistency() {
133        use axum::response::IntoResponse as AxumIntoResponse;
134
135        let error = HttpError::not_found("User");
136        let response = AxumIntoResponse::into_response(error);
137
138        assert_eq!(response.status(), axum::http::StatusCode::NOT_FOUND);
139    }
140}