llm_incident_manager/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde_json::json;
7use thiserror::Error;
8
9/// Application error types
10#[derive(Error, Debug)]
11pub enum AppError {
12    /// Database errors
13    #[error("Database error: {0}")]
14    Database(String),
15
16    /// Not found errors
17    #[error("Not found: {0}")]
18    NotFound(String),
19
20    /// Validation errors
21    #[error("Validation error: {0}")]
22    Validation(String),
23
24    /// Configuration errors
25    #[error("Configuration error: {0}")]
26    Configuration(String),
27
28    /// IO errors
29    #[error("IO error: {0}")]
30    Io(#[from] std::io::Error),
31
32    /// Serialization errors
33    #[error("Serialization error: {0}")]
34    Serialization(String),
35
36    /// Network errors
37    #[error("Network error: {0}")]
38    Network(String),
39
40    /// Authentication errors
41    #[error("Authentication error: {0}")]
42    Authentication(String),
43
44    /// Authorization errors
45    #[error("Authorization error: {0}")]
46    Authorization(String),
47
48    /// Rate limit errors
49    #[error("Rate limit exceeded")]
50    RateLimit,
51
52    /// Timeout errors
53    #[error("Operation timed out: {0}")]
54    Timeout(String),
55
56    /// Internal server errors
57    #[error("Internal error: {0}")]
58    Internal(String),
59
60    /// Integration errors
61    #[error("Integration error ({integration_source}): {message}")]
62    Integration { integration_source: String, message: String },
63
64    /// Processing errors
65    #[error("Processing error: {0}")]
66    Processing(String),
67
68    /// Invalid state transition
69    #[error("Invalid state transition: {0}")]
70    InvalidStateTransition(String),
71}
72
73impl AppError {
74    /// Get HTTP status code for this error
75    pub fn status_code(&self) -> StatusCode {
76        match self {
77            AppError::NotFound(_) => StatusCode::NOT_FOUND,
78            AppError::Validation(_) => StatusCode::BAD_REQUEST,
79            AppError::Authentication(_) => StatusCode::UNAUTHORIZED,
80            AppError::Authorization(_) => StatusCode::FORBIDDEN,
81            AppError::RateLimit => StatusCode::TOO_MANY_REQUESTS,
82            AppError::Timeout(_) => StatusCode::REQUEST_TIMEOUT,
83            AppError::Configuration(_) => StatusCode::INTERNAL_SERVER_ERROR,
84            AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
85            AppError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
86            AppError::Serialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
87            AppError::Network(_) => StatusCode::BAD_GATEWAY,
88            AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
89            AppError::Integration { .. } => StatusCode::BAD_GATEWAY,
90            AppError::Processing(_) => StatusCode::INTERNAL_SERVER_ERROR,
91            AppError::InvalidStateTransition(_) => StatusCode::CONFLICT,
92        }
93    }
94
95    /// Get error code string
96    pub fn error_code(&self) -> &str {
97        match self {
98            AppError::NotFound(_) => "NOT_FOUND",
99            AppError::Validation(_) => "VALIDATION_ERROR",
100            AppError::Authentication(_) => "AUTHENTICATION_ERROR",
101            AppError::Authorization(_) => "AUTHORIZATION_ERROR",
102            AppError::RateLimit => "RATE_LIMIT_EXCEEDED",
103            AppError::Timeout(_) => "TIMEOUT",
104            AppError::Configuration(_) => "CONFIGURATION_ERROR",
105            AppError::Database(_) => "DATABASE_ERROR",
106            AppError::Io(_) => "IO_ERROR",
107            AppError::Serialization(_) => "SERIALIZATION_ERROR",
108            AppError::Network(_) => "NETWORK_ERROR",
109            AppError::Internal(_) => "INTERNAL_ERROR",
110            AppError::Integration { .. } => "INTEGRATION_ERROR",
111            AppError::Processing(_) => "PROCESSING_ERROR",
112            AppError::InvalidStateTransition(_) => "INVALID_STATE_TRANSITION",
113        }
114    }
115}
116
117/// Convert AppError to HTTP response
118impl IntoResponse for AppError {
119    fn into_response(self) -> Response {
120        let status = self.status_code();
121        let error_code = self.error_code();
122        let message = self.to_string();
123
124        tracing::error!(
125            error_code = error_code,
126            status_code = status.as_u16(),
127            message = %message,
128            "Request error"
129        );
130
131        let body = Json(json!({
132            "error": {
133                "code": error_code,
134                "message": message,
135                "status": status.as_u16(),
136            }
137        }));
138
139        (status, body).into_response()
140    }
141}
142
143/// Conversion from serde_json::Error
144impl From<serde_json::Error> for AppError {
145    fn from(err: serde_json::Error) -> Self {
146        AppError::Serialization(err.to_string())
147    }
148}
149
150/// Conversion from serde_yaml::Error
151impl From<serde_yaml::Error> for AppError {
152    fn from(err: serde_yaml::Error) -> Self {
153        AppError::Serialization(err.to_string())
154    }
155}
156
157/// Conversion from validator::ValidationErrors
158impl From<validator::ValidationErrors> for AppError {
159    fn from(err: validator::ValidationErrors) -> Self {
160        AppError::Validation(err.to_string())
161    }
162}
163
164/// Conversion from config::ConfigError
165impl From<config::ConfigError> for AppError {
166    fn from(err: config::ConfigError) -> Self {
167        AppError::Configuration(err.to_string())
168    }
169}
170
171/// Result type alias
172pub type Result<T> = std::result::Result<T, AppError>;
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_error_status_codes() {
180        assert_eq!(
181            AppError::NotFound("test".to_string()).status_code(),
182            StatusCode::NOT_FOUND
183        );
184        assert_eq!(
185            AppError::Validation("test".to_string()).status_code(),
186            StatusCode::BAD_REQUEST
187        );
188        assert_eq!(
189            AppError::Authentication("test".to_string()).status_code(),
190            StatusCode::UNAUTHORIZED
191        );
192        assert_eq!(
193            AppError::RateLimit.status_code(),
194            StatusCode::TOO_MANY_REQUESTS
195        );
196    }
197
198    #[test]
199    fn test_error_codes() {
200        assert_eq!(
201            AppError::NotFound("test".to_string()).error_code(),
202            "NOT_FOUND"
203        );
204        assert_eq!(
205            AppError::Validation("test".to_string()).error_code(),
206            "VALIDATION_ERROR"
207        );
208        assert_eq!(AppError::RateLimit.error_code(), "RATE_LIMIT_EXCEEDED");
209    }
210}