display_error_chain/
lib.rs

1//! A lightweight library for displaying errors and their sources.
2//!
3//! A sample output:
4//!
5//! ```rust
6//! macro_rules! impl_error {
7//!     // ...
8//! #    ($ty:ty, $display:expr, $source:expr) => {
9//! #        impl ::std::fmt::Display for $ty {
10//! #            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
11//! #                write!(f, "{}", $display)
12//! #            }
13//! #        }
14//! #
15//! #        impl ::std::error::Error for $ty {
16//! #            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
17//! #                $source
18//! #            }
19//! #        }
20//! #    };
21//! }
22//!
23//! // `TopLevel` is caused by a `MidLevel`.
24//! #[derive(Debug)]
25//! struct TopLevel;
26//! impl_error!(TopLevel, "top level", Some(&MidLevel));
27//!
28//! // `MidLevel` is caused by a `LowLevel`.
29//! #[derive(Debug)]
30//! struct MidLevel;
31//! impl_error!(MidLevel, "mid level", Some(&LowLevel));
32//!
33//! // `LowLevel` is the cause itself.
34//! #[derive(Debug)]
35//! struct LowLevel;
36//! impl_error!(LowLevel, "low level", None);
37//!
38//! // Now let's see how it works:
39//! let formatted = display_error_chain::DisplayErrorChain::new(&TopLevel).to_string();
40//! assert_eq!(
41//!     formatted,
42//!     "\
43//!top level
44//!Caused by:
45//!   -> mid level
46//!   -> low level"
47//! );
48//!
49//! // Or with `.chain()` helper:
50//! use display_error_chain::ErrorChainExt as _;
51//! let formatted = TopLevel.chain().to_string();
52//! assert_eq!(
53//!     formatted,
54//!     "\
55//!top level
56//!Caused by:
57//!   -> mid level
58//!   -> low level"
59//! );
60//!
61//! // Or even with `.into_chain()` helper to consume the error.
62//! use display_error_chain::ErrorChainExt as _;
63//! let formatted = TopLevel.into_chain().to_string();
64//! assert_eq!(
65//!     formatted,
66//!     "\
67//!top level
68//!Caused by:
69//!   -> mid level
70//!   -> low level"
71//! );
72//! ```
73
74use std::{error::Error, fmt};
75
76mod result_ext;
77pub use result_ext::ResultExt;
78
79/// Provides an [fmt::Display] implementation for an error as a chain.
80///
81/// ```rust
82/// use display_error_chain::{DisplayErrorChain, ErrorChainExt as _};
83///
84/// // Let's set up a custom error. Normally one would use `snafu` or
85/// // something similar to avoid the boilerplate.
86/// #[derive(Debug)]
87/// enum CustomError {
88///     NoCause,
89///     IO { source: std::io::Error },
90/// }
91///
92/// // Custom display implementation (which doesn't take error
93/// // sources into consideration).
94/// impl std::fmt::Display for CustomError {
95///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96///         match self {
97///             CustomError::NoCause => {
98///                 write!(f, "No cause")
99///             }
100///             CustomError::IO { .. } => {
101///                 write!(f, "Some I/O")
102///             }
103///         }
104///     }
105/// }
106///
107/// impl std::error::Error for CustomError {
108///     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
109///         match self {
110///             CustomError::NoCause => None,
111///             CustomError::IO { source } => Some(source),
112///         }
113///     }
114/// }
115///
116/// // And finally let's see how `DisplayErrorChain` helps!
117/// let io = CustomError::IO {
118///     source: std::io::Error::new(std::io::ErrorKind::AlreadyExists, "wow"),
119/// };
120/// let formatted = DisplayErrorChain::new(&io).to_string();
121/// assert_eq!("Some I/O\nCaused by:\n  -> wow", formatted);
122///
123/// let no_cause = CustomError::NoCause;
124/// // You can also use a `.chain()` shortcut from the `ErrorChainExt` trait.
125/// let formatted = no_cause.chain().to_string();
126/// assert_eq!("No cause", formatted);
127///
128/// // or `.into_chain()` to make the `DisplayErrorChain` to consume the error.
129/// let formatted = no_cause.into_chain().to_string();
130/// assert_eq!("No cause", formatted);
131///
132/// // `from` or `into` will also work with both owned and referenced errors:
133/// let chain: DisplayErrorChain<_> = CustomError::NoCause.into();
134/// assert_eq!("No cause", chain.to_string());
135///
136/// let chain: DisplayErrorChain<_> = (&CustomError::NoCause).into();
137/// assert_eq!("No cause", chain.to_string());
138/// ```
139///
140/// Other standard traits (like [`Debug`][std::fmt::Debug], [`Clone`] and some
141/// others) are automatically derived for the convenience using the standard
142/// derive macros. If you need another trait, feel free to submit a PR and/or
143/// use the [`DisplayErrorChain::into_inner`] method to access the wrapped
144/// error.
145#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
146pub struct DisplayErrorChain<E>(E);
147
148impl<E: Error> From<E> for DisplayErrorChain<E> {
149    fn from(value: E) -> Self {
150        DisplayErrorChain::new(value)
151    }
152}
153
154impl<E> DisplayErrorChain<E>
155where
156    E: Error,
157{
158    /// Initializes the formatter with the error provided.
159    pub fn new(error: E) -> Self {
160        DisplayErrorChain(error)
161    }
162
163    /// Deconstructs the [`DisplayErrorChain`] and returns the wrapped error.
164    pub fn into_inner(self) -> E {
165        self.0
166    }
167}
168
169impl<E> fmt::Display for DisplayErrorChain<E>
170where
171    E: Error,
172{
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(f, "{}", self.0)?;
175
176        let mut cause_printed = false;
177        let mut source = self.0.source();
178        while let Some(cause) = source {
179            if !cause_printed {
180                cause_printed = true;
181                writeln!(f, "\nCaused by:")?;
182            } else {
183                writeln!(f)?
184            }
185            write!(f, "  -> {}", cause)?;
186            source = cause.source();
187        }
188        Ok(())
189    }
190}
191
192/// An extension trait for [`Error`] types to display their sources in a chain.
193pub trait ErrorChainExt {
194    /// Provides an [fmt::Display] implementation for an error as a chain.
195    fn chain(&self) -> DisplayErrorChain<&Self>;
196
197    /// Same as [`chain`][ErrorChainExt::chain], but consumes `self`.
198    fn into_chain(self) -> DisplayErrorChain<Self>
199    where
200        Self: Sized;
201}
202
203impl<E> ErrorChainExt for E
204where
205    E: Error,
206{
207    fn chain(&self) -> DisplayErrorChain<&Self> {
208        DisplayErrorChain::new(self)
209    }
210
211    fn into_chain(self) -> DisplayErrorChain<Self>
212    where
213        Self: Sized,
214    {
215        DisplayErrorChain::new(self)
216    }
217}