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<SystemTimeError> for ReductError<SystemTimeError> {
130 fn from(err: SystemTimeError) -> Self {
131 ReductError {
133 status: ErrorCode::InternalServerError,
134 message: err.to_string(),
135 source: Some(err),
136 }
137 }
138}
139
140impl From<ParseError> for ReductError<ParseError> {
141 fn from(err: ParseError) -> Self {
142 ReductError {
144 status: ErrorCode::UrlParseError,
145 message: err.to_string(),
146 source: Some(err),
147 }
148 }
149}
150
151impl<T> From<PoisonError<T>> for ReductError<PoisonError<T>> {
152 fn from(err: PoisonError<T>) -> Self {
153 ReductError {
155 status: ErrorCode::InternalServerError,
156 message: "Poison error".to_string(),
157 source: Some(err),
158 }
159 }
160}
161
162#[cfg(feature = "io")]
163impl<T> From<SendError<T>> for ReductError<SendError<T>> {
164 fn from(err: SendError<T>) -> Self {
165 ReductError {
167 status: ErrorCode::InternalServerError,
168 message: err.to_string(),
169 source: Some(err),
170 }
171 }
172}
173
174impl Error for ReductError {
175 fn description(&self) -> &str {
176 &self.message
177 }
178}
179
180impl ReductError {
181 pub fn new(status: ErrorCode, message: &str) -> Self {
182 ReductError {
183 status,
184 message: message.to_string(),
185 source: None,
186 }
187 }
188
189 pub fn status(&self) -> ErrorCode {
190 self.status
191 }
192
193 pub fn message(&self) -> &str {
194 &self.message
195 }
196
197 pub fn ok() -> ReductError {
198 ReductError {
199 status: ErrorCode::OK,
200 message: "".to_string(),
201 source: None,
202 }
203 }
204
205 pub fn timeout(msg: &str) -> ReductError {
206 ReductError {
207 status: ErrorCode::Timeout,
208 message: msg.to_string(),
209 source: None,
210 }
211 }
212
213 pub fn no_content(msg: &str) -> ReductError {
215 ReductError {
216 status: ErrorCode::NoContent,
217 message: msg.to_string(),
218 source: None,
219 }
220 }
221
222 pub fn not_found(msg: &str) -> ReductError {
224 ReductError {
225 status: ErrorCode::NotFound,
226 message: msg.to_string(),
227 source: None,
228 }
229 }
230
231 pub fn conflict(msg: &str) -> ReductError {
233 ReductError {
234 status: ErrorCode::Conflict,
235 message: msg.to_string(),
236 source: None,
237 }
238 }
239
240 pub fn bad_request(msg: &str) -> ReductError {
242 ReductError {
243 status: ErrorCode::BadRequest,
244 message: msg.to_string(),
245 source: None,
246 }
247 }
248
249 pub fn unauthorized(msg: &str) -> ReductError {
251 ReductError {
252 status: ErrorCode::Unauthorized,
253 message: msg.to_string(),
254 source: None,
255 }
256 }
257
258 pub fn forbidden(msg: &str) -> ReductError {
260 ReductError {
261 status: ErrorCode::Forbidden,
262 message: msg.to_string(),
263 source: None,
264 }
265 }
266
267 pub fn unprocessable_entity(msg: &str) -> ReductError {
269 ReductError {
270 status: ErrorCode::UnprocessableEntity,
271 message: msg.to_string(),
272 source: None,
273 }
274 }
275
276 pub fn too_early(msg: &str) -> ReductError {
278 ReductError {
279 status: ErrorCode::TooEarly,
280 message: msg.to_string(),
281 source: None,
282 }
283 }
284
285 pub fn internal_server_error(msg: &str) -> ReductError {
287 ReductError {
288 status: ErrorCode::InternalServerError,
289 message: msg.to_string(),
290 source: None,
291 }
292 }
293}
294
295#[macro_export]
297macro_rules! ok {
298 () => {
299 ReductError::ok()
300 };
301}
302
303#[macro_export]
304macro_rules! timeout {
305 ($msg:expr, $($arg:tt)*) => {
306 ReductError::timeout(&format!($msg, $($arg)*))
307 };
308 ($msg:expr) => {
309 ReductError::timeout($msg)
310 };
311}
312
313#[macro_export]
314macro_rules! no_content {
315 ($msg:expr, $($arg:tt)*) => {
316 ReductError::no_content(&format!($msg, $($arg)*))
317 };
318 ($msg:expr) => {
319 ReductError::no_content($msg)
320 };
321}
322
323#[macro_export]
324macro_rules! bad_request {
325 ($msg:expr, $($arg:tt)*) => {
326 ReductError::bad_request(&format!($msg, $($arg)*))
327 };
328 ($msg:expr) => {
329 ReductError::bad_request($msg)
330 };
331}
332
333#[macro_export]
334macro_rules! forbidden {
335 ($msg:expr, $($arg:tt)*) => {
336 ReductError::forbidden(&format!($msg, $($arg)*))
337 };
338 ($msg:expr) => {
339 ReductError::forbidden($msg)
340 };
341}
342
343#[macro_export]
344macro_rules! unprocessable_entity {
345 ($msg:expr, $($arg:tt)*) => {
346 ReductError::unprocessable_entity(&format!($msg, $($arg)*))
347 };
348 ($msg:expr) => {
349 ReductError::unprocessable_entity($msg)
350 };
351}
352
353#[macro_export]
354macro_rules! not_found {
355 ($msg:expr, $($arg:tt)*) => {
356 ReductError::not_found(&format!($msg, $($arg)*))
357 };
358 ($msg:expr) => {
359 ReductError::not_found($msg)
360 };
361}
362#[macro_export]
363macro_rules! conflict {
364 ($msg:expr, $($arg:tt)*) => {
365 ReductError::conflict(&format!($msg, $($arg)*))
366 };
367 ($msg:expr) => {
368 ReductError::conflict($msg)
369 };
370}
371
372#[macro_export]
373macro_rules! too_early {
374 ($msg:expr, $($arg:tt)*) => {
375 ReductError::too_early(&format!($msg, $($arg)*))
376 };
377 ($msg:expr) => {
378 ReductError::too_early($msg)
379 };
380}
381
382#[macro_export]
383macro_rules! internal_server_error {
384 ($msg:expr, $($arg:tt)*) => {
385 ReductError::internal_server_error(&format!($msg, $($arg)*))
386 };
387 ($msg:expr) => {
388 ReductError::internal_server_error($msg)
389 };
390}
391
392#[macro_export]
393macro_rules! unauthorized {
394 ($msg:expr, $($arg:tt)*) => {
395 ReductError::unauthorized(&format!($msg, $($arg)*))
396 };
397 ($msg:expr) => {
398 ReductError::unauthorized($msg)
399 };
400}
401
402#[macro_export]
403macro_rules! service_unavailable {
404 ($msg:expr, $($arg:tt)*) => {
405 ReductError::new(ErrorCode::ServiceUnavailable, &format!($msg, $($arg)*))
406 };
407 ($msg:expr) => {
408 ReductError::new(ErrorCode::ServiceUnavailable, $msg)
409 };
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use std::fmt;
416 use std::time::{SystemTime, UNIX_EPOCH};
417
418 #[derive(Debug)]
419 struct CustomSource;
420
421 impl fmt::Display for CustomSource {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 write!(f, "custom source")
424 }
425 }
426
427 #[test]
428 fn creates_internal_server_error() {
429 let error = ReductError::internal_server_error("Unexpected server error");
430 assert_eq!(error.status, ErrorCode::InternalServerError);
431 assert_eq!(error.message, "Unexpected server error");
432 }
433
434 #[test]
435 fn converts_io_error_to_reduct_error() {
436 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "IO failure");
437 let error: ReductError = io_error.into();
438 assert_eq!(error.status, ErrorCode::InternalServerError);
439 assert_eq!(error.message, "IO failure");
440 }
441
442 #[test]
443 fn converts_system_time_error_to_reduct_error() {
444 let system_time_error = UNIX_EPOCH.duration_since(SystemTime::now()).unwrap_err();
445 let error: ReductError<_> = system_time_error.into();
446 assert_eq!(error.status, ErrorCode::InternalServerError);
447 assert_eq!(error.message, "second time provided was later than self");
448 }
449
450 #[test]
451 fn converts_url_parse_error_to_reduct_error() {
452 let parse_error = ParseError::EmptyHost;
453 let error: ReductError<_> = parse_error.into();
454 assert_eq!(error.status, ErrorCode::UrlParseError);
455 assert_eq!(error.message, "empty host");
456 }
457
458 #[test]
459 fn converts_poison_error_to_reduct_error() {
460 let poison_error: PoisonError<()> = PoisonError::new(());
461 let error: ReductError<_> = poison_error.into();
462 assert_eq!(error.status, ErrorCode::InternalServerError);
463 assert_eq!(error.message, "Poison error");
464 }
465
466 #[test]
467 fn drops_source_from_typed_reduct_error() {
468 let error_with_source = ReductError {
469 status: ErrorCode::BadRequest,
470 message: "bad input".to_string(),
471 source: Some(CustomSource),
472 };
473
474 let error = error_with_source.without_source();
475 assert_eq!(error.status, ErrorCode::BadRequest);
476 assert_eq!(error.message, "bad input");
477 assert_eq!(error.source, None);
478 }
479
480 #[test]
481 fn converts_io_reduct_error_into_plain_error() {
482 let error_with_source = ReductError {
483 status: ErrorCode::InternalServerError,
484 message: "io typed error".to_string(),
485 source: Some(std::io::Error::other("boom")),
486 };
487
488 let error: ReductError = error_with_source.without_source();
489 assert_eq!(error.status, ErrorCode::InternalServerError);
490 assert_eq!(error.message, "io typed error");
491 assert_eq!(error.source, None);
492 }
493
494 #[cfg(feature = "io")]
495 #[test]
496 fn converts_send_error_to_reduct_error() {
497 let send_error: SendError<()> = SendError(());
498 let error: ReductError<_> = send_error.into();
499 assert_eq!(error.status, ErrorCode::InternalServerError);
500 assert_eq!(error.message, "channel closed");
501 }
502
503 mod macros {
504 use super::*;
505
506 #[test]
507 fn test_ok_macro() {
508 let error = ok!();
509 assert_eq!(error.status, ErrorCode::OK);
510 assert_eq!(error.message, "");
511 }
512
513 #[test]
514 fn test_timeout_macro() {
515 let error = timeout!("Timeout error: {}", 42);
516 assert_eq!(error.status, ErrorCode::Timeout);
517 assert_eq!(error.message, "Timeout error: 42");
518 }
519
520 #[test]
521 fn test_no_content_macro() {
522 let error = no_content!("No content error: {}", 42);
523 assert_eq!(error.status, ErrorCode::NoContent);
524 assert_eq!(error.message, "No content error: 42");
525 }
526
527 #[test]
528 fn test_bad_request_macro() {
529 let error = bad_request!("Bad request error: {}", 42);
530 assert_eq!(error.status, ErrorCode::BadRequest);
531 assert_eq!(error.message, "Bad request error: 42");
532 }
533
534 #[test]
535 fn test_unprocessable_entity_macro() {
536 let error = unprocessable_entity!("Unprocessable entity error: {}", 42);
537 assert_eq!(error.status, ErrorCode::UnprocessableEntity);
538 assert_eq!(error.message, "Unprocessable entity error: 42");
539 }
540
541 #[test]
542 fn test_not_found_macro() {
543 let error = not_found!("Not found error: {}", 42);
544 assert_eq!(error.status, ErrorCode::NotFound);
545 assert_eq!(error.message, "Not found error: 42");
546 }
547
548 #[test]
549 fn test_conflict_macro() {
550 let error = conflict!("Conflict error: {}", 42);
551 assert_eq!(error.status, ErrorCode::Conflict);
552 assert_eq!(error.message, "Conflict error: 42");
553 }
554
555 #[test]
556 fn test_too_early_macro() {
557 let error = too_early!("Too early error: {}", 42);
558 assert_eq!(error.status, ErrorCode::TooEarly);
559 assert_eq!(error.message, "Too early error: 42");
560 }
561
562 #[test]
563 fn test_internal_server_error_macro() {
564 let error = internal_server_error!("Internal server error: {}", 42);
565 assert_eq!(error.status, ErrorCode::InternalServerError);
566 assert_eq!(error.message, "Internal server error: 42");
567 }
568 }
569}