1pub use int_enum::IntEnum;
5use std::error::Error;
6use std::fmt::{Debug, Display, Error as FmtError, Formatter};
7use std::sync::PoisonError;
8use std::time::SystemTimeError;
9use url::ParseError;
10
11#[cfg(feature = "io")]
12use tokio::sync::mpsc::error::SendError;
13
14#[repr(i16)]
16#[derive(Debug, PartialEq, PartialOrd, Copy, Clone, IntEnum)]
17pub enum ErrorCode {
18 InvalidRequest = -6, Interrupt = -5, UrlParseError = -4,
21 ConnectionError = -3,
22 Timeout = -2,
23 Unknown = -1,
24
25 Continue = 100,
26 OK = 200,
27 Created = 201,
28 Accepted = 202,
29 NoContent = 204,
30 BadRequest = 400,
31 Unauthorized = 401,
32 Forbidden = 403,
33 NotFound = 404,
34 MethodNotAllowed = 405,
35 NotAcceptable = 406,
36 RequestTimeout = 408,
37 Conflict = 409,
38 Gone = 410,
39 LengthRequired = 411,
40 PreconditionFailed = 412,
41 PayloadTooLarge = 413,
42 URITooLong = 414,
43 UnsupportedMediaType = 415,
44 RangeNotSatisfiable = 416,
45 ExpectationFailed = 417,
46 ImATeapot = 418,
47 MisdirectedRequest = 421,
48 UnprocessableEntity = 422,
49 Locked = 423,
50 FailedDependency = 424,
51 TooEarly = 425,
52 UpgradeRequired = 426,
53 PreconditionRequired = 428,
54 TooManyRequests = 429,
55 RequestHeaderFieldsTooLarge = 431,
56 UnavailableForLegalReasons = 451,
57 InternalServerError = 500,
58 NotImplemented = 501,
59 BadGateway = 502,
60 ServiceUnavailable = 503,
61 GatewayTimeout = 504,
62 HTTPVersionNotSupported = 505,
63 VariantAlsoNegotiates = 506,
64 InsufficientStorage = 507,
65 LoopDetected = 508,
66 NotExtended = 510,
67 NetworkAuthenticationRequired = 511,
68}
69
70#[derive(PartialEq, Debug, Clone)]
72pub struct ReductError<Original = ()> {
73 pub status: ErrorCode,
75
76 pub message: String,
78
79 pub source: Option<Original>,
81}
82
83impl<Original> ReductError<Original> {
84 pub fn without_source(self) -> ReductError {
86 ReductError {
87 status: self.status,
88 message: self.message,
89 source: None,
90 }
91 }
92
93 pub fn with_source<T>(self, source: T) -> ReductError<T> {
94 ReductError {
95 status: self.status,
96 message: self.message,
97 source: Some(source),
98 }
99 }
100}
101
102impl<Original> Display for ReductError<Original> {
103 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
104 write!(f, "[{:?}] {}", self.status, self.message)
105 }
106}
107
108impl Display for ErrorCode {
109 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
110 write!(f, "{}", i16::from(*self))
111 }
112}
113
114impl From<std::io::Error> for ReductError {
115 fn from(err: std::io::Error) -> Self {
116 let status = match err.kind() {
117 std::io::ErrorKind::Unsupported => ErrorCode::MethodNotAllowed,
118 _ => ErrorCode::InternalServerError,
119 };
120
121 ReductError {
122 status,
123 message: err.to_string(),
124 source: None,
125 }
126 }
127}
128
129impl From<ParseError> for ReductError {
130 fn from(err: ParseError) -> Self {
131 ReductError {
133 status: ErrorCode::UrlParseError,
134 message: err.to_string(),
135 source: None,
136 }
137 }
138}
139
140impl From<SystemTimeError> for ReductError {
141 fn from(err: SystemTimeError) -> Self {
142 ReductError {
144 status: ErrorCode::InternalServerError,
145 message: err.to_string(),
146 source: None,
147 }
148 }
149}
150
151impl From<SystemTimeError> for ReductError<SystemTimeError> {
152 fn from(err: SystemTimeError) -> Self {
153 ReductError {
155 status: ErrorCode::InternalServerError,
156 message: err.to_string(),
157 source: Some(err),
158 }
159 }
160}
161
162impl From<ParseError> for ReductError<ParseError> {
163 fn from(err: ParseError) -> Self {
164 ReductError {
166 status: ErrorCode::UrlParseError,
167 message: err.to_string(),
168 source: Some(err),
169 }
170 }
171}
172
173impl<T> From<PoisonError<T>> for ReductError {
174 fn from(_: PoisonError<T>) -> Self {
175 ReductError {
177 status: ErrorCode::InternalServerError,
178 message: "Poison error".to_string(),
179 source: None,
180 }
181 }
182}
183
184impl<T> From<PoisonError<T>> for ReductError<PoisonError<T>> {
185 fn from(err: PoisonError<T>) -> Self {
186 ReductError {
188 status: ErrorCode::InternalServerError,
189 message: "Poison error".to_string(),
190 source: Some(err),
191 }
192 }
193}
194
195impl From<Box<dyn std::any::Any + Send>> for ReductError {
196 fn from(err: Box<dyn std::any::Any + Send>) -> Self {
197 ReductError {
199 status: ErrorCode::InternalServerError,
200 message: format!("{:?}", err),
201 source: None,
202 }
203 }
204}
205
206#[cfg(feature = "io")]
207impl<T> From<SendError<T>> for ReductError {
208 fn from(err: SendError<T>) -> Self {
209 ReductError {
211 status: ErrorCode::InternalServerError,
212 message: err.to_string(),
213 source: None,
214 }
215 }
216}
217
218#[cfg(feature = "io")]
219impl<T> From<SendError<T>> for ReductError<SendError<T>> {
220 fn from(err: SendError<T>) -> Self {
221 ReductError {
223 status: ErrorCode::InternalServerError,
224 message: err.to_string(),
225 source: Some(err),
226 }
227 }
228}
229
230impl Error for ReductError {
231 fn description(&self) -> &str {
232 &self.message
233 }
234}
235
236impl ReductError {
237 pub fn new(status: ErrorCode, message: &str) -> Self {
238 ReductError {
239 status,
240 message: message.to_string(),
241 source: None,
242 }
243 }
244
245 pub fn status(&self) -> ErrorCode {
246 self.status
247 }
248
249 pub fn message(&self) -> &str {
250 &self.message
251 }
252
253 pub fn ok() -> ReductError {
254 ReductError {
255 status: ErrorCode::OK,
256 message: "".to_string(),
257 source: None,
258 }
259 }
260
261 pub fn timeout(msg: &str) -> ReductError {
262 ReductError {
263 status: ErrorCode::Timeout,
264 message: msg.to_string(),
265 source: None,
266 }
267 }
268
269 pub fn no_content(msg: &str) -> ReductError {
271 ReductError {
272 status: ErrorCode::NoContent,
273 message: msg.to_string(),
274 source: None,
275 }
276 }
277
278 pub fn not_found(msg: &str) -> ReductError {
280 ReductError {
281 status: ErrorCode::NotFound,
282 message: msg.to_string(),
283 source: None,
284 }
285 }
286
287 pub fn conflict(msg: &str) -> ReductError {
289 ReductError {
290 status: ErrorCode::Conflict,
291 message: msg.to_string(),
292 source: None,
293 }
294 }
295
296 pub fn bad_request(msg: &str) -> ReductError {
298 ReductError {
299 status: ErrorCode::BadRequest,
300 message: msg.to_string(),
301 source: None,
302 }
303 }
304
305 pub fn unauthorized(msg: &str) -> ReductError {
307 ReductError {
308 status: ErrorCode::Unauthorized,
309 message: msg.to_string(),
310 source: None,
311 }
312 }
313
314 pub fn forbidden(msg: &str) -> ReductError {
316 ReductError {
317 status: ErrorCode::Forbidden,
318 message: msg.to_string(),
319 source: None,
320 }
321 }
322
323 pub fn unprocessable_entity(msg: &str) -> ReductError {
325 ReductError {
326 status: ErrorCode::UnprocessableEntity,
327 message: msg.to_string(),
328 source: None,
329 }
330 }
331
332 pub fn too_early(msg: &str) -> ReductError {
334 ReductError {
335 status: ErrorCode::TooEarly,
336 message: msg.to_string(),
337 source: None,
338 }
339 }
340
341 pub fn internal_server_error(msg: &str) -> ReductError {
343 ReductError {
344 status: ErrorCode::InternalServerError,
345 message: msg.to_string(),
346 source: None,
347 }
348 }
349}
350
351#[macro_export]
353macro_rules! ok {
354 () => {
355 ReductError::ok()
356 };
357}
358
359#[macro_export]
360macro_rules! timeout {
361 ($msg:expr, $($arg:tt)*) => {
362 ReductError::timeout(&format!($msg, $($arg)*))
363 };
364 ($msg:expr) => {
365 ReductError::timeout($msg)
366 };
367}
368
369#[macro_export]
370macro_rules! no_content {
371 ($msg:expr, $($arg:tt)*) => {
372 ReductError::no_content(&format!($msg, $($arg)*))
373 };
374 ($msg:expr) => {
375 ReductError::no_content($msg)
376 };
377}
378
379#[macro_export]
380macro_rules! bad_request {
381 ($msg:expr, $($arg:tt)*) => {
382 ReductError::bad_request(&format!($msg, $($arg)*))
383 };
384 ($msg:expr) => {
385 ReductError::bad_request($msg)
386 };
387}
388
389#[macro_export]
390macro_rules! forbidden {
391 ($msg:expr, $($arg:tt)*) => {
392 ReductError::forbidden(&format!($msg, $($arg)*))
393 };
394 ($msg:expr) => {
395 ReductError::forbidden($msg)
396 };
397}
398
399#[macro_export]
400macro_rules! unprocessable_entity {
401 ($msg:expr, $($arg:tt)*) => {
402 ReductError::unprocessable_entity(&format!($msg, $($arg)*))
403 };
404 ($msg:expr) => {
405 ReductError::unprocessable_entity($msg)
406 };
407}
408
409#[macro_export]
410macro_rules! not_found {
411 ($msg:expr, $($arg:tt)*) => {
412 ReductError::not_found(&format!($msg, $($arg)*))
413 };
414 ($msg:expr) => {
415 ReductError::not_found($msg)
416 };
417}
418#[macro_export]
419macro_rules! conflict {
420 ($msg:expr, $($arg:tt)*) => {
421 ReductError::conflict(&format!($msg, $($arg)*))
422 };
423 ($msg:expr) => {
424 ReductError::conflict($msg)
425 };
426}
427
428#[macro_export]
429macro_rules! too_early {
430 ($msg:expr, $($arg:tt)*) => {
431 ReductError::too_early(&format!($msg, $($arg)*))
432 };
433 ($msg:expr) => {
434 ReductError::too_early($msg)
435 };
436}
437
438#[macro_export]
439macro_rules! internal_server_error {
440 ($msg:expr, $($arg:tt)*) => {
441 ReductError::internal_server_error(&format!($msg, $($arg)*))
442 };
443 ($msg:expr) => {
444 ReductError::internal_server_error($msg)
445 };
446}
447
448#[macro_export]
449macro_rules! unauthorized {
450 ($msg:expr, $($arg:tt)*) => {
451 ReductError::unauthorized(&format!($msg, $($arg)*))
452 };
453 ($msg:expr) => {
454 ReductError::unauthorized($msg)
455 };
456}
457
458#[macro_export]
459macro_rules! service_unavailable {
460 ($msg:expr, $($arg:tt)*) => {
461 ReductError::new(ErrorCode::ServiceUnavailable, &format!($msg, $($arg)*))
462 };
463 ($msg:expr) => {
464 ReductError::new(ErrorCode::ServiceUnavailable, $msg)
465 };
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use std::fmt;
472 use std::time::{SystemTime, UNIX_EPOCH};
473
474 #[derive(Debug)]
475 struct CustomSource;
476
477 impl fmt::Display for CustomSource {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 write!(f, "custom source")
480 }
481 }
482
483 #[test]
484 fn creates_internal_server_error() {
485 let error = ReductError::internal_server_error("Unexpected server error");
486 assert_eq!(error.status, ErrorCode::InternalServerError);
487 assert_eq!(error.message, "Unexpected server error");
488 }
489
490 #[test]
491 fn converts_io_error_to_reduct_error() {
492 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "IO failure");
493 let error: ReductError = io_error.into();
494 assert_eq!(error.status, ErrorCode::InternalServerError);
495 assert_eq!(error.message, "IO failure");
496 }
497
498 #[test]
499 fn converts_system_time_error_to_reduct_error() {
500 let system_time_error = UNIX_EPOCH.duration_since(SystemTime::now()).unwrap_err();
501 let error: ReductError = system_time_error.into();
502 assert_eq!(error.status, ErrorCode::InternalServerError);
503 assert_eq!(error.message, "second time provided was later than self");
504 assert_eq!(error.source, None);
505 }
506
507 #[test]
508 fn converts_system_time_error_to_typed_reduct_error() {
509 let system_time_error = UNIX_EPOCH.duration_since(SystemTime::now()).unwrap_err();
510 let error: ReductError<SystemTimeError> = system_time_error.into();
511 assert_eq!(error.status, ErrorCode::InternalServerError);
512 assert_eq!(error.message, "second time provided was later than self");
513 assert!(error.source.is_some());
514 }
515
516 #[test]
517 fn converts_url_parse_error_to_reduct_error() {
518 let parse_error = ParseError::EmptyHost;
519 let error: ReductError = parse_error.into();
520 assert_eq!(error.status, ErrorCode::UrlParseError);
521 assert_eq!(error.message, "empty host");
522 assert_eq!(error.source, None);
523 }
524
525 #[test]
526 fn converts_url_parse_error_to_typed_reduct_error() {
527 let parse_error = ParseError::EmptyHost;
528 let error: ReductError<ParseError> = parse_error.into();
529 assert_eq!(error.status, ErrorCode::UrlParseError);
530 assert_eq!(error.message, "empty host");
531 assert_eq!(error.source, Some(ParseError::EmptyHost));
532 }
533
534 #[test]
535 fn converts_poison_error_to_reduct_error() {
536 let poison_error: PoisonError<()> = PoisonError::new(());
537 let error: ReductError = poison_error.into();
538 assert_eq!(error.status, ErrorCode::InternalServerError);
539 assert_eq!(error.message, "Poison error");
540 assert_eq!(error.source, None);
541 }
542
543 #[test]
544 fn converts_poison_error_to_typed_reduct_error() {
545 let poison_error: PoisonError<()> = PoisonError::new(());
546 let error: ReductError<PoisonError<()>> = poison_error.into();
547 assert_eq!(error.status, ErrorCode::InternalServerError);
548 assert_eq!(error.message, "Poison error");
549 assert!(error.source.is_some());
550 }
551
552 #[test]
553 fn converts_boxed_any_to_reduct_error() {
554 let boxed_error: Box<dyn std::any::Any + Send> = Box::new("panic payload");
555 let error: ReductError = boxed_error.into();
556 assert_eq!(error.status, ErrorCode::InternalServerError);
557 assert_eq!(error.source, None);
558 }
559
560 #[test]
561 fn drops_source_from_typed_reduct_error() {
562 let error_with_source = ReductError {
563 status: ErrorCode::BadRequest,
564 message: "bad input".to_string(),
565 source: Some(CustomSource),
566 };
567
568 let error = error_with_source.without_source();
569 assert_eq!(error.status, ErrorCode::BadRequest);
570 assert_eq!(error.message, "bad input");
571 assert_eq!(error.source, None);
572 }
573
574 #[test]
575 fn converts_io_reduct_error_into_plain_error() {
576 let error_with_source = ReductError {
577 status: ErrorCode::InternalServerError,
578 message: "io typed error".to_string(),
579 source: Some(std::io::Error::other("boom")),
580 };
581
582 let error: ReductError = error_with_source.without_source();
583 assert_eq!(error.status, ErrorCode::InternalServerError);
584 assert_eq!(error.message, "io typed error");
585 assert_eq!(error.source, None);
586 }
587
588 #[cfg(feature = "io")]
589 #[test]
590 fn converts_send_error_to_reduct_error() {
591 let send_error: SendError<()> = SendError(());
592 let error: ReductError = send_error.into();
593 assert_eq!(error.status, ErrorCode::InternalServerError);
594 assert_eq!(error.message, "channel closed");
595 assert_eq!(error.source, None);
596 }
597
598 #[cfg(feature = "io")]
599 #[test]
600 fn converts_send_error_to_typed_reduct_error() {
601 let send_error: SendError<()> = SendError(());
602 let error: ReductError<SendError<()>> = send_error.into();
603 assert_eq!(error.status, ErrorCode::InternalServerError);
604 assert_eq!(error.message, "channel closed");
605 assert!(error.source.is_some());
606 }
607
608 mod macros {
609 use super::*;
610
611 #[test]
612 fn test_ok_macro() {
613 let error = ok!();
614 assert_eq!(error.status, ErrorCode::OK);
615 assert_eq!(error.message, "");
616 }
617
618 #[test]
619 fn test_timeout_macro() {
620 let error = timeout!("Timeout error: {}", 42);
621 assert_eq!(error.status, ErrorCode::Timeout);
622 assert_eq!(error.message, "Timeout error: 42");
623 }
624
625 #[test]
626 fn test_no_content_macro() {
627 let error = no_content!("No content error: {}", 42);
628 assert_eq!(error.status, ErrorCode::NoContent);
629 assert_eq!(error.message, "No content error: 42");
630 }
631
632 #[test]
633 fn test_bad_request_macro() {
634 let error = bad_request!("Bad request error: {}", 42);
635 assert_eq!(error.status, ErrorCode::BadRequest);
636 assert_eq!(error.message, "Bad request error: 42");
637 }
638
639 #[test]
640 fn test_unprocessable_entity_macro() {
641 let error = unprocessable_entity!("Unprocessable entity error: {}", 42);
642 assert_eq!(error.status, ErrorCode::UnprocessableEntity);
643 assert_eq!(error.message, "Unprocessable entity error: 42");
644 }
645
646 #[test]
647 fn test_not_found_macro() {
648 let error = not_found!("Not found error: {}", 42);
649 assert_eq!(error.status, ErrorCode::NotFound);
650 assert_eq!(error.message, "Not found error: 42");
651 }
652
653 #[test]
654 fn test_conflict_macro() {
655 let error = conflict!("Conflict error: {}", 42);
656 assert_eq!(error.status, ErrorCode::Conflict);
657 assert_eq!(error.message, "Conflict error: 42");
658 }
659
660 #[test]
661 fn test_too_early_macro() {
662 let error = too_early!("Too early error: {}", 42);
663 assert_eq!(error.status, ErrorCode::TooEarly);
664 assert_eq!(error.message, "Too early error: 42");
665 }
666
667 #[test]
668 fn test_internal_server_error_macro() {
669 let error = internal_server_error!("Internal server error: {}", 42);
670 assert_eq!(error.status, ErrorCode::InternalServerError);
671 assert_eq!(error.message, "Internal server error: 42");
672 }
673 }
674}