Skip to main content

topcoat_core/
error.rs

1use std::{
2    any::Any,
3    backtrace::{Backtrace, BacktraceStatus},
4    fmt::{self, Debug, Display},
5    ops::Deref,
6    sync::Arc,
7};
8
9pub type Result<T = (), E = Error> = ::core::result::Result<T, E>;
10
11/// Error type used by Topcoat APIs.
12///
13/// Use `?` to convert a compatible [`std::error::Error`] into this type.
14/// Inspect its concrete value with [`downcast_ref`](Self::downcast_ref), or
15/// recover it with [`downcast`](Self::downcast). Clones share the stored error.
16/// Backtrace capture follows the environment settings described by
17/// [`backtrace`](Self::backtrace).
18///
19/// With the `anyhow` feature, `Error::from_anyhow` converts an `anyhow::Error`
20/// as well.
21#[derive(Clone)]
22pub struct Error(Arc<dyn ErrorObject>);
23
24impl Error {
25    /// Builds an error from a message.
26    pub fn msg(message: impl Display + Debug + Send + Sync + 'static) -> Self {
27        Self::from(Message(message))
28    }
29
30    /// Builds an error from a boxed error. Its message and sources carry
31    /// over, but the concrete error inside the box is not reachable through
32    /// the downcast methods.
33    #[must_use]
34    pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
35        Self::from(Boxed(error))
36    }
37
38    /// Builds an error from an [`anyhow::Error`]. Its message and sources
39    /// carry over, but the concrete error inside it is not reachable through
40    /// the downcast methods.
41    #[cfg(feature = "anyhow")]
42    #[must_use]
43    pub fn from_anyhow(error: anyhow::Error) -> Self {
44        Self::from_boxed(error.into())
45    }
46
47    /// Wraps this error in a context message, which becomes its [`Display`]
48    /// output. The wrapped error stays reachable through
49    /// [`source`](std::error::Error::source), [`chain`](Self::chain), and
50    /// the downcast methods.
51    #[must_use]
52    pub fn context(self, context: impl Display + Send + Sync + 'static) -> Self {
53        Self::from(WithContext {
54            context: Box::new(context),
55            error: self,
56        })
57    }
58
59    /// Returns whether the stored error is an instance of `E`, looking through
60    /// [`context`](Self::context) layers.
61    #[inline]
62    #[must_use]
63    pub fn is<E>(&self) -> bool
64    where
65        E: std::error::Error + Send + Sync + 'static,
66    {
67        self.downcast_ref::<E>().is_some()
68    }
69
70    /// Attempts to move the concrete error out of this error object, looking
71    /// through [`context`](Self::context) layers and discarding their
72    /// messages.
73    ///
74    /// This never clones the stored error, so it only succeeds while this is
75    /// the sole handle to it. To fall back to a clone instead, use
76    /// [`downcast_cloned`](Self::downcast_cloned).
77    ///
78    /// # Errors
79    ///
80    /// Returns a [`DowncastError`] carrying the original error back, and
81    /// saying whether the stored error is not an instance of `E` or a clone
82    /// of it is still alive.
83    pub fn downcast<E>(self) -> Result<E, DowncastError>
84    where
85        E: std::error::Error + Send + Sync + 'static,
86    {
87        if !self.is::<E>() {
88            return Err(DowncastError::new(DowncastFailure::Mismatch, self));
89        }
90        self.unwrap::<E>()
91    }
92
93    /// Attempts to downcast the error object to a concrete type, looking
94    /// through [`context`](Self::context) layers.
95    ///
96    /// The stored error is moved out when this is the sole handle to it and
97    /// cloned otherwise.
98    ///
99    /// # Errors
100    ///
101    /// Returns `Err(Self)` if the stored error is not an instance of `E`,
102    /// handing back the original error unchanged.
103    pub fn downcast_cloned<E>(self) -> Result<E, Self>
104    where
105        E: std::error::Error + Send + Sync + Clone + 'static,
106    {
107        match self.downcast::<E>() {
108            Ok(error) => Ok(error),
109            Err(failed) => {
110                let error = failed.into_error();
111                match error.downcast_ref::<E>() {
112                    Some(error) => Ok(error.clone()),
113                    None => Err(error),
114                }
115            }
116        }
117    }
118
119    /// Borrows the concrete error, looking through
120    /// [`context`](Self::context) layers.
121    #[must_use]
122    pub fn downcast_ref<E>(&self) -> Option<&E>
123    where
124        E: std::error::Error + Send + Sync + 'static,
125    {
126        let error = self.0.error();
127        match error.downcast_ref::<E>() {
128            Some(error) => Some(error),
129            None => error
130                .downcast_ref::<WithContext>()
131                .and_then(|context| context.error.downcast_ref::<E>()),
132        }
133    }
134
135    /// Mutably borrows the concrete error, looking through
136    /// [`context`](Self::context) layers.
137    ///
138    /// # Errors
139    ///
140    /// Returns a [`DowncastFailure`] saying whether the stored error is not
141    /// an instance of `E` or a clone of it is still alive: shared contents
142    /// cannot be handed out mutably.
143    pub fn downcast_mut<E>(&mut self) -> Result<&mut E, DowncastFailure>
144    where
145        E: std::error::Error + Send + Sync + 'static,
146    {
147        if !self.is::<E>() {
148            return Err(DowncastFailure::Mismatch);
149        }
150        self.unwrap_mut::<E>()
151    }
152
153    /// Moves the stored error out of an error known to hold an `E`, either
154    /// directly or behind context layers.
155    fn unwrap<E>(self) -> Result<E, DowncastError>
156    where
157        E: std::error::Error + Send + Sync + 'static,
158    {
159        let any = match self.0.into_any().downcast::<Object<E>>() {
160            Ok(object) => {
161                return match Arc::try_unwrap(object) {
162                    Ok(object) => Ok(object.error),
163                    Err(shared) => Err(DowncastError::new(DowncastFailure::Shared, Self(shared))),
164                };
165            }
166            Err(any) => any,
167        };
168        let Ok(object) = any.downcast::<Object<WithContext>>() else {
169            unreachable!("an error holding an `E` is an `Object<E>` or a context layer");
170        };
171        match Arc::try_unwrap(object) {
172            Ok(object) => {
173                let WithContext { context, error } = object.error;
174                error.unwrap::<E>().map_err(|failed| {
175                    // Put the layer back so the original error is handed back
176                    // intact, backtrace included.
177                    let error = Self(Arc::new(Object {
178                        error: WithContext {
179                            context,
180                            error: failed.error,
181                        },
182                        backtrace: object.backtrace,
183                    }));
184                    DowncastError::new(failed.failure, error)
185                })
186            }
187            Err(shared) => Err(DowncastError::new(DowncastFailure::Shared, Self(shared))),
188        }
189    }
190
191    /// Borrows the stored error of an error known to hold an `E`, either
192    /// directly or behind context layers.
193    fn unwrap_mut<E>(&mut self) -> Result<&mut E, DowncastFailure>
194    where
195        E: std::error::Error + Send + Sync + 'static,
196    {
197        let error = Arc::get_mut(&mut self.0)
198            .ok_or(DowncastFailure::Shared)?
199            .error_mut();
200        if error.is::<E>() {
201            return Ok(error
202                .downcast_mut::<E>()
203                .expect("the stored error was checked to be an `E`"));
204        }
205        error
206            .downcast_mut::<WithContext>()
207            .expect("an error holding an `E` is an `E` or a context layer")
208            .error
209            .unwrap_mut::<E>()
210    }
211
212    /// The backtrace captured when the error was built. Capture follows the
213    /// `RUST_BACKTRACE` and `RUST_LIB_BACKTRACE` environment variables; check
214    /// [`Backtrace::status`] before relying on its contents.
215    #[inline]
216    #[must_use]
217    pub fn backtrace(&self) -> &Backtrace {
218        self.0.backtrace()
219    }
220
221    /// The stored error followed by each of its
222    /// [`source`](std::error::Error::source)s, outermost first.
223    pub fn chain(&self) -> impl Iterator<Item = &(dyn std::error::Error + 'static)> {
224        let mut next: Option<&(dyn std::error::Error + 'static)> = Some(self.0.error());
225        std::iter::from_fn(move || {
226            let current = next?;
227            next = current.source();
228            Some(current)
229        })
230    }
231}
232
233/// Prints the stored error's message; the alternate form (`{:#}`) appends
234/// each source after a colon.
235impl Display for Error {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        Display::fmt(self.0.error(), f)?;
238        if f.alternate() {
239            for cause in self.chain().skip(1) {
240                write!(f, ": {cause}")?;
241            }
242        }
243        Ok(())
244    }
245}
246
247/// Prints the stored error's message, its sources, and the backtrace when one
248/// was captured; the alternate form (`{:#?}`) prints the stored error's own
249/// [`Debug`] output instead.
250impl Debug for Error {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        if f.alternate() {
253            return Debug::fmt(self.0.error(), f);
254        }
255        write!(f, "{}", self.0.error())?;
256        let mut causes = self.chain().skip(1).peekable();
257        if causes.peek().is_some() {
258            write!(f, "\n\nCaused by:")?;
259            for cause in causes {
260                write!(f, "\n    {cause}")?;
261            }
262        }
263        let backtrace = self.backtrace();
264        if backtrace.status() == BacktraceStatus::Captured {
265            write!(f, "\n\nStack backtrace:\n{backtrace}")?;
266        }
267        Ok(())
268    }
269}
270
271/// Borrows the stored error as an error trait object.
272impl Deref for Error {
273    type Target = dyn std::error::Error + Send + Sync + 'static;
274
275    fn deref(&self) -> &Self::Target {
276        self.0.error()
277    }
278}
279
280impl<E> From<E> for Error
281where
282    E: std::error::Error + Send + Sync + 'static,
283{
284    fn from(error: E) -> Self {
285        Self(Arc::new(Object {
286            error,
287            backtrace: Backtrace::capture(),
288        }))
289    }
290}
291
292/// Clones a borrowed error, so `?` works on a `Result<T, &Error>`.
293impl From<&Error> for Error {
294    fn from(error: &Error) -> Self {
295        error.clone()
296    }
297}
298
299impl From<Error> for Box<dyn std::error::Error + Send + Sync + 'static> {
300    fn from(error: Error) -> Self {
301        Box::new(BoxedError(error))
302    }
303}
304
305/// Why a downcast could not hand out the stored error.
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum DowncastFailure {
308    /// The stored error is not an instance of the requested type.
309    Mismatch,
310    /// A clone of the error is still alive, so its contents can neither be
311    /// moved out nor borrowed mutably.
312    Shared,
313}
314
315impl Display for DowncastFailure {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        match self {
318            Self::Mismatch => f.write_str("the error is not an instance of the requested type"),
319            Self::Shared => f.write_str("the error is still shared with a clone"),
320        }
321    }
322}
323
324impl std::error::Error for DowncastFailure {}
325
326/// A failed [`Error::downcast`], carrying the original error back.
327///
328/// Deliberately not an error type itself: the only way past it is
329/// [`into_error`](Self::into_error), so the original error cannot be
330/// replaced by a shell describing the failure through `?` or `.into()`.
331#[derive(Debug)]
332pub struct DowncastError {
333    failure: DowncastFailure,
334    error: Error,
335}
336
337impl DowncastError {
338    fn new(failure: DowncastFailure, error: Error) -> Self {
339        Self { failure, error }
340    }
341
342    /// Why the downcast failed.
343    #[must_use]
344    pub fn failure(&self) -> DowncastFailure {
345        self.failure
346    }
347
348    /// The error the downcast was attempted on.
349    #[must_use]
350    pub fn error(&self) -> &Error {
351        &self.error
352    }
353
354    /// Hands the original error back.
355    #[must_use]
356    pub fn into_error(self) -> Error {
357        self.error
358    }
359}
360
361/// The object an [`Error`] points at: a concrete error together with the
362/// backtrace captured when it was wrapped.
363///
364/// A trait object rather than `dyn std::error::Error` so the error can be
365/// handed back by value: [`into_any`](Self::into_any) recovers the concrete
366/// object, which [`Arc::try_unwrap`] then moves out of.
367trait ErrorObject: Send + Sync + 'static {
368    /// The stored error.
369    fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
370
371    /// The stored error, mutably.
372    fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static);
373
374    /// The backtrace captured when the error was wrapped.
375    fn backtrace(&self) -> &Backtrace;
376
377    /// Erases the object to `dyn Any`, from which its concrete type can be
378    /// recovered with [`Arc::downcast`].
379    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
380}
381
382/// The concrete [`ErrorObject`] holding an error of type `E`.
383struct Object<E> {
384    error: E,
385    backtrace: Backtrace,
386}
387
388impl<E> ErrorObject for Object<E>
389where
390    E: std::error::Error + Send + Sync + 'static,
391{
392    fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
393        &self.error
394    }
395
396    fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
397        &mut self.error
398    }
399
400    fn backtrace(&self) -> &Backtrace {
401        &self.backtrace
402    }
403
404    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
405        self
406    }
407}
408
409/// The error behind [`Error::msg`].
410struct Message<M>(M);
411
412impl<M: Display> Display for Message<M> {
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        Display::fmt(&self.0, f)
415    }
416}
417
418impl<M: Debug> Debug for Message<M> {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        Debug::fmt(&self.0, f)
421    }
422}
423
424impl<M: Display + Debug> std::error::Error for Message<M> {}
425
426/// The error behind [`Error::context`]: a message in front of the error it
427/// wraps. Not generic over the message so a downcast can look through it.
428struct WithContext {
429    context: Box<dyn Display + Send + Sync>,
430    error: Error,
431}
432
433impl Display for WithContext {
434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435        Display::fmt(&self.context, f)
436    }
437}
438
439impl Debug for WithContext {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        f.debug_struct("WithContext")
442            .field("context", &format_args!("{}", self.context))
443            .field("error", &self.error)
444            .finish()
445    }
446}
447
448impl std::error::Error for WithContext {
449    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
450        Some(self.error.0.error())
451    }
452}
453
454/// The error behind [`Error::from_boxed`], since a box of a trait object is
455/// not itself an [`std::error::Error`].
456struct Boxed(Box<dyn std::error::Error + Send + Sync + 'static>);
457
458impl Display for Boxed {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        Display::fmt(&self.0, f)
461    }
462}
463
464impl Debug for Boxed {
465    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466        Debug::fmt(&self.0, f)
467    }
468}
469
470impl std::error::Error for Boxed {
471    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
472        self.0.source()
473    }
474}
475
476/// An [`Error`] in the shape of an [`std::error::Error`], for conversion
477/// into a boxed error. [`Error`] cannot implement the trait itself: the
478/// blanket conversion from every [`std::error::Error`] would then apply to
479/// it and collide with the reflexive `From<Error> for Error`.
480struct BoxedError(Error);
481
482impl Display for BoxedError {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        Display::fmt(&self.0, f)
485    }
486}
487
488impl Debug for BoxedError {
489    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490        Debug::fmt(&self.0, f)
491    }
492}
493
494impl std::error::Error for BoxedError {
495    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
496        self.0.0.error().source()
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503
504    /// A payload type to recover from wrapped errors.
505    #[derive(Debug, Clone)]
506    struct Failure(&'static str);
507
508    impl Display for Failure {
509        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510            f.write_str(self.0)
511        }
512    }
513
514    impl std::error::Error for Failure {}
515
516    /// A payload type with a source.
517    #[derive(Debug)]
518    struct WithCause {
519        cause: Failure,
520    }
521
522    impl Display for WithCause {
523        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524            f.write_str("outer")
525        }
526    }
527
528    impl std::error::Error for WithCause {
529        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
530            Some(&self.cause)
531        }
532    }
533
534    #[test]
535    fn thiserror_transparent_forwards_display_and_source() {
536        #[derive(Debug, thiserror::Error)]
537        enum AppError {
538            #[error(transparent)]
539            Topcoat(#[from] Error),
540        }
541
542        let cause = Failure("boom");
543        let expected_cause = cause.to_string();
544        let error = Error::from(WithCause { cause });
545        let expected_message = error.to_string();
546        let app_error = AppError::from(error);
547
548        assert_eq!(app_error.to_string(), expected_message);
549        let source = std::error::Error::source(&app_error).unwrap();
550        assert!(source.is::<Failure>());
551        assert_eq!(source.to_string(), expected_cause);
552        assert!(source.source().is_none());
553
554        let app_error = AppError::from(Error::from(Failure("leaf")));
555        assert!(std::error::Error::source(&app_error).is_none());
556    }
557
558    #[test]
559    fn thiserror_from_preserves_the_stored_error_as_source() {
560        #[derive(Debug, thiserror::Error)]
561        enum AppError {
562            #[error("operation failed")]
563            Topcoat(#[from] Error),
564        }
565
566        let cause = Failure("boom");
567        let expected_cause = cause.to_string();
568        let error = Error::from(WithCause { cause });
569        let expected_message = error.to_string();
570        let app_error = AppError::from(error);
571
572        let source = std::error::Error::source(&app_error).unwrap();
573        assert!(source.is::<WithCause>());
574        assert_eq!(source.to_string(), expected_message);
575        let cause = source.source().unwrap();
576        assert!(cause.is::<Failure>());
577        assert_eq!(cause.to_string(), expected_cause);
578        assert!(cause.source().is_none());
579    }
580
581    #[test]
582    fn the_error_is_two_words_wide() {
583        assert_eq!(size_of::<Error>(), 2 * size_of::<usize>());
584        assert_eq!(size_of::<Result<()>>(), 2 * size_of::<usize>());
585    }
586
587    #[test]
588    fn clone_shares_the_underlying_error() {
589        let error = Error::from(Failure("boom"));
590        let clone = error.clone();
591
592        assert_eq!(error.to_string(), "boom");
593        assert_eq!(clone.to_string(), "boom");
594        assert_eq!(
595            clone.downcast_ref::<Failure>().map(|failure| failure.0),
596            Some("boom")
597        );
598    }
599
600    #[test]
601    fn msg_builds_an_error_from_a_message() {
602        let error = Error::msg(format!("boom {}", 1));
603        assert_eq!(error.to_string(), "boom 1");
604        assert!(error.is::<Message<String>>());
605    }
606
607    #[test]
608    fn downcast_extracts_a_unique_error() {
609        let failure = Error::from(Failure("boom")).downcast::<Failure>().unwrap();
610        assert_eq!(failure.0, "boom");
611    }
612
613    #[test]
614    fn downcast_reports_a_mismatch() {
615        let error = Error::from(std::io::Error::other("boom"));
616        let failed = error.downcast::<Failure>().unwrap_err();
617        assert_eq!(failed.failure(), DowncastFailure::Mismatch);
618        assert_eq!(failed.into_error().to_string(), "boom");
619    }
620
621    #[test]
622    fn downcast_reports_a_shared_error() {
623        let error = Error::from(Failure("boom"));
624        let clone = error.clone();
625
626        let failed = error.downcast::<Failure>().unwrap_err();
627        assert_eq!(failed.failure(), DowncastFailure::Shared);
628        let error = failed.into_error();
629        assert_eq!(error.to_string(), "boom");
630
631        // Dropping the share makes the error unique again.
632        drop(clone);
633        let failure = error.downcast::<Failure>().unwrap();
634        assert_eq!(failure.0, "boom");
635    }
636
637    #[test]
638    fn downcast_moves_out_of_a_context_layer() {
639        let error = Error::from(Failure("boom")).context("loading");
640        let failure = error.downcast::<Failure>().unwrap();
641        assert_eq!(failure.0, "boom");
642    }
643
644    #[test]
645    fn downcast_hands_a_context_layer_back_intact() {
646        let inner = Error::from(Failure("boom"));
647        let shared = inner.clone();
648        let error = inner.context("loading");
649
650        let failed = error.downcast::<Failure>().unwrap_err();
651        assert_eq!(failed.failure(), DowncastFailure::Shared);
652        let error = failed.into_error();
653        assert_eq!(format!("{error:#}"), "loading: boom");
654        drop(shared);
655    }
656
657    #[test]
658    fn downcast_cloned_extracts_a_unique_error() {
659        let failure = Error::from(Failure("boom"))
660            .downcast_cloned::<Failure>()
661            .unwrap();
662        assert_eq!(failure.0, "boom");
663    }
664
665    #[test]
666    fn downcast_cloned_clones_a_shared_error() {
667        let error = Error::from(Failure("boom"));
668        let clone = error.clone();
669
670        let failure = error.downcast_cloned::<Failure>().unwrap();
671        assert_eq!(failure.0, "boom");
672        assert_eq!(clone.to_string(), "boom");
673    }
674
675    #[test]
676    fn downcast_cloned_keeps_a_non_matching_error() {
677        let error = Error::from(std::io::Error::other("boom"));
678        let clone = error.clone();
679
680        let error = error.downcast_cloned::<Failure>().unwrap_err();
681        assert_eq!(error.to_string(), "boom");
682        drop(clone);
683    }
684
685    #[test]
686    fn downcast_mut_mutates_a_unique_error() {
687        let mut error = Error::from(Failure("boom"));
688        error.downcast_mut::<Failure>().unwrap().0 = "bang";
689        assert_eq!(error.to_string(), "bang");
690    }
691
692    #[test]
693    fn downcast_mut_mutates_through_a_context_layer() {
694        let mut error = Error::from(Failure("boom")).context("loading");
695        error.downcast_mut::<Failure>().unwrap().0 = "bang";
696        assert_eq!(format!("{error:#}"), "loading: bang");
697    }
698
699    #[test]
700    fn downcast_mut_reports_a_mismatch() {
701        let mut error = Error::from(std::io::Error::other("boom"));
702        assert_eq!(
703            error.downcast_mut::<Failure>().unwrap_err(),
704            DowncastFailure::Mismatch
705        );
706    }
707
708    #[test]
709    fn downcast_mut_reports_a_shared_error() {
710        let mut error = Error::from(Failure("boom"));
711        let clone = error.clone();
712
713        assert_eq!(
714            error.downcast_mut::<Failure>().unwrap_err(),
715            DowncastFailure::Shared
716        );
717
718        drop(clone);
719        assert!(error.downcast_mut::<Failure>().is_ok());
720    }
721
722    #[test]
723    fn context_wraps_the_message_and_keeps_the_error_reachable() {
724        let error = Error::from(Failure("boom")).context("loading");
725
726        assert_eq!(error.to_string(), "loading");
727        assert_eq!(format!("{error:#}"), "loading: boom");
728        assert_eq!(error.chain().count(), 2);
729        assert!(error.is::<Failure>());
730        assert_eq!(error.downcast_cloned::<Failure>().unwrap().0, "boom");
731    }
732
733    #[test]
734    fn display_and_debug_walk_the_chain() {
735        let error = Error::from(WithCause {
736            cause: Failure("boom"),
737        });
738
739        assert_eq!(error.to_string(), "outer");
740        assert_eq!(format!("{error:#}"), "outer: boom");
741        assert!(format!("{error:?}").starts_with("outer\n\nCaused by:\n    boom"));
742        assert!(format!("{error:#?}").starts_with("WithCause"));
743    }
744
745    #[test]
746    fn box_conversion_keeps_the_message_and_source() {
747        let error = Error::from(WithCause {
748            cause: Failure("boom"),
749        });
750        let shared = error.clone();
751
752        let boxed: Box<dyn std::error::Error + Send + Sync> = error.into();
753        assert_eq!(boxed.to_string(), "outer");
754        assert_eq!(
755            boxed.source().map(ToString::to_string),
756            Some(String::from("boom"))
757        );
758        drop(shared);
759    }
760
761    #[test]
762    fn boxed_errors_convert_with_their_chain() {
763        let boxed: Box<dyn std::error::Error + Send + Sync> = Box::new(WithCause {
764            cause: Failure("boom"),
765        });
766        let error = Error::from_boxed(boxed);
767        assert_eq!(error.to_string(), "outer");
768        assert_eq!(format!("{error:#}"), "outer: boom");
769    }
770
771    #[cfg(feature = "anyhow")]
772    #[test]
773    fn anyhow_errors_convert_with_their_chain() {
774        let error = Error::from_anyhow(anyhow::anyhow!("boom").context("loading"));
775        assert_eq!(error.to_string(), "loading");
776        assert_eq!(format!("{error:#}"), "loading: boom");
777    }
778}