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 {
73 pub status: ErrorCode,
75
76 pub message: String,
78}
79
80impl Display for ReductError {
81 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
82 write!(f, "[{:?}] {}", self.status, self.message)
83 }
84}
85
86impl Display for ErrorCode {
87 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
88 write!(f, "{}", i16::from(*self))
89 }
90}
91
92impl From<std::io::Error> for ReductError {
93 fn from(err: std::io::Error) -> Self {
94 let status = match err.kind() {
95 std::io::ErrorKind::Unsupported => ErrorCode::MethodNotAllowed,
96 _ => ErrorCode::InternalServerError,
97 };
98
99 ReductError {
100 status,
101 message: err.to_string(),
102 }
103 }
104}
105
106impl From<SystemTimeError> for ReductError {
107 fn from(err: SystemTimeError) -> Self {
108 ReductError {
110 status: ErrorCode::InternalServerError,
111 message: err.to_string(),
112 }
113 }
114}
115
116impl From<ParseError> for ReductError {
117 fn from(err: ParseError) -> Self {
118 ReductError {
120 status: ErrorCode::UrlParseError,
121 message: err.to_string(),
122 }
123 }
124}
125
126impl<T> From<PoisonError<T>> for ReductError {
127 fn from(_: PoisonError<T>) -> Self {
128 ReductError {
130 status: ErrorCode::InternalServerError,
131 message: "Poison error".to_string(),
132 }
133 }
134}
135
136impl From<Box<dyn std::any::Any + Send>> for ReductError {
137 fn from(err: Box<dyn std::any::Any + Send>) -> Self {
138 ReductError {
140 status: ErrorCode::InternalServerError,
141 message: format!("{:?}", err),
142 }
143 }
144}
145
146#[cfg(feature = "io")]
147impl<T> From<SendError<T>> for ReductError {
148 fn from(err: SendError<T>) -> Self {
149 ReductError {
151 status: ErrorCode::InternalServerError,
152 message: err.to_string(),
153 }
154 }
155}
156
157impl Error for ReductError {
158 fn description(&self) -> &str {
159 &self.message
160 }
161}
162
163impl ReductError {
164 pub fn new(status: ErrorCode, message: &str) -> Self {
165 ReductError {
166 status,
167 message: message.to_string(),
168 }
169 }
170
171 pub fn status(&self) -> ErrorCode {
172 self.status
173 }
174
175 pub fn message(&self) -> &str {
176 &self.message
177 }
178
179 pub fn ok() -> ReductError {
180 ReductError {
181 status: ErrorCode::OK,
182 message: "".to_string(),
183 }
184 }
185
186 pub fn timeout(msg: &str) -> ReductError {
187 ReductError {
188 status: ErrorCode::Timeout,
189 message: msg.to_string(),
190 }
191 }
192
193 pub fn no_content(msg: &str) -> ReductError {
195 ReductError {
196 status: ErrorCode::NoContent,
197 message: msg.to_string(),
198 }
199 }
200
201 pub fn not_found(msg: &str) -> ReductError {
203 ReductError {
204 status: ErrorCode::NotFound,
205 message: msg.to_string(),
206 }
207 }
208
209 pub fn conflict(msg: &str) -> ReductError {
211 ReductError {
212 status: ErrorCode::Conflict,
213 message: msg.to_string(),
214 }
215 }
216
217 pub fn bad_request(msg: &str) -> ReductError {
219 ReductError {
220 status: ErrorCode::BadRequest,
221 message: msg.to_string(),
222 }
223 }
224
225 pub fn unauthorized(msg: &str) -> ReductError {
227 ReductError {
228 status: ErrorCode::Unauthorized,
229 message: msg.to_string(),
230 }
231 }
232
233 pub fn forbidden(msg: &str) -> ReductError {
235 ReductError {
236 status: ErrorCode::Forbidden,
237 message: msg.to_string(),
238 }
239 }
240
241 pub fn unprocessable_entity(msg: &str) -> ReductError {
243 ReductError {
244 status: ErrorCode::UnprocessableEntity,
245 message: msg.to_string(),
246 }
247 }
248
249 pub fn too_early(msg: &str) -> ReductError {
251 ReductError {
252 status: ErrorCode::TooEarly,
253 message: msg.to_string(),
254 }
255 }
256
257 pub fn internal_server_error(msg: &str) -> ReductError {
259 ReductError {
260 status: ErrorCode::InternalServerError,
261 message: msg.to_string(),
262 }
263 }
264}
265
266#[macro_export]
269macro_rules! timeout {
270 ($msg:expr, $($arg:tt)*) => {
271 ReductError::timeout(&format!($msg, $($arg)*))
272 };
273 ($msg:expr) => {
274 ReductError::timeout($msg)
275 };
276}
277
278#[macro_export]
279macro_rules! no_content {
280 ($msg:expr, $($arg:tt)*) => {
281 ReductError::no_content(&format!($msg, $($arg)*))
282 };
283 ($msg:expr) => {
284 ReductError::no_content($msg)
285 };
286}
287
288#[macro_export]
289macro_rules! bad_request {
290 ($msg:expr, $($arg:tt)*) => {
291 ReductError::bad_request(&format!($msg, $($arg)*))
292 };
293 ($msg:expr) => {
294 ReductError::bad_request($msg)
295 };
296}
297
298#[macro_export]
299macro_rules! forbidden {
300 ($msg:expr, $($arg:tt)*) => {
301 ReductError::forbidden(&format!($msg, $($arg)*))
302 };
303 ($msg:expr) => {
304 ReductError::forbidden($msg)
305 };
306}
307
308#[macro_export]
309macro_rules! unprocessable_entity {
310 ($msg:expr, $($arg:tt)*) => {
311 ReductError::unprocessable_entity(&format!($msg, $($arg)*))
312 };
313 ($msg:expr) => {
314 ReductError::unprocessable_entity($msg)
315 };
316}
317
318#[macro_export]
319macro_rules! not_found {
320 ($msg:expr, $($arg:tt)*) => {
321 ReductError::not_found(&format!($msg, $($arg)*))
322 };
323 ($msg:expr) => {
324 ReductError::not_found($msg)
325 };
326}
327#[macro_export]
328macro_rules! conflict {
329 ($msg:expr, $($arg:tt)*) => {
330 ReductError::conflict(&format!($msg, $($arg)*))
331 };
332 ($msg:expr) => {
333 ReductError::conflict($msg)
334 };
335}
336
337#[macro_export]
338macro_rules! too_early {
339 ($msg:expr, $($arg:tt)*) => {
340 ReductError::too_early(&format!($msg, $($arg)*))
341 };
342 ($msg:expr) => {
343 ReductError::too_early($msg)
344 };
345}
346
347#[macro_export]
348macro_rules! internal_server_error {
349 ($msg:expr, $($arg:tt)*) => {
350 ReductError::internal_server_error(&format!($msg, $($arg)*))
351 };
352 ($msg:expr) => {
353 ReductError::internal_server_error($msg)
354 };
355}
356
357#[macro_export]
358macro_rules! unauthorized {
359 ($msg:expr, $($arg:tt)*) => {
360 ReductError::unauthorized(&format!($msg, $($arg)*))
361 };
362 ($msg:expr) => {
363 ReductError::unauthorized($msg)
364 };
365}
366
367#[macro_export]
368macro_rules! service_unavailable {
369 ($msg:expr, $($arg:tt)*) => {
370 ReductError::new(ErrorCode::ServiceUnavailable, &format!($msg, $($arg)*))
371 };
372 ($msg:expr) => {
373 ReductError::new(ErrorCode::ServiceUnavailable, $msg)
374 };
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use std::time::{SystemTime, UNIX_EPOCH};
381
382 #[test]
383 fn creates_internal_server_error() {
384 let error = ReductError::internal_server_error("Unexpected server error");
385 assert_eq!(error.status, ErrorCode::InternalServerError);
386 assert_eq!(error.message, "Unexpected server error");
387 }
388
389 #[test]
390 fn converts_io_error_to_reduct_error() {
391 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "IO failure");
392 let error: ReductError = io_error.into();
393 assert_eq!(error.status, ErrorCode::InternalServerError);
394 assert_eq!(error.message, "IO failure");
395 }
396
397 #[test]
398 fn converts_system_time_error_to_reduct_error() {
399 let system_time_error = UNIX_EPOCH.duration_since(SystemTime::now()).unwrap_err();
400 let error: ReductError = system_time_error.into();
401 assert_eq!(error.status, ErrorCode::InternalServerError);
402 assert_eq!(error.message, "second time provided was later than self");
403 }
404
405 #[test]
406 fn converts_url_parse_error_to_reduct_error() {
407 let parse_error = ParseError::EmptyHost;
408 let error: ReductError = parse_error.into();
409 assert_eq!(error.status, ErrorCode::UrlParseError);
410 assert_eq!(error.message, "empty host");
411 }
412
413 #[test]
414 fn converts_poison_error_to_reduct_error() {
415 let poison_error: PoisonError<()> = PoisonError::new(());
416 let error: ReductError = poison_error.into();
417 assert_eq!(error.status, ErrorCode::InternalServerError);
418 assert_eq!(error.message, "Poison error");
419 }
420
421 #[cfg(feature = "io")]
422 #[test]
423 fn converts_send_error_to_reduct_error() {
424 let send_error: SendError<()> = SendError(());
425 let error: ReductError = send_error.into();
426 assert_eq!(error.status, ErrorCode::InternalServerError);
427 assert_eq!(error.message, "channel closed");
428 }
429
430 mod macros {
431 use super::*;
432
433 #[test]
434 fn test_timeout_macro() {
435 let error = timeout!("Timeout error: {}", 42);
436 assert_eq!(error.status, ErrorCode::Timeout);
437 assert_eq!(error.message, "Timeout error: 42");
438 }
439
440 #[test]
441 fn test_no_content_macro() {
442 let error = no_content!("No content error: {}", 42);
443 assert_eq!(error.status, ErrorCode::NoContent);
444 assert_eq!(error.message, "No content error: 42");
445 }
446
447 #[test]
448 fn test_bad_request_macro() {
449 let error = bad_request!("Bad request error: {}", 42);
450 assert_eq!(error.status, ErrorCode::BadRequest);
451 assert_eq!(error.message, "Bad request error: 42");
452 }
453
454 #[test]
455 fn test_unprocessable_entity_macro() {
456 let error = unprocessable_entity!("Unprocessable entity error: {}", 42);
457 assert_eq!(error.status, ErrorCode::UnprocessableEntity);
458 assert_eq!(error.message, "Unprocessable entity error: 42");
459 }
460
461 #[test]
462 fn test_not_found_macro() {
463 let error = not_found!("Not found error: {}", 42);
464 assert_eq!(error.status, ErrorCode::NotFound);
465 assert_eq!(error.message, "Not found error: 42");
466 }
467
468 #[test]
469 fn test_conflict_macro() {
470 let error = conflict!("Conflict error: {}", 42);
471 assert_eq!(error.status, ErrorCode::Conflict);
472 assert_eq!(error.message, "Conflict error: 42");
473 }
474
475 #[test]
476 fn test_too_early_macro() {
477 let error = too_early!("Too early error: {}", 42);
478 assert_eq!(error.status, ErrorCode::TooEarly);
479 assert_eq!(error.message, "Too early error: 42");
480 }
481
482 #[test]
483 fn test_internal_server_error_macro() {
484 let error = internal_server_error!("Internal server error: {}", 42);
485 assert_eq!(error.status, ErrorCode::InternalServerError);
486 assert_eq!(error.message, "Internal server error: 42");
487 }
488 }
489}