1#[cfg(feature = "http")]
7use crate::http::{RawResponse, StatusCode};
8use std::borrow::Cow;
9use std::fmt::{Debug, Display};
10
11pub type Result<T> = std::result::Result<T, Error>;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum ErrorKind {
19 #[cfg(feature = "amqp")]
21 Amqp,
22 #[cfg(feature = "http")]
24 HttpResponse {
25 status: StatusCode,
27 error_code: Option<String>,
29 raw_response: Option<Box<RawResponse>>,
31 },
32 Io,
34 DataConversion,
36 Credential,
38 MockFramework,
40 Other,
42}
43
44impl ErrorKind {
45 pub fn into_error(self) -> Error {
47 Error {
48 context: Repr::Simple(self),
49 }
50 }
51}
52
53impl Display for ErrorKind {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 #[cfg(feature = "http")]
57 ErrorKind::HttpResponse {
58 status, error_code, ..
59 } => f
60 .debug_tuple("HttpResponse")
61 .field(status)
62 .field(&error_code.as_deref().unwrap_or("(unknown error code)"))
63 .finish(),
64 ErrorKind::Io => f.write_str("Io"),
65 ErrorKind::DataConversion => f.write_str("DataConversion"),
66 ErrorKind::Credential => f.write_str("Credential"),
67 ErrorKind::MockFramework => f.write_str("MockFramework"),
68 ErrorKind::Other => f.write_str("Other"),
69 #[cfg(feature = "amqp")]
70 ErrorKind::Amqp => f.write_str("Amqp"),
71 }
72 }
73}
74
75#[derive(Debug)]
77pub struct Error {
78 context: Repr,
79}
80
81impl Error {
82 pub fn new<E>(kind: ErrorKind, error: E) -> Self
84 where
85 E: Into<Box<dyn std::error::Error + Send + Sync>>,
86 {
87 Self {
88 context: Repr::Custom(Custom {
89 kind,
90 error: error.into(),
91 }),
92 }
93 }
94
95 #[must_use]
97 pub fn with_error<E, C>(kind: ErrorKind, error: E, message: C) -> Self
98 where
99 E: Into<Box<dyn std::error::Error + Send + Sync>>,
100 C: Into<Cow<'static, str>>,
101 {
102 Self {
103 context: Repr::CustomMessage(
104 Custom {
105 kind,
106 error: error.into(),
107 },
108 message.into(),
109 ),
110 }
111 }
112
113 #[must_use]
115 pub fn with_error_fn<E, F, C>(kind: ErrorKind, error: E, f: F) -> Self
116 where
117 E: Into<Box<dyn std::error::Error + Send + Sync>>,
118 F: FnOnce() -> C,
119 C: Into<Cow<'static, str>>,
120 {
121 Self::with_error(kind, error, f())
122 }
123
124 #[must_use]
126 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
127 where
128 C: Into<Cow<'static, str>>,
129 {
130 Self {
131 context: Repr::SimpleMessage(kind, message.into()),
132 }
133 }
134
135 #[must_use]
137 pub fn with_message_fn<F, C>(kind: ErrorKind, f: F) -> Self
138 where
139 F: FnOnce() -> C,
140 C: Into<Cow<'static, str>>,
141 {
142 Self::with_message(kind, f())
143 }
144
145 #[must_use]
147 pub fn with_context<C>(self, message: C) -> Self
148 where
149 C: Into<Cow<'static, str>>,
150 {
151 Self::with_error(self.kind().clone(), self, message)
152 }
153
154 #[must_use]
156 pub fn with_context_fn<F, C>(self, f: F) -> Self
157 where
158 F: FnOnce() -> C,
159 C: Into<Cow<'static, str>>,
160 {
161 self.with_context(f())
162 }
163
164 pub fn kind(&self) -> &ErrorKind {
166 match &self.context {
167 Repr::Simple(kind)
168 | Repr::SimpleMessage(kind, ..)
169 | Repr::Custom(Custom { kind, .. })
170 | Repr::CustomMessage(Custom { kind, .. }, _) => kind,
171 }
172 }
173
174 #[cfg(feature = "http")]
176 pub fn http_status(&self) -> Option<StatusCode> {
177 match &self.kind() {
178 ErrorKind::HttpResponse { status, .. } => Some(*status),
179 _ => None,
180 }
181 }
182
183 pub fn into_inner(self) -> std::result::Result<Box<dyn std::error::Error + Send + Sync>, Self> {
185 match self.context {
186 Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
187 Ok(error)
188 }
189 _ => Err(self),
190 }
191 }
192
193 pub fn into_downcast<T: std::error::Error + 'static>(self) -> std::result::Result<T, Self> {
197 if self.downcast_ref::<T>().is_none() {
198 return Err(self);
199 }
200 Ok(*self.into_inner()?.downcast().expect("downcast is Some(T)"))
202 }
203
204 pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
206 match &self.context {
207 Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
208 Some(error.as_ref())
209 }
210 _ => None,
211 }
212 }
213
214 pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
216 self.get_ref()?.downcast_ref()
217 }
218
219 pub fn get_mut(&mut self) -> Option<&mut (dyn std::error::Error + Send + Sync + 'static)> {
221 match &mut self.context {
222 Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
223 Some(error.as_mut())
224 }
225 _ => None,
226 }
227 }
228
229 pub fn downcast_mut<T: std::error::Error + 'static>(&mut self) -> Option<&mut T> {
231 self.get_mut()?.downcast_mut()
232 }
233}
234
235impl std::error::Error for Error {
236 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
237 match &self.context {
238 Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
239 Some(&**error)
240 }
241 _ => None,
242 }
243 }
244}
245
246impl From<ErrorKind> for Error {
247 fn from(kind: ErrorKind) -> Self {
248 Self {
249 context: Repr::Simple(kind),
250 }
251 }
252}
253
254impl From<std::io::Error> for Error {
255 fn from(error: std::io::Error) -> Self {
256 Self::new(ErrorKind::Io, error)
257 }
258}
259
260impl From<std::str::ParseBoolError> for Error {
261 fn from(error: std::str::ParseBoolError) -> Self {
262 Self::new(ErrorKind::DataConversion, error)
263 }
264}
265
266impl From<std::num::ParseIntError> for Error {
267 fn from(error: std::num::ParseIntError) -> Self {
268 Self::new(ErrorKind::DataConversion, error)
269 }
270}
271
272impl From<base64::DecodeError> for Error {
273 fn from(error: base64::DecodeError) -> Self {
274 Self::new(ErrorKind::DataConversion, error)
275 }
276}
277
278#[cfg(feature = "json")]
279impl From<serde_json::Error> for Error {
280 fn from(error: serde_json::Error) -> Self {
281 Self::new(ErrorKind::DataConversion, error)
282 }
283}
284
285impl From<std::string::FromUtf8Error> for Error {
286 fn from(error: std::string::FromUtf8Error) -> Self {
287 Self::new(ErrorKind::DataConversion, error)
288 }
289}
290
291impl From<std::str::Utf8Error> for Error {
292 fn from(error: std::str::Utf8Error) -> Self {
293 Self::new(ErrorKind::DataConversion, error)
294 }
295}
296
297impl From<url::ParseError> for Error {
298 fn from(error: url::ParseError) -> Self {
299 Self::new(ErrorKind::DataConversion, error)
300 }
301}
302
303impl From<core::convert::Infallible> for Error {
304 fn from(_: core::convert::Infallible) -> Self {
305 panic!("no error")
306 }
307}
308
309impl Display for Error {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 match &self.context {
312 Repr::Simple(kind) => std::fmt::Display::fmt(&kind, f),
313 Repr::SimpleMessage(_, message) => f.write_str(message),
314 Repr::Custom(Custom { error, .. }) => std::fmt::Display::fmt(&error, f),
315 Repr::CustomMessage(_, message) => f.write_str(message),
316 }
317 }
318}
319
320pub trait ResultExt<T>: private::Sealed {
324 fn with_kind(self, kind: ErrorKind) -> Result<T>
326 where
327 Self: Sized;
328
329 fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
331 where
332 Self: Sized,
333 C: Into<Cow<'static, str>>;
334
335 fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
337 where
338 Self: Sized,
339 F: FnOnce() -> C,
340 C: Into<Cow<'static, str>>;
341}
342
343mod private {
344 pub trait Sealed {}
345
346 impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
347}
348
349impl<T, E> ResultExt<T> for std::result::Result<T, E>
350where
351 E: std::error::Error + Send + Sync + 'static,
352{
353 fn with_kind(self, kind: ErrorKind) -> Result<T>
354 where
355 Self: Sized,
356 {
357 self.map_err(|e| Error::new(kind, e))
358 }
359
360 fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
361 where
362 Self: Sized,
363 C: Into<Cow<'static, str>>,
364 {
365 self.map_err(|e| Error {
366 context: Repr::CustomMessage(
367 Custom {
368 error: Box::new(e),
369 kind,
370 },
371 message.into(),
372 ),
373 })
374 }
375
376 fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
377 where
378 Self: Sized,
379 F: FnOnce() -> C,
380 C: Into<Cow<'static, str>>,
381 {
382 self.with_context(kind, f())
383 }
384}
385
386#[derive(Debug)]
387enum Repr {
388 Simple(ErrorKind),
389 SimpleMessage(ErrorKind, Cow<'static, str>),
390 Custom(Custom),
391 CustomMessage(Custom, Cow<'static, str>),
392}
393
394#[derive(Debug)]
395struct Custom {
396 kind: ErrorKind,
397 error: Box<dyn std::error::Error + Send + Sync>,
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use std::io;
404
405 #[allow(
406 dead_code,
407 unconditional_recursion,
408 clippy::extra_unused_type_parameters
409 )]
410 fn ensure_send<T: Send>() {
411 ensure_send::<Error>();
412 }
413
414 #[derive(thiserror::Error, Debug)]
415 enum IntermediateError {
416 #[error("second error")]
417 Io(#[from] std::io::Error),
418 }
419
420 fn create_error() -> Error {
421 let inner = io::Error::new(io::ErrorKind::BrokenPipe, "third error");
423 let inner: IntermediateError = inner.into();
424 let inner = io::Error::new(io::ErrorKind::ConnectionAborted, inner);
425
426 Error::new(ErrorKind::Io, inner)
428 }
429
430 #[test]
431 fn errors_display_properly() {
432 let error = create_error();
433
434 let mut error: &dyn std::error::Error = &error;
436 let display = format!("{error}");
437 let mut errors = vec![];
438 while let Some(cause) = error.source() {
439 errors.push(format!("{cause}"));
440 error = cause;
441 }
442
443 assert_eq!(display, "second error");
444 assert_eq!(errors.join(","), "second error,third error");
445
446 let inner = io::Error::new(io::ErrorKind::BrokenPipe, "third error");
447 let error: Result<()> = std::result::Result::<(), std::io::Error>::Err(inner)
448 .with_context(ErrorKind::Io, "oh no broken pipe!");
449 assert_eq!(format!("{}", error.unwrap_err()), "oh no broken pipe!");
450 }
451
452 #[test]
453 fn downcasting_works() {
454 let error = &create_error() as &dyn std::error::Error;
455 assert!(error.is::<Error>());
456 let downcasted = error
457 .source()
458 .unwrap()
459 .downcast_ref::<std::io::Error>()
460 .unwrap();
461 assert_eq!(format!("{downcasted}"), "second error");
462 }
463
464 #[test]
465 fn turn_into_inner_error() {
466 let error = create_error();
467 let inner = error.into_inner().unwrap();
468 let inner = inner.downcast_ref::<std::io::Error>().unwrap();
469 assert_eq!(format!("{inner}"), "second error");
470
471 let error = create_error();
472 let inner = error.get_ref().unwrap();
473 let inner = inner.downcast_ref::<std::io::Error>().unwrap();
474 assert_eq!(format!("{inner}"), "second error");
475
476 let mut error = create_error();
477 let inner = error.get_mut().unwrap();
478 let inner = inner.downcast_ref::<std::io::Error>().unwrap();
479 assert_eq!(format!("{inner}"), "second error");
480 }
481
482 #[test]
483 fn set_result_kind() {
484 let result = std::result::Result::<(), _>::Err(create_error());
485 let result = result.with_kind(ErrorKind::Io);
486 assert_eq!(&ErrorKind::Io, result.unwrap_err().kind());
487 }
488}