llm_incident_manager/
error.rs1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde_json::json;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum AppError {
12 #[error("Database error: {0}")]
14 Database(String),
15
16 #[error("Not found: {0}")]
18 NotFound(String),
19
20 #[error("Validation error: {0}")]
22 Validation(String),
23
24 #[error("Configuration error: {0}")]
26 Configuration(String),
27
28 #[error("IO error: {0}")]
30 Io(#[from] std::io::Error),
31
32 #[error("Serialization error: {0}")]
34 Serialization(String),
35
36 #[error("Network error: {0}")]
38 Network(String),
39
40 #[error("Authentication error: {0}")]
42 Authentication(String),
43
44 #[error("Authorization error: {0}")]
46 Authorization(String),
47
48 #[error("Rate limit exceeded")]
50 RateLimit,
51
52 #[error("Operation timed out: {0}")]
54 Timeout(String),
55
56 #[error("Internal error: {0}")]
58 Internal(String),
59
60 #[error("Integration error ({integration_source}): {message}")]
62 Integration { integration_source: String, message: String },
63
64 #[error("Processing error: {0}")]
66 Processing(String),
67
68 #[error("Invalid state transition: {0}")]
70 InvalidStateTransition(String),
71}
72
73impl AppError {
74 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 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
117impl 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
143impl From<serde_json::Error> for AppError {
145 fn from(err: serde_json::Error) -> Self {
146 AppError::Serialization(err.to_string())
147 }
148}
149
150impl From<serde_yaml::Error> for AppError {
152 fn from(err: serde_yaml::Error) -> Self {
153 AppError::Serialization(err.to_string())
154 }
155}
156
157impl From<validator::ValidationErrors> for AppError {
159 fn from(err: validator::ValidationErrors) -> Self {
160 AppError::Validation(err.to_string())
161 }
162}
163
164impl From<config::ConfigError> for AppError {
166 fn from(err: config::ConfigError) -> Self {
167 AppError::Configuration(err.to_string())
168 }
169}
170
171pub 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}