1pub use int_enum::IntEnum;
7use std::error::Error;
8use std::fmt::{Debug, Display, Error as FmtError, Formatter};
9use std::sync::PoisonError;
10use std::time::SystemTimeError;
11use url::ParseError;
12
13#[cfg(feature = "io")]
14use tokio::sync::mpsc::error::SendError;
15
16#[repr(i16)]
18#[derive(Debug, PartialEq, PartialOrd, Copy, Clone, IntEnum)]
19pub enum ErrorCode {
20 InvalidRequest = -6, Interrupt = -5, UrlParseError = -4,
23 ConnectionError = -3,
24 Timeout = -2,
25 Unknown = -1,
26
27 Continue = 100,
28 OK = 200,
29 Created = 201,
30 Accepted = 202,
31 NoContent = 204,
32 BadRequest = 400,
33 Unauthorized = 401,
34 Forbidden = 403,
35 NotFound = 404,
36 MethodNotAllowed = 405,
37 NotAcceptable = 406,
38 RequestTimeout = 408,
39 Conflict = 409,
40 Gone = 410,
41 LengthRequired = 411,
42 PreconditionFailed = 412,
43 PayloadTooLarge = 413,
44 URITooLong = 414,
45 UnsupportedMediaType = 415,
46 RangeNotSatisfiable = 416,
47 ExpectationFailed = 417,
48 ImATeapot = 418,
49 MisdirectedRequest = 421,
50 UnprocessableEntity = 422,
51 Locked = 423,
52 FailedDependency = 424,
53 TooEarly = 425,
54 UpgradeRequired = 426,
55 PreconditionRequired = 428,
56 TooManyRequests = 429,
57 RequestHeaderFieldsTooLarge = 431,
58 UnavailableForLegalReasons = 451,
59 InternalServerError = 500,
60 NotImplemented = 501,
61 BadGateway = 502,
62 ServiceUnavailable = 503,
63 GatewayTimeout = 504,
64 HTTPVersionNotSupported = 505,
65 VariantAlsoNegotiates = 506,
66 InsufficientStorage = 507,
67 LoopDetected = 508,
68 NotExtended = 510,
69 NetworkAuthenticationRequired = 511,
70}
71
72#[derive(PartialEq, Debug, Clone)]
74pub struct ReductError {
75 pub status: ErrorCode,
77
78 pub message: String,
80}
81
82impl Display for ReductError {
83 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
84 write!(f, "[{:?}] {}", self.status, self.message)
85 }
86}
87
88impl Display for ErrorCode {
89 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
90 write!(f, "{}", i16::from(*self))
91 }
92}
93
94impl From<std::io::Error> for ReductError {
95 fn from(err: std::io::Error) -> Self {
96 let status = match err.kind() {
97 std::io::ErrorKind::Unsupported => ErrorCode::MethodNotAllowed,
98 _ => ErrorCode::InternalServerError,
99 };
100
101 ReductError {
102 status,
103 message: err.to_string(),
104 }
105 }
106}
107
108impl From<SystemTimeError> for ReductError {
109 fn from(err: SystemTimeError) -> Self {
110 ReductError {
112 status: ErrorCode::InternalServerError,
113 message: err.to_string(),
114 }
115 }
116}
117
118impl From<ParseError> for ReductError {
119 fn from(err: ParseError) -> Self {
120 ReductError {
122 status: ErrorCode::UrlParseError,
123 message: err.to_string(),
124 }
125 }
126}
127
128impl<T> From<PoisonError<T>> for ReductError {
129 fn from(_: PoisonError<T>) -> Self {
130 ReductError {
132 status: ErrorCode::InternalServerError,
133 message: "Poison error".to_string(),
134 }
135 }
136}
137
138impl From<Box<dyn std::any::Any + Send>> for ReductError {
139 fn from(err: Box<dyn std::any::Any + Send>) -> Self {
140 ReductError {
142 status: ErrorCode::InternalServerError,
143 message: format!("{:?}", err),
144 }
145 }
146}
147
148#[cfg(feature = "io")]
149impl<T> From<SendError<T>> for ReductError {
150 fn from(err: SendError<T>) -> Self {
151 ReductError {
153 status: ErrorCode::InternalServerError,
154 message: err.to_string(),
155 }
156 }
157}
158
159impl Error for ReductError {
160 fn description(&self) -> &str {
161 &self.message
162 }
163}
164
165impl ReductError {
166 pub fn new(status: ErrorCode, message: &str) -> Self {
167 ReductError {
168 status,
169 message: message.to_string(),
170 }
171 }
172
173 pub fn status(&self) -> ErrorCode {
174 self.status
175 }
176
177 pub fn message(&self) -> &str {
178 &self.message
179 }
180
181 pub fn ok() -> ReductError {
182 ReductError {
183 status: ErrorCode::OK,
184 message: "".to_string(),
185 }
186 }
187
188 pub fn timeout(msg: &str) -> ReductError {
189 ReductError {
190 status: ErrorCode::Timeout,
191 message: msg.to_string(),
192 }
193 }
194
195 pub fn no_content(msg: &str) -> ReductError {
197 ReductError {
198 status: ErrorCode::NoContent,
199 message: msg.to_string(),
200 }
201 }
202
203 pub fn not_found(msg: &str) -> ReductError {
205 ReductError {
206 status: ErrorCode::NotFound,
207 message: msg.to_string(),
208 }
209 }
210
211 pub fn conflict(msg: &str) -> ReductError {
213 ReductError {
214 status: ErrorCode::Conflict,
215 message: msg.to_string(),
216 }
217 }
218
219 pub fn bad_request(msg: &str) -> ReductError {
221 ReductError {
222 status: ErrorCode::BadRequest,
223 message: msg.to_string(),
224 }
225 }
226
227 pub fn unauthorized(msg: &str) -> ReductError {
229 ReductError {
230 status: ErrorCode::Unauthorized,
231 message: msg.to_string(),
232 }
233 }
234
235 pub fn forbidden(msg: &str) -> ReductError {
237 ReductError {
238 status: ErrorCode::Forbidden,
239 message: msg.to_string(),
240 }
241 }
242
243 pub fn unprocessable_entity(msg: &str) -> ReductError {
245 ReductError {
246 status: ErrorCode::UnprocessableEntity,
247 message: msg.to_string(),
248 }
249 }
250
251 pub fn too_early(msg: &str) -> ReductError {
253 ReductError {
254 status: ErrorCode::TooEarly,
255 message: msg.to_string(),
256 }
257 }
258
259 pub fn internal_server_error(msg: &str) -> ReductError {
261 ReductError {
262 status: ErrorCode::InternalServerError,
263 message: msg.to_string(),
264 }
265 }
266}
267
268#[macro_export]
271macro_rules! timeout {
272 ($msg:expr, $($arg:tt)*) => {
273 ReductError::timeout(&format!($msg, $($arg)*))
274 };
275 ($msg:expr) => {
276 ReductError::timeout($msg)
277 };
278}
279
280#[macro_export]
281macro_rules! no_content {
282 ($msg:expr, $($arg:tt)*) => {
283 ReductError::no_content(&format!($msg, $($arg)*))
284 };
285 ($msg:expr) => {
286 ReductError::no_content($msg)
287 };
288}
289
290#[macro_export]
291macro_rules! bad_request {
292 ($msg:expr, $($arg:tt)*) => {
293 ReductError::bad_request(&format!($msg, $($arg)*))
294 };
295 ($msg:expr) => {
296 ReductError::bad_request($msg)
297 };
298}
299
300#[macro_export]
301macro_rules! forbidden {
302 ($msg:expr, $($arg:tt)*) => {
303 ReductError::forbidden(&format!($msg, $($arg)*))
304 };
305 ($msg:expr) => {
306 ReductError::forbidden($msg)
307 };
308}
309
310#[macro_export]
311macro_rules! unprocessable_entity {
312 ($msg:expr, $($arg:tt)*) => {
313 ReductError::unprocessable_entity(&format!($msg, $($arg)*))
314 };
315 ($msg:expr) => {
316 ReductError::unprocessable_entity($msg)
317 };
318}
319
320#[macro_export]
321macro_rules! not_found {
322 ($msg:expr, $($arg:tt)*) => {
323 ReductError::not_found(&format!($msg, $($arg)*))
324 };
325 ($msg:expr) => {
326 ReductError::not_found($msg)
327 };
328}
329#[macro_export]
330macro_rules! conflict {
331 ($msg:expr, $($arg:tt)*) => {
332 ReductError::conflict(&format!($msg, $($arg)*))
333 };
334 ($msg:expr) => {
335 ReductError::conflict($msg)
336 };
337}
338
339#[macro_export]
340macro_rules! too_early {
341 ($msg:expr, $($arg:tt)*) => {
342 ReductError::too_early(&format!($msg, $($arg)*))
343 };
344 ($msg:expr) => {
345 ReductError::too_early($msg)
346 };
347}
348
349#[macro_export]
350macro_rules! internal_server_error {
351 ($msg:expr, $($arg:tt)*) => {
352 ReductError::internal_server_error(&format!($msg, $($arg)*))
353 };
354 ($msg:expr) => {
355 ReductError::internal_server_error($msg)
356 };
357}
358
359#[macro_export]
360macro_rules! unauthorized {
361 ($msg:expr, $($arg:tt)*) => {
362 ReductError::unauthorized(&format!($msg, $($arg)*))
363 };
364 ($msg:expr) => {
365 ReductError::unauthorized($msg)
366 };
367}
368
369#[macro_export]
370macro_rules! service_unavailable {
371 ($msg:expr, $($arg:tt)*) => {
372 ReductError::new(ErrorCode::ServiceUnavailable, &format!($msg, $($arg)*))
373 };
374 ($msg:expr) => {
375 ReductError::new(ErrorCode::ServiceUnavailable, $msg)
376 };
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use std::time::{SystemTime, UNIX_EPOCH};
383
384 #[test]
385 fn creates_internal_server_error() {
386 let error = ReductError::internal_server_error("Unexpected server error");
387 assert_eq!(error.status, ErrorCode::InternalServerError);
388 assert_eq!(error.message, "Unexpected server error");
389 }
390
391 #[test]
392 fn converts_io_error_to_reduct_error() {
393 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "IO failure");
394 let error: ReductError = io_error.into();
395 assert_eq!(error.status, ErrorCode::InternalServerError);
396 assert_eq!(error.message, "IO failure");
397 }
398
399 #[test]
400 fn converts_system_time_error_to_reduct_error() {
401 let system_time_error = UNIX_EPOCH.duration_since(SystemTime::now()).unwrap_err();
402 let error: ReductError = system_time_error.into();
403 assert_eq!(error.status, ErrorCode::InternalServerError);
404 assert_eq!(error.message, "second time provided was later than self");
405 }
406
407 #[test]
408 fn converts_url_parse_error_to_reduct_error() {
409 let parse_error = ParseError::EmptyHost;
410 let error: ReductError = parse_error.into();
411 assert_eq!(error.status, ErrorCode::UrlParseError);
412 assert_eq!(error.message, "empty host");
413 }
414
415 #[test]
416 fn converts_poison_error_to_reduct_error() {
417 let poison_error: PoisonError<()> = PoisonError::new(());
418 let error: ReductError = poison_error.into();
419 assert_eq!(error.status, ErrorCode::InternalServerError);
420 assert_eq!(error.message, "Poison error");
421 }
422
423 #[cfg(feature = "io")]
424 #[test]
425 fn converts_send_error_to_reduct_error() {
426 let send_error: SendError<()> = SendError(());
427 let error: ReductError = send_error.into();
428 assert_eq!(error.status, ErrorCode::InternalServerError);
429 assert_eq!(error.message, "channel closed");
430 }
431
432 mod macros {
433 use super::*;
434
435 #[test]
436 fn test_timeout_macro() {
437 let error = timeout!("Timeout error: {}", 42);
438 assert_eq!(error.status, ErrorCode::Timeout);
439 assert_eq!(error.message, "Timeout error: 42");
440 }
441
442 #[test]
443 fn test_no_content_macro() {
444 let error = no_content!("No content error: {}", 42);
445 assert_eq!(error.status, ErrorCode::NoContent);
446 assert_eq!(error.message, "No content error: 42");
447 }
448
449 #[test]
450 fn test_bad_request_macro() {
451 let error = bad_request!("Bad request error: {}", 42);
452 assert_eq!(error.status, ErrorCode::BadRequest);
453 assert_eq!(error.message, "Bad request error: 42");
454 }
455
456 #[test]
457 fn test_unprocessable_entity_macro() {
458 let error = unprocessable_entity!("Unprocessable entity error: {}", 42);
459 assert_eq!(error.status, ErrorCode::UnprocessableEntity);
460 assert_eq!(error.message, "Unprocessable entity error: 42");
461 }
462
463 #[test]
464 fn test_not_found_macro() {
465 let error = not_found!("Not found error: {}", 42);
466 assert_eq!(error.status, ErrorCode::NotFound);
467 assert_eq!(error.message, "Not found error: 42");
468 }
469
470 #[test]
471 fn test_conflict_macro() {
472 let error = conflict!("Conflict error: {}", 42);
473 assert_eq!(error.status, ErrorCode::Conflict);
474 assert_eq!(error.message, "Conflict error: 42");
475 }
476
477 #[test]
478 fn test_too_early_macro() {
479 let error = too_early!("Too early error: {}", 42);
480 assert_eq!(error.status, ErrorCode::TooEarly);
481 assert_eq!(error.message, "Too early error: 42");
482 }
483
484 #[test]
485 fn test_internal_server_error_macro() {
486 let error = internal_server_error!("Internal server error: {}", 42);
487 assert_eq!(error.status, ErrorCode::InternalServerError);
488 assert_eq!(error.message, "Internal server error: 42");
489 }
490 }
491}