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
292impl From<Error> for Box<dyn std::error::Error + Send + Sync + 'static> {
293    fn from(error: Error) -> Self {
294        Box::new(BoxedError(error))
295    }
296}
297
298/// Why a downcast could not hand out the stored error.
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum DowncastFailure {
301    /// The stored error is not an instance of the requested type.
302    Mismatch,
303    /// A clone of the error is still alive, so its contents can neither be
304    /// moved out nor borrowed mutably.
305    Shared,
306}
307
308impl Display for DowncastFailure {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        match self {
311            Self::Mismatch => f.write_str("the error is not an instance of the requested type"),
312            Self::Shared => f.write_str("the error is still shared with a clone"),
313        }
314    }
315}
316
317impl std::error::Error for DowncastFailure {}
318
319/// A failed [`Error::downcast`], carrying the original error back.
320///
321/// Deliberately not an error type itself: the only way past it is
322/// [`into_error`](Self::into_error), so the original error cannot be
323/// replaced by a shell describing the failure through `?` or `.into()`.
324#[derive(Debug)]
325pub struct DowncastError {
326    failure: DowncastFailure,
327    error: Error,
328}
329
330impl DowncastError {
331    fn new(failure: DowncastFailure, error: Error) -> Self {
332        Self { failure, error }
333    }
334
335    /// Why the downcast failed.
336    #[must_use]
337    pub fn failure(&self) -> DowncastFailure {
338        self.failure
339    }
340
341    /// The error the downcast was attempted on.
342    #[must_use]
343    pub fn error(&self) -> &Error {
344        &self.error
345    }
346
347    /// Hands the original error back.
348    #[must_use]
349    pub fn into_error(self) -> Error {
350        self.error
351    }
352}
353
354/// The object an [`Error`] points at: a concrete error together with the
355/// backtrace captured when it was wrapped.
356///
357/// A trait object rather than `dyn std::error::Error` so the error can be
358/// handed back by value: [`into_any`](Self::into_any) recovers the concrete
359/// object, which [`Arc::try_unwrap`] then moves out of.
360trait ErrorObject: Send + Sync + 'static {
361    /// The stored error.
362    fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
363
364    /// The stored error, mutably.
365    fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static);
366
367    /// The backtrace captured when the error was wrapped.
368    fn backtrace(&self) -> &Backtrace;
369
370    /// Erases the object to `dyn Any`, from which its concrete type can be
371    /// recovered with [`Arc::downcast`].
372    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
373}
374
375/// The concrete [`ErrorObject`] holding an error of type `E`.
376struct Object<E> {
377    error: E,
378    backtrace: Backtrace,
379}
380
381impl<E> ErrorObject for Object<E>
382where
383    E: std::error::Error + Send + Sync + 'static,
384{
385    fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
386        &self.error
387    }
388
389    fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
390        &mut self.error
391    }
392
393    fn backtrace(&self) -> &Backtrace {
394        &self.backtrace
395    }
396
397    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
398        self
399    }
400}
401
402/// The error behind [`Error::msg`].
403struct Message<M>(M);
404
405impl<M: Display> Display for Message<M> {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        Display::fmt(&self.0, f)
408    }
409}
410
411impl<M: Debug> Debug for Message<M> {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        Debug::fmt(&self.0, f)
414    }
415}
416
417impl<M: Display + Debug> std::error::Error for Message<M> {}
418
419/// The error behind [`Error::context`]: a message in front of the error it
420/// wraps. Not generic over the message so a downcast can look through it.
421struct WithContext {
422    context: Box<dyn Display + Send + Sync>,
423    error: Error,
424}
425
426impl Display for WithContext {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        Display::fmt(&self.context, f)
429    }
430}
431
432impl Debug for WithContext {
433    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434        f.debug_struct("WithContext")
435            .field("context", &format_args!("{}", self.context))
436            .field("error", &self.error)
437            .finish()
438    }
439}
440
441impl std::error::Error for WithContext {
442    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
443        Some(self.error.0.error())
444    }
445}
446
447/// The error behind [`Error::from_boxed`], since a box of a trait object is
448/// not itself an [`std::error::Error`].
449struct Boxed(Box<dyn std::error::Error + Send + Sync + 'static>);
450
451impl Display for Boxed {
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        Display::fmt(&self.0, f)
454    }
455}
456
457impl Debug for Boxed {
458    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459        Debug::fmt(&self.0, f)
460    }
461}
462
463impl std::error::Error for Boxed {
464    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
465        self.0.source()
466    }
467}
468
469/// An [`Error`] in the shape of an [`std::error::Error`], for conversion
470/// into a boxed error. [`Error`] cannot implement the trait itself: the
471/// blanket conversion from every [`std::error::Error`] would then apply to
472/// it and collide with the reflexive `From<Error> for Error`.
473struct BoxedError(Error);
474
475impl Display for BoxedError {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        Display::fmt(&self.0, f)
478    }
479}
480
481impl Debug for BoxedError {
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        Debug::fmt(&self.0, f)
484    }
485}
486
487impl std::error::Error for BoxedError {
488    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
489        self.0.0.error().source()
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    /// A payload type to recover from wrapped errors.
498    #[derive(Debug, Clone)]
499    struct Failure(&'static str);
500
501    impl Display for Failure {
502        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503            f.write_str(self.0)
504        }
505    }
506
507    impl std::error::Error for Failure {}
508
509    /// A payload type with a source.
510    #[derive(Debug)]
511    struct WithCause {
512        cause: Failure,
513    }
514
515    impl Display for WithCause {
516        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517            f.write_str("outer")
518        }
519    }
520
521    impl std::error::Error for WithCause {
522        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
523            Some(&self.cause)
524        }
525    }
526
527    #[test]
528    fn thiserror_transparent_forwards_display_and_source() {
529        #[derive(Debug, thiserror::Error)]
530        enum AppError {
531            #[error(transparent)]
532            Topcoat(#[from] Error),
533        }
534
535        let cause = Failure("boom");
536        let expected_cause = cause.to_string();
537        let error = Error::from(WithCause { cause });
538        let expected_message = error.to_string();
539        let app_error = AppError::from(error);
540
541        assert_eq!(app_error.to_string(), expected_message);
542        let source = std::error::Error::source(&app_error).unwrap();
543        assert!(source.is::<Failure>());
544        assert_eq!(source.to_string(), expected_cause);
545        assert!(source.source().is_none());
546
547        let app_error = AppError::from(Error::from(Failure("leaf")));
548        assert!(std::error::Error::source(&app_error).is_none());
549    }
550
551    #[test]
552    fn thiserror_from_preserves_the_stored_error_as_source() {
553        #[derive(Debug, thiserror::Error)]
554        enum AppError {
555            #[error("operation failed")]
556            Topcoat(#[from] Error),
557        }
558
559        let cause = Failure("boom");
560        let expected_cause = cause.to_string();
561        let error = Error::from(WithCause { cause });
562        let expected_message = error.to_string();
563        let app_error = AppError::from(error);
564
565        let source = std::error::Error::source(&app_error).unwrap();
566        assert!(source.is::<WithCause>());
567        assert_eq!(source.to_string(), expected_message);
568        let cause = source.source().unwrap();
569        assert!(cause.is::<Failure>());
570        assert_eq!(cause.to_string(), expected_cause);
571        assert!(cause.source().is_none());
572    }
573
574    #[test]
575    fn the_error_is_two_words_wide() {
576        assert_eq!(size_of::<Error>(), 2 * size_of::<usize>());
577        assert_eq!(size_of::<Result<()>>(), 2 * size_of::<usize>());
578    }
579
580    #[test]
581    fn clone_shares_the_underlying_error() {
582        let error = Error::from(Failure("boom"));
583        let clone = error.clone();
584
585        assert_eq!(error.to_string(), "boom");
586        assert_eq!(clone.to_string(), "boom");
587        assert_eq!(
588            clone.downcast_ref::<Failure>().map(|failure| failure.0),
589            Some("boom")
590        );
591    }
592
593    #[test]
594    fn msg_builds_an_error_from_a_message() {
595        let error = Error::msg(format!("boom {}", 1));
596        assert_eq!(error.to_string(), "boom 1");
597        assert!(error.is::<Message<String>>());
598    }
599
600    #[test]
601    fn downcast_extracts_a_unique_error() {
602        let failure = Error::from(Failure("boom")).downcast::<Failure>().unwrap();
603        assert_eq!(failure.0, "boom");
604    }
605
606    #[test]
607    fn downcast_reports_a_mismatch() {
608        let error = Error::from(std::io::Error::other("boom"));
609        let failed = error.downcast::<Failure>().unwrap_err();
610        assert_eq!(failed.failure(), DowncastFailure::Mismatch);
611        assert_eq!(failed.into_error().to_string(), "boom");
612    }
613
614    #[test]
615    fn downcast_reports_a_shared_error() {
616        let error = Error::from(Failure("boom"));
617        let clone = error.clone();
618
619        let failed = error.downcast::<Failure>().unwrap_err();
620        assert_eq!(failed.failure(), DowncastFailure::Shared);
621        let error = failed.into_error();
622        assert_eq!(error.to_string(), "boom");
623
624        // Dropping the share makes the error unique again.
625        drop(clone);
626        let failure = error.downcast::<Failure>().unwrap();
627        assert_eq!(failure.0, "boom");
628    }
629
630    #[test]
631    fn downcast_moves_out_of_a_context_layer() {
632        let error = Error::from(Failure("boom")).context("loading");
633        let failure = error.downcast::<Failure>().unwrap();
634        assert_eq!(failure.0, "boom");
635    }
636
637    #[test]
638    fn downcast_hands_a_context_layer_back_intact() {
639        let inner = Error::from(Failure("boom"));
640        let shared = inner.clone();
641        let error = inner.context("loading");
642
643        let failed = error.downcast::<Failure>().unwrap_err();
644        assert_eq!(failed.failure(), DowncastFailure::Shared);
645        let error = failed.into_error();
646        assert_eq!(format!("{error:#}"), "loading: boom");
647        drop(shared);
648    }
649
650    #[test]
651    fn downcast_cloned_extracts_a_unique_error() {
652        let failure = Error::from(Failure("boom"))
653            .downcast_cloned::<Failure>()
654            .unwrap();
655        assert_eq!(failure.0, "boom");
656    }
657
658    #[test]
659    fn downcast_cloned_clones_a_shared_error() {
660        let error = Error::from(Failure("boom"));
661        let clone = error.clone();
662
663        let failure = error.downcast_cloned::<Failure>().unwrap();
664        assert_eq!(failure.0, "boom");
665        assert_eq!(clone.to_string(), "boom");
666    }
667
668    #[test]
669    fn downcast_cloned_keeps_a_non_matching_error() {
670        let error = Error::from(std::io::Error::other("boom"));
671        let clone = error.clone();
672
673        let error = error.downcast_cloned::<Failure>().unwrap_err();
674        assert_eq!(error.to_string(), "boom");
675        drop(clone);
676    }
677
678    #[test]
679    fn downcast_mut_mutates_a_unique_error() {
680        let mut error = Error::from(Failure("boom"));
681        error.downcast_mut::<Failure>().unwrap().0 = "bang";
682        assert_eq!(error.to_string(), "bang");
683    }
684
685    #[test]
686    fn downcast_mut_mutates_through_a_context_layer() {
687        let mut error = Error::from(Failure("boom")).context("loading");
688        error.downcast_mut::<Failure>().unwrap().0 = "bang";
689        assert_eq!(format!("{error:#}"), "loading: bang");
690    }
691
692    #[test]
693    fn downcast_mut_reports_a_mismatch() {
694        let mut error = Error::from(std::io::Error::other("boom"));
695        assert_eq!(
696            error.downcast_mut::<Failure>().unwrap_err(),
697            DowncastFailure::Mismatch
698        );
699    }
700
701    #[test]
702    fn downcast_mut_reports_a_shared_error() {
703        let mut error = Error::from(Failure("boom"));
704        let clone = error.clone();
705
706        assert_eq!(
707            error.downcast_mut::<Failure>().unwrap_err(),
708            DowncastFailure::Shared
709        );
710
711        drop(clone);
712        assert!(error.downcast_mut::<Failure>().is_ok());
713    }
714
715    #[test]
716    fn context_wraps_the_message_and_keeps_the_error_reachable() {
717        let error = Error::from(Failure("boom")).context("loading");
718
719        assert_eq!(error.to_string(), "loading");
720        assert_eq!(format!("{error:#}"), "loading: boom");
721        assert_eq!(error.chain().count(), 2);
722        assert!(error.is::<Failure>());
723        assert_eq!(error.downcast_cloned::<Failure>().unwrap().0, "boom");
724    }
725
726    #[test]
727    fn display_and_debug_walk_the_chain() {
728        let error = Error::from(WithCause {
729            cause: Failure("boom"),
730        });
731
732        assert_eq!(error.to_string(), "outer");
733        assert_eq!(format!("{error:#}"), "outer: boom");
734        assert!(format!("{error:?}").starts_with("outer\n\nCaused by:\n    boom"));
735        assert!(format!("{error:#?}").starts_with("WithCause"));
736    }
737
738    #[test]
739    fn box_conversion_keeps_the_message_and_source() {
740        let error = Error::from(WithCause {
741            cause: Failure("boom"),
742        });
743        let shared = error.clone();
744
745        let boxed: Box<dyn std::error::Error + Send + Sync> = error.into();
746        assert_eq!(boxed.to_string(), "outer");
747        assert_eq!(
748            boxed.source().map(ToString::to_string),
749            Some(String::from("boom"))
750        );
751        drop(shared);
752    }
753
754    #[test]
755    fn boxed_errors_convert_with_their_chain() {
756        let boxed: Box<dyn std::error::Error + Send + Sync> = Box::new(WithCause {
757            cause: Failure("boom"),
758        });
759        let error = Error::from_boxed(boxed);
760        assert_eq!(error.to_string(), "outer");
761        assert_eq!(format!("{error:#}"), "outer: boom");
762    }
763
764    #[cfg(feature = "anyhow")]
765    #[test]
766    fn anyhow_errors_convert_with_their_chain() {
767        let error = Error::from_anyhow(anyhow::anyhow!("boom").context("loading"));
768        assert_eq!(error.to_string(), "loading");
769        assert_eq!(format!("{error:#}"), "loading: boom");
770    }
771}