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