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