1use thiserror::Error;
7
8pub type HttpResult<T> = Result<T, HttpError>;
10
11#[derive(Error, Debug)]
13pub enum HttpError {
14 #[error("Server startup failed: {message}")]
15 StartupFailed { message: String },
16
17 #[error("Server shutdown failed: {message}")]
18 ShutdownFailed { message: String },
19
20 #[error("Configuration error: {message}")]
21 ConfigError { message: String },
22
23 #[error("Service resolution failed: {service}")]
24 ServiceResolutionFailed { service: String },
25
26 #[error("Request timeout")]
27 RequestTimeout,
28
29 #[error("Request too large: {size} bytes exceeds limit of {limit} bytes")]
30 RequestTooLarge { size: usize, limit: usize },
31
32 #[error("Invalid request: {message}")]
33 BadRequest { message: String },
34
35 #[error("Internal server error: {message}")]
36 InternalError { message: String },
37
38 #[error("Health check failed: {reason}")]
39 HealthCheckFailed { reason: String },
40
41 #[error("Database error: {message}")]
42 DatabaseError { message: String },
43
44 #[error("Validation error: {message}")]
45 ValidationError { message: String },
46
47 #[error("Resource not found: {resource}")]
48 NotFound { resource: String },
49
50 #[error("Resource already exists: {message}")]
51 Conflict { message: String },
52
53 #[error("Unauthorized access")]
54 Unauthorized,
55
56 #[error("Access forbidden: {message}")]
57 Forbidden { message: String },
58}
59
60impl HttpError {
61 pub fn startup<T: Into<String>>(message: T) -> Self {
63 HttpError::StartupFailed {
64 message: message.into(),
65 }
66 }
67
68 pub fn shutdown<T: Into<String>>(message: T) -> Self {
70 HttpError::ShutdownFailed {
71 message: message.into(),
72 }
73 }
74
75 pub fn config<T: Into<String>>(message: T) -> Self {
77 HttpError::ConfigError {
78 message: message.into(),
79 }
80 }
81
82 pub fn service_resolution<T: Into<String>>(service: T) -> Self {
84 HttpError::ServiceResolutionFailed {
85 service: service.into(),
86 }
87 }
88
89 pub fn bad_request<T: Into<String>>(message: T) -> Self {
91 HttpError::BadRequest {
92 message: message.into(),
93 }
94 }
95
96 pub fn internal<T: Into<String>>(message: T) -> Self {
98 HttpError::InternalError {
99 message: message.into(),
100 }
101 }
102
103 pub fn health_check<T: Into<String>>(reason: T) -> Self {
105 HttpError::HealthCheckFailed {
106 reason: reason.into(),
107 }
108 }
109
110 pub fn database_error<T: Into<String>>(message: T) -> Self {
112 HttpError::DatabaseError {
113 message: message.into(),
114 }
115 }
116
117 pub fn validation_error<T: Into<String>>(message: T) -> Self {
119 HttpError::ValidationError {
120 message: message.into(),
121 }
122 }
123
124 pub fn not_found<T: Into<String>>(resource: T) -> Self {
126 HttpError::NotFound {
127 resource: resource.into(),
128 }
129 }
130
131 pub fn conflict<T: Into<String>>(message: T) -> Self {
133 HttpError::Conflict {
134 message: message.into(),
135 }
136 }
137
138 pub fn unauthorized() -> Self {
140 HttpError::Unauthorized
141 }
142
143 pub fn forbidden<T: Into<String>>(message: T) -> Self {
145 HttpError::Forbidden {
146 message: message.into(),
147 }
148 }
149
150 pub fn timeout() -> Self {
152 HttpError::RequestTimeout
153 }
154
155 pub fn payload_too_large(size: usize, limit: usize) -> Self {
157 HttpError::RequestTooLarge { size, limit }
158 }
159
160 pub fn with_detail<T: Into<String>>(self, _detail: T) -> Self {
162 self
163 }
164
165 pub fn error_code(&self) -> &'static str {
167 match self {
168 HttpError::StartupFailed { .. } => "SERVER_STARTUP_FAILED",
169 HttpError::ShutdownFailed { .. } => "SERVER_SHUTDOWN_FAILED",
170 HttpError::ConfigError { .. } => "CONFIGURATION_ERROR",
171 HttpError::ServiceResolutionFailed { .. } => "SERVICE_RESOLUTION_FAILED",
172 HttpError::RequestTimeout => "REQUEST_TIMEOUT",
173 HttpError::RequestTooLarge { .. } => "REQUEST_TOO_LARGE",
174 HttpError::BadRequest { .. } => "BAD_REQUEST",
175 HttpError::InternalError { .. } => "INTERNAL_ERROR",
176 HttpError::HealthCheckFailed { .. } => "HEALTH_CHECK_FAILED",
177 HttpError::DatabaseError { .. } => "DATABASE_ERROR",
178 HttpError::ValidationError { .. } => "VALIDATION_ERROR",
179 HttpError::NotFound { .. } => "RESOURCE_NOT_FOUND",
180 HttpError::Conflict { .. } => "RESOURCE_CONFLICT",
181 HttpError::Unauthorized => "UNAUTHORIZED_ACCESS",
182 HttpError::Forbidden { .. } => "ACCESS_FORBIDDEN",
183 }
184 }
185}
186
187impl From<elif_core::ConfigError> for HttpError {
189 fn from(err: elif_core::ConfigError) -> Self {
190 HttpError::ConfigError {
191 message: err.to_string(),
192 }
193 }
194}
195
196impl From<std::io::Error> for HttpError {
198 fn from(err: std::io::Error) -> Self {
199 HttpError::InternalError {
200 message: format!("IO error: {}", err),
201 }
202 }
203}
204
205impl From<hyper::Error> for HttpError {
207 fn from(err: hyper::Error) -> Self {
208 HttpError::InternalError {
209 message: format!("Hyper error: {}", err),
210 }
211 }
212}
213
214impl From<serde_json::Error> for HttpError {
216 fn from(err: serde_json::Error) -> Self {
217 HttpError::InternalError {
218 message: format!("JSON serialization error: {}", err),
219 }
220 }
221}
222
223#[cfg(feature = "orm")]
225impl From<orm::ModelError> for HttpError {
226 fn from(err: orm::ModelError) -> Self {
227 match err {
228 orm::ModelError::NotFound(table) => HttpError::NotFound { resource: table },
229 orm::ModelError::Validation(msg) => HttpError::ValidationError { message: msg },
230 orm::ModelError::Database(msg) => HttpError::DatabaseError { message: msg },
231 orm::ModelError::Connection(msg) => HttpError::DatabaseError {
232 message: format!("Connection error: {}", msg),
233 },
234 orm::ModelError::Transaction(msg) => HttpError::DatabaseError {
235 message: format!("Transaction error: {}", msg),
236 },
237 orm::ModelError::Query(msg) => HttpError::BadRequest {
238 message: format!("Query error: {}", msg),
239 },
240 orm::ModelError::Schema(msg) => HttpError::InternalError {
241 message: format!("Schema error: {}", msg),
242 },
243 orm::ModelError::Migration(msg) => HttpError::InternalError {
244 message: format!("Migration error: {}", msg),
245 },
246 orm::ModelError::MissingPrimaryKey => HttpError::BadRequest {
247 message: "Missing or invalid primary key".to_string(),
248 },
249 orm::ModelError::Relationship(msg) => HttpError::BadRequest {
250 message: format!("Relationship error: {}", msg),
251 },
252 orm::ModelError::Serialization(msg) => HttpError::InternalError {
253 message: format!("Serialization error: {}", msg),
254 },
255 orm::ModelError::Event(msg) => HttpError::InternalError {
256 message: format!("Event error: {}", msg),
257 },
258 }
259 }
260}
261
262#[cfg(feature = "orm")]
264impl From<orm::QueryError> for HttpError {
265 fn from(err: orm::QueryError) -> Self {
266 match err {
267 orm::QueryError::InvalidSql(msg) => HttpError::BadRequest {
268 message: format!("Invalid SQL query: {}", msg),
269 },
270 orm::QueryError::MissingFields(msg) => HttpError::BadRequest {
271 message: format!("Missing required fields: {}", msg),
272 },
273 orm::QueryError::InvalidParameter(msg) => HttpError::BadRequest {
274 message: format!("Invalid query parameter: {}", msg),
275 },
276 orm::QueryError::UnsupportedOperation(msg) => HttpError::BadRequest {
277 message: format!("Unsupported operation: {}", msg),
278 },
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_error_creation() {
289 let error = HttpError::startup("Failed to bind to port");
290 assert!(matches!(error, HttpError::StartupFailed { .. }));
291 assert_eq!(error.error_code(), "SERVER_STARTUP_FAILED");
292 }
293
294 #[test]
295 fn test_error_codes() {
296 assert_eq!(HttpError::bad_request("test").error_code(), "BAD_REQUEST");
297 assert_eq!(HttpError::RequestTimeout.error_code(), "REQUEST_TIMEOUT");
298 assert_eq!(HttpError::internal("test").error_code(), "INTERNAL_ERROR");
299 }
300
301 #[test]
302 fn test_config_error_conversion() {
303 let config_error = elif_core::ConfigError::validation_failed("Test validation error");
304 let http_error = HttpError::from(config_error);
305 assert!(matches!(http_error, HttpError::ConfigError { .. }));
306 }
307
308 #[test]
309 fn test_io_error_conversion() {
310 let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
311 let http_error = HttpError::from(io_error);
312 assert!(matches!(http_error, HttpError::InternalError { .. }));
313 }
314
315 #[test]
316 fn test_validation_error_creation() {
317 let validation_error = HttpError::validation_error("Field is required");
318 assert_eq!(validation_error.error_code(), "VALIDATION_ERROR");
319 }
320}