1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//! # source-chain
//!
//! Formats StdError with it's source chain
//!
//! ```rust
//! #[derive(Debug, thiserror::Error)]
//! enum Error {
//!     #[error("unknown file {1}")]
//!     UnknownFile(#[source] std::io::Error, &'static str),
//! }
//!
//! fn file_error() -> Result<String, Error> {
//!     let filename = "unknown-file.txt";
//!     std::fs::read_to_string(filename).map_err(|e| Error::UnknownFile(e, filename))
//! }
//!
//! let err = file_error().unwrap_err();
//! assert_eq!(
//!     source_chain::to_string(&err),
//!     "unknown file unknown-file.txt\nCaused by:\n\tNo such file or directory (os error 2)"
//! );
//!
//! let dyn_err: Box<dyn std::error::Error> = Box::new(err);
//! assert_eq!(
//!     // notice dereferencing
//!     source_chain::to_string(&*dyn_err),
//!     "unknown file unknown-file.txt\nCaused by:\n\tNo such file or directory (os error 2)"
//! );
//! ```

#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]

/// A helper function to format an error with its source chain.
///
/// This function works with both `&Error` and `Box<dyn Error>`. When passing a boxed error,
/// make sure to dereference it using `&*e`.
pub fn to_string(e: &(dyn std::error::Error + 'static)) -> String {
    use std::fmt::Write as _;

    let mut s = e.to_string();
    let mut current = e.source();
    if current.is_some() {
        s.push_str("\nCaused by:");
    }
    while let Some(cause) = current {
        write!(s, "\n\t{}", cause).ok();
        current = cause.source();
    }
    s
}