1use crate::HttpStatus;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
7pub enum Error {
8 #[error("HTTP error: {0}")]
9 Http(String),
10
11 #[error(
12 "Route not found: '{0}'. Check your controller paths and ensure the route is registered."
13 )]
14 RouteNotFound(String),
15
16 #[error(
17 "Method {0} not allowed. Verify the HTTP method matches your route definition (#[get], #[post], etc.)."
18 )]
19 MethodNotAllowed(String),
20
21 #[error(
22 "Dependency injection error: {0}. Ensure all dependencies are registered with the container."
23 )]
24 DependencyInjection(String),
25
26 #[error(
27 "Provider not found: '{0}'. Did you forget to register it? Use container.register() or add it to your module's providers()."
28 )]
29 ProviderNotFound(String),
30
31 #[error("Serialization error: {0}. Ensure your type implements Serialize correctly.")]
32 Serialization(String),
33
34 #[error("Deserialization error: {0}. Check that the request body matches the expected format.")]
35 Deserialization(String),
36
37 #[error("Validation error: {0}")]
38 Validation(String),
39
40 #[error("Internal server error: {0}. Check server logs for details.")]
41 Internal(String),
42
43 #[error("Forbidden: {0}. User lacks required permissions for this resource.")]
44 Forbidden(String),
45
46 #[error("IO error: {0}")]
47 Io(#[from] std::io::Error),
48
49 #[error("Bad Request: {0}. Check the request parameters and body format.")]
51 BadRequest(String),
52
53 #[error(
54 "Unauthorized: {0}. Include valid authentication credentials (e.g., Bearer token in Authorization header)."
55 )]
56 Unauthorized(String),
57
58 #[error("Payment Required: {0}")]
59 PaymentRequired(String),
60
61 #[error("Not Found: {0}. Verify the resource exists and the URL is correct.")]
62 NotFound(String),
63
64 #[error("Not Acceptable: {0}. Check the Accept header matches available response formats.")]
65 NotAcceptable(String),
66
67 #[error("Proxy Authentication Required: {0}")]
68 ProxyAuthenticationRequired(String),
69
70 #[error("Request Timeout: {0}")]
71 RequestTimeout(String),
72
73 #[error("Conflict: {0}")]
74 Conflict(String),
75
76 #[error("Gone: {0}")]
77 Gone(String),
78
79 #[error("Length Required: {0}")]
80 LengthRequired(String),
81
82 #[error("Precondition Failed: {0}")]
83 PreconditionFailed(String),
84
85 #[error(
86 "Payload Too Large: {0}. Reduce the request body size or increase the server's body_limit."
87 )]
88 PayloadTooLarge(String),
89
90 #[error("URI Too Long: {0}. Use POST with a request body instead of query parameters.")]
91 UriTooLong(String),
92
93 #[error(
94 "Unsupported Media Type: {0}. Set Content-Type header to a supported format (e.g., application/json)."
95 )]
96 UnsupportedMediaType(String),
97
98 #[error("Range Not Satisfiable: {0}")]
99 RangeNotSatisfiable(String),
100
101 #[error("Expectation Failed: {0}")]
102 ExpectationFailed(String),
103
104 #[error("I'm a teapot: {0}")]
105 ImATeapot(String),
106
107 #[error("Misdirected Request: {0}")]
108 MisdirectedRequest(String),
109
110 #[error("Unprocessable Entity: {0}")]
111 UnprocessableEntity(String),
112
113 #[error("Locked: {0}")]
114 Locked(String),
115
116 #[error("Failed Dependency: {0}")]
117 FailedDependency(String),
118
119 #[error("Too Early: {0}")]
120 TooEarly(String),
121
122 #[error("Upgrade Required: {0}")]
123 UpgradeRequired(String),
124
125 #[error("Precondition Required: {0}")]
126 PreconditionRequired(String),
127
128 #[error(
129 "Too Many Requests: {0}. Rate limit exceeded. Wait before retrying or reduce request frequency."
130 )]
131 TooManyRequests(String),
132
133 #[error("Request Header Fields Too Large: {0}")]
134 RequestHeaderFieldsTooLarge(String),
135
136 #[error("Unavailable For Legal Reasons: {0}")]
137 UnavailableForLegalReasons(String),
138
139 #[error("Not Implemented: {0}. This feature is not yet available.")]
141 NotImplemented(String),
142
143 #[error("Bad Gateway: {0}. The upstream server returned an invalid response.")]
144 BadGateway(String),
145
146 #[error(
147 "Service Unavailable: {0}. Server is temporarily unable to handle requests. Try again later."
148 )]
149 ServiceUnavailable(String),
150
151 #[error("Gateway Timeout: {0}. The upstream server did not respond in time.")]
152 GatewayTimeout(String),
153
154 #[error("HTTP Version Not Supported: {0}")]
155 HttpVersionNotSupported(String),
156
157 #[error("Variant Also Negotiates: {0}")]
158 VariantAlsoNegotiates(String),
159
160 #[error("Insufficient Storage: {0}")]
161 InsufficientStorage(String),
162
163 #[error("Loop Detected: {0}")]
164 LoopDetected(String),
165
166 #[error("Not Extended: {0}")]
167 NotExtended(String),
168
169 #[error("Network Authentication Required: {0}")]
170 NetworkAuthenticationRequired(String),
171}
172
173impl Error {
174 pub fn status_code(&self) -> u16 {
176 match self {
177 Error::RouteNotFound(_) => HttpStatus::NotFound.code(),
179 Error::MethodNotAllowed(_) => HttpStatus::MethodNotAllowed.code(),
180 Error::Validation(_) => HttpStatus::BadRequest.code(),
181 Error::Deserialization(_) => HttpStatus::BadRequest.code(),
182 Error::Forbidden(_) => HttpStatus::Forbidden.code(),
183
184 Error::BadRequest(_) => HttpStatus::BadRequest.code(),
186 Error::Unauthorized(_) => HttpStatus::Unauthorized.code(),
187 Error::PaymentRequired(_) => HttpStatus::PaymentRequired.code(),
188 Error::NotFound(_) => HttpStatus::NotFound.code(),
189 Error::NotAcceptable(_) => HttpStatus::NotAcceptable.code(),
190 Error::ProxyAuthenticationRequired(_) => HttpStatus::ProxyAuthenticationRequired.code(),
191 Error::RequestTimeout(_) => HttpStatus::RequestTimeout.code(),
192 Error::Conflict(_) => HttpStatus::Conflict.code(),
193 Error::Gone(_) => HttpStatus::Gone.code(),
194 Error::LengthRequired(_) => HttpStatus::LengthRequired.code(),
195 Error::PreconditionFailed(_) => HttpStatus::PreconditionFailed.code(),
196 Error::PayloadTooLarge(_) => HttpStatus::PayloadTooLarge.code(),
197 Error::UriTooLong(_) => HttpStatus::UriTooLong.code(),
198 Error::UnsupportedMediaType(_) => HttpStatus::UnsupportedMediaType.code(),
199 Error::RangeNotSatisfiable(_) => HttpStatus::RangeNotSatisfiable.code(),
200 Error::ExpectationFailed(_) => HttpStatus::ExpectationFailed.code(),
201 Error::ImATeapot(_) => HttpStatus::ImATeapot.code(),
202 Error::MisdirectedRequest(_) => HttpStatus::MisdirectedRequest.code(),
203 Error::UnprocessableEntity(_) => HttpStatus::UnprocessableEntity.code(),
204 Error::Locked(_) => HttpStatus::Locked.code(),
205 Error::FailedDependency(_) => HttpStatus::FailedDependency.code(),
206 Error::TooEarly(_) => HttpStatus::TooEarly.code(),
207 Error::UpgradeRequired(_) => HttpStatus::UpgradeRequired.code(),
208 Error::PreconditionRequired(_) => HttpStatus::PreconditionRequired.code(),
209 Error::TooManyRequests(_) => HttpStatus::TooManyRequests.code(),
210 Error::RequestHeaderFieldsTooLarge(_) => HttpStatus::RequestHeaderFieldsTooLarge.code(),
211 Error::UnavailableForLegalReasons(_) => HttpStatus::UnavailableForLegalReasons.code(),
212
213 Error::NotImplemented(_) => HttpStatus::NotImplemented.code(),
215 Error::BadGateway(_) => HttpStatus::BadGateway.code(),
216 Error::ServiceUnavailable(_) => HttpStatus::ServiceUnavailable.code(),
217 Error::GatewayTimeout(_) => HttpStatus::GatewayTimeout.code(),
218 Error::HttpVersionNotSupported(_) => HttpStatus::HttpVersionNotSupported.code(),
219 Error::VariantAlsoNegotiates(_) => HttpStatus::VariantAlsoNegotiates.code(),
220 Error::InsufficientStorage(_) => HttpStatus::InsufficientStorage.code(),
221 Error::LoopDetected(_) => HttpStatus::LoopDetected.code(),
222 Error::NotExtended(_) => HttpStatus::NotExtended.code(),
223 Error::NetworkAuthenticationRequired(_) => {
224 HttpStatus::NetworkAuthenticationRequired.code()
225 }
226
227 _ => HttpStatus::InternalServerError.code(),
229 }
230 }
231
232 pub fn http_status(&self) -> HttpStatus {
234 HttpStatus::from_code(self.status_code()).unwrap_or(HttpStatus::InternalServerError)
235 }
236
237 pub fn is_client_error(&self) -> bool {
239 self.http_status().is_client_error()
240 }
241
242 pub fn is_server_error(&self) -> bool {
244 self.http_status().is_server_error()
245 }
246
247 pub fn bad_request(msg: impl Into<String>) -> Self {
253 Self::BadRequest(msg.into())
254 }
255
256 pub fn unauthorized(msg: impl Into<String>) -> Self {
258 Self::Unauthorized(msg.into())
259 }
260
261 pub fn forbidden(msg: impl Into<String>) -> Self {
263 Self::Forbidden(msg.into())
264 }
265
266 pub fn not_found(msg: impl Into<String>) -> Self {
268 Self::NotFound(msg.into())
269 }
270
271 pub fn conflict(msg: impl Into<String>) -> Self {
273 Self::Conflict(msg.into())
274 }
275
276 pub fn internal(msg: impl Into<String>) -> Self {
278 Self::Internal(msg.into())
279 }
280
281 pub fn validation(msg: impl Into<String>) -> Self {
283 Self::Validation(msg.into())
284 }
285
286 pub fn timeout(msg: impl Into<String>) -> Self {
288 Self::RequestTimeout(msg.into())
289 }
290
291 pub fn rate_limited(msg: impl Into<String>) -> Self {
293 Self::TooManyRequests(msg.into())
294 }
295
296 pub fn unavailable(msg: impl Into<String>) -> Self {
298 Self::ServiceUnavailable(msg.into())
299 }
300
301 pub fn help(&self) -> Option<&'static str> {
303 match self {
304 Error::ProviderNotFound(_) => Some(
305 "Make sure to:\n\
306 1. Add the provider to your module's providers() method\n\
307 2. Or register it directly: container.register(MyService::new())\n\
308 3. Check that the type matches exactly (including generics)",
309 ),
310 Error::RouteNotFound(_) => Some(
311 "Check that:\n\
312 1. The route is registered in a controller\n\
313 2. The controller is added to a module\n\
314 3. The module is imported into your app module\n\
315 4. The HTTP method matches (GET, POST, etc.)",
316 ),
317 Error::Deserialization(_) => Some(
318 "Verify that:\n\
319 1. The request body is valid JSON\n\
320 2. Field names match your struct (check #[serde(rename)] attributes)\n\
321 3. Data types match (strings vs numbers, etc.)\n\
322 4. Required fields are present",
323 ),
324 Error::Unauthorized(_) => Some(
325 "To authenticate:\n\
326 1. Include 'Authorization: Bearer <token>' header\n\
327 2. Ensure the token is not expired\n\
328 3. Check that the token has the required scopes",
329 ),
330 Error::TooManyRequests(_) => Some(
331 "To resolve rate limiting:\n\
332 1. Wait for the retry-after duration\n\
333 2. Reduce request frequency\n\
334 3. Check the X-RateLimit-* headers for limits",
335 ),
336 _ => None,
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn test_internal_error_status() {
347 let err = Error::Internal("test".to_string());
348 assert_eq!(err.status_code(), 500);
349 assert!(err.is_server_error());
350 assert!(!err.is_client_error());
351 }
352
353 #[test]
354 fn test_not_found_error() {
355 let err = Error::NotFound("resource".to_string());
356 assert_eq!(err.status_code(), 404);
357 assert!(err.is_client_error());
358 assert!(!err.is_server_error());
359 }
360
361 #[test]
362 fn test_unauthorized_error() {
363 let err = Error::Unauthorized("auth required".to_string());
364 assert_eq!(err.status_code(), 401);
365 assert!(err.is_client_error());
366 }
367
368 #[test]
369 fn test_forbidden_error() {
370 let err = Error::Forbidden("access denied".to_string());
371 assert_eq!(err.status_code(), 403);
372 assert!(err.is_client_error());
373 }
374
375 #[test]
376 fn test_bad_request_error() {
377 let err = Error::BadRequest("invalid input".to_string());
378 assert_eq!(err.status_code(), 400);
379 }
380
381 #[test]
382 fn test_conflict_error() {
383 let err = Error::Conflict("resource conflict".to_string());
384 assert_eq!(err.status_code(), 409);
385 }
386
387 #[test]
388 fn test_gone_error() {
389 let err = Error::Gone("resource deleted".to_string());
390 assert_eq!(err.status_code(), 410);
391 }
392
393 #[test]
394 fn test_payload_too_large() {
395 let err = Error::PayloadTooLarge("file too big".to_string());
396 assert_eq!(err.status_code(), 413);
397 }
398
399 #[test]
400 fn test_unsupported_media_type() {
401 let err = Error::UnsupportedMediaType("invalid content-type".to_string());
402 assert_eq!(err.status_code(), 415);
403 }
404
405 #[test]
406 fn test_too_many_requests() {
407 let err = Error::TooManyRequests("rate limited".to_string());
408 assert_eq!(err.status_code(), 429);
409 }
410
411 #[test]
412 fn test_not_implemented() {
413 let err = Error::NotImplemented("feature not ready".to_string());
414 assert_eq!(err.status_code(), 501);
415 assert!(err.is_server_error());
416 }
417
418 #[test]
419 fn test_bad_gateway() {
420 let err = Error::BadGateway("upstream error".to_string());
421 assert_eq!(err.status_code(), 502);
422 }
423
424 #[test]
425 fn test_service_unavailable() {
426 let err = Error::ServiceUnavailable("maintenance".to_string());
427 assert_eq!(err.status_code(), 503);
428 }
429
430 #[test]
431 fn test_gateway_timeout() {
432 let err = Error::GatewayTimeout("upstream timeout".to_string());
433 assert_eq!(err.status_code(), 504);
434 }
435
436 #[test]
437 fn test_method_not_allowed() {
438 let err = Error::MethodNotAllowed("POST not allowed".to_string());
439 assert_eq!(err.status_code(), 405);
440 }
441
442 #[test]
443 fn test_not_acceptable() {
444 let err = Error::NotAcceptable("format not supported".to_string());
445 assert_eq!(err.status_code(), 406);
446 }
447
448 #[test]
449 fn test_request_timeout() {
450 let err = Error::RequestTimeout("request took too long".to_string());
451 assert_eq!(err.status_code(), 408);
452 }
453
454 #[test]
455 fn test_unprocessable_entity() {
456 let err = Error::UnprocessableEntity("validation failed".to_string());
457 assert_eq!(err.status_code(), 422);
458 }
459
460 #[test]
461 fn test_locked() {
462 let err = Error::Locked("resource locked".to_string());
463 assert_eq!(err.status_code(), 423);
464 }
465
466 #[test]
467 fn test_upgrade_required() {
468 let err = Error::UpgradeRequired("http/2 required".to_string());
469 assert_eq!(err.status_code(), 426);
470 }
471
472 #[test]
473 fn test_precondition_required() {
474 let err = Error::PreconditionRequired("if-match required".to_string());
475 assert_eq!(err.status_code(), 428);
476 }
477
478 #[test]
479 fn test_http_status_conversion() {
480 let err = Error::NotFound("test".to_string());
481 let status = err.http_status();
482 assert_eq!(status, HttpStatus::NotFound);
483 }
484
485 #[test]
486 fn test_error_display() {
487 let err = Error::Internal("something went wrong".to_string());
488 let display = format!("{}", err);
489 assert!(display.contains("something went wrong"));
490 }
491
492 #[test]
493 fn test_io_error_conversion() {
494 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
495 let err: Error = io_err.into();
496 assert!(matches!(err, Error::Io(_)));
497 }
498
499 #[test]
500 fn test_serialization_error() {
501 let err = Error::Serialization("failed to serialize".to_string());
502 assert!(format!("{}", err).contains("Serialization"));
503 }
504
505 #[test]
506 fn test_deserialization_error() {
507 let err = Error::Deserialization("failed to deserialize".to_string());
508 assert!(format!("{}", err).contains("Deserialization"));
509 }
510
511 #[test]
512 fn test_validation_error() {
513 let err = Error::Validation("validation failed".to_string());
514 assert!(format!("{}", err).contains("Validation"));
515 }
516
517 #[test]
518 fn test_http_error() {
519 let err = Error::Http("http error".to_string());
520 assert!(format!("{}", err).contains("HTTP error"));
521 }
522
523 #[test]
524 fn test_route_not_found_error() {
525 let err = Error::RouteNotFound("/api/users".to_string());
526 assert!(format!("{}", err).contains("Route not found"));
527 }
528
529 #[test]
530 fn test_im_a_teapot() {
531 let err = Error::ImATeapot("I'm a teapot".to_string());
532 assert_eq!(err.status_code(), 418);
533 }
534
535 #[test]
536 fn test_misdirected_request() {
537 let err = Error::MisdirectedRequest("wrong server".to_string());
538 assert_eq!(err.status_code(), 421);
539 }
540
541 #[test]
542 fn test_failed_dependency() {
543 let err = Error::FailedDependency("dependent request failed".to_string());
544 assert_eq!(err.status_code(), 424);
545 }
546
547 #[test]
548 fn test_too_early() {
549 let err = Error::TooEarly("request too early".to_string());
550 assert_eq!(err.status_code(), 425);
551 }
552
553 #[test]
554 fn test_request_header_fields_too_large() {
555 let err = Error::RequestHeaderFieldsTooLarge("headers too big".to_string());
556 assert_eq!(err.status_code(), 431);
557 }
558
559 #[test]
560 fn test_unavailable_for_legal_reasons() {
561 let err = Error::UnavailableForLegalReasons("blocked by law".to_string());
562 assert_eq!(err.status_code(), 451);
563 }
564
565 #[test]
566 fn test_http_version_not_supported() {
567 let err = Error::HttpVersionNotSupported("http/0.9 not supported".to_string());
568 assert_eq!(err.status_code(), 505);
569 }
570
571 #[test]
572 fn test_variant_also_negotiates() {
573 let err = Error::VariantAlsoNegotiates("circular reference".to_string());
574 assert_eq!(err.status_code(), 506);
575 }
576
577 #[test]
578 fn test_insufficient_storage() {
579 let err = Error::InsufficientStorage("disk full".to_string());
580 assert_eq!(err.status_code(), 507);
581 }
582
583 #[test]
584 fn test_loop_detected() {
585 let err = Error::LoopDetected("infinite loop".to_string());
586 assert_eq!(err.status_code(), 508);
587 }
588
589 #[test]
590 fn test_not_extended() {
591 let err = Error::NotExtended("extension required".to_string());
592 assert_eq!(err.status_code(), 510);
593 }
594
595 #[test]
596 fn test_network_authentication_required() {
597 let err = Error::NetworkAuthenticationRequired("proxy auth required".to_string());
598 assert_eq!(err.status_code(), 511);
599 }
600
601 #[test]
602 fn test_length_required() {
603 let err = Error::LengthRequired("content-length missing".to_string());
604 assert_eq!(err.status_code(), 411);
605 }
606
607 #[test]
608 fn test_precondition_failed() {
609 let err = Error::PreconditionFailed("if-match failed".to_string());
610 assert_eq!(err.status_code(), 412);
611 }
612
613 #[test]
614 fn test_uri_too_long() {
615 let err = Error::UriTooLong("url too long".to_string());
616 assert_eq!(err.status_code(), 414);
617 }
618
619 #[test]
620 fn test_range_not_satisfiable() {
621 let err = Error::RangeNotSatisfiable("invalid range".to_string());
622 assert_eq!(err.status_code(), 416);
623 }
624
625 #[test]
626 fn test_expectation_failed() {
627 let err = Error::ExpectationFailed("expect header failed".to_string());
628 assert_eq!(err.status_code(), 417);
629 }
630
631 #[test]
632 fn test_proxy_authentication_required() {
633 let err = Error::ProxyAuthenticationRequired("proxy auth needed".to_string());
634 assert_eq!(err.status_code(), 407);
635 }
636
637 #[test]
638 fn test_client_error_range() {
639 for code in 400..500 {
640 if let Some(_status) = HttpStatus::from_code(code) {
641 let err = Error::BadRequest("test".to_string());
642 if err.status_code() == code {
643 assert!(err.is_client_error());
644 assert!(!err.is_server_error());
645 }
646 }
647 }
648 }
649
650 #[test]
651 fn test_server_error_range() {
652 for code in 500..600 {
653 if let Some(_status) = HttpStatus::from_code(code) {
654 let err = Error::Internal("test".to_string());
655 if err.status_code() == code {
656 assert!(err.is_server_error());
657 assert!(!err.is_client_error());
658 }
659 }
660 }
661 }
662}