stable_eyre/
lib.rs

1//! This library provides a custom [`eyre::EyreHandler`] type for usage with [`eyre`] that provides
2//! all the same features as `eyre::DefaultHandler` except it works on stable by capturing a
3//! [`backtrace::Backtrace`] via backtrace-rs.
4//!
5//! ## Setup
6//!
7//! Add the following to your toml file:
8//!
9//! ```toml
10//! [dependencies]
11//! stable-eyre = "0.2"
12//! ```
13//!
14//! Then install the hook handler before constructing any `eyre::Report` types.
15//!
16//! # Example
17//!
18//! ```rust,should_panic
19//! use stable_eyre::eyre::{eyre, Report, WrapErr};
20//!
21//! fn main() -> Result<(), Report> {
22//!     stable_eyre::install()?;
23//!
24//!     let e: Report = eyre!("oh no this program is just bad!");
25//!
26//!     Err(e).wrap_err("usage example successfully experienced a failure")
27//! }
28//! ```
29//!
30//! [`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html
31//! [`eyre`]: https://docs.rs/eyre
32//! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
33#![doc(html_root_url = "https://docs.rs/stable-eyre/0.2.2")]
34#![warn(
35    missing_debug_implementations,
36    missing_docs,
37    rust_2018_idioms,
38    unreachable_pub,
39    bad_style,
40    const_err,
41    dead_code,
42    improper_ctypes,
43    non_shorthand_field_patterns,
44    no_mangle_generic_items,
45    overflowing_literals,
46    path_statements,
47    patterns_in_fns_without_body,
48    private_in_public,
49    unconditional_recursion,
50    unused,
51    unused_allocation,
52    unused_comparisons,
53    unused_parens,
54    while_true
55)]
56
57pub use eyre;
58#[doc(hidden)]
59pub use eyre::{Report, Result};
60
61use ::backtrace::Backtrace;
62use indenter::indented;
63use std::{env, error::Error, iter};
64
65/// Extension trait to extract a backtrace from an `eyre::Report`, assuming
66/// stable-eyre's hook is installed.
67pub trait BacktraceExt {
68    /// Returns a reference to the captured backtrace if one exists
69    ///
70    /// # Example
71    ///
72    /// ```rust
73    /// use stable_eyre::{BacktraceExt, eyre::eyre};
74    /// stable_eyre::install();
75    /// std::env::set_var("RUST_BACKTRACE", "1");
76    ///
77    /// let report = eyre!("capture a report");
78    /// assert!(report.backtrace().is_some());
79    /// ```
80    fn backtrace(&self) -> Option<&Backtrace>;
81}
82
83impl BacktraceExt for eyre::Report {
84    fn backtrace(&self) -> Option<&Backtrace> {
85        self.handler()
86            .downcast_ref::<crate::Handler>()
87            .and_then(|handler| handler.backtrace.as_ref())
88    }
89}
90
91/// A custom context type for capturing backtraces on stable with `eyre`
92#[derive(Debug)]
93pub struct Handler {
94    backtrace: Option<Backtrace>,
95}
96
97impl eyre::EyreHandler for Handler {
98    fn debug(
99        &self,
100        error: &(dyn Error + 'static),
101        f: &mut core::fmt::Formatter<'_>,
102    ) -> core::fmt::Result {
103        use core::fmt::Write as _;
104
105        if f.alternate() {
106            return core::fmt::Debug::fmt(error, f);
107        }
108
109        write!(f, "{}", error)?;
110
111        if let Some(cause) = error.source() {
112            write!(f, "\n\nCaused by:")?;
113
114            let multiple = cause.source().is_some();
115            let errors = iter::successors(Some(cause), |e| (*e).source());
116
117            for (n, error) in errors.enumerate() {
118                writeln!(f)?;
119                if multiple {
120                    write!(indented(f).ind(n), "{}", error)?;
121                } else {
122                    write!(indented(f), "{}", error)?;
123                }
124            }
125        }
126
127        if let Some(backtrace) = &self.backtrace {
128            write!(f, "\n\nStack backtrace:\n{:?}", backtrace)?;
129        }
130
131        Ok(())
132    }
133}
134
135/// Builder for customizing the behavior of the global error report hook
136#[derive(Debug)]
137pub struct HookBuilder {
138    capture_backtrace_by_default: bool,
139}
140
141impl HookBuilder {
142    #[allow(unused_variables)]
143    fn make_handler(&self, error: &(dyn Error + 'static)) -> Handler {
144        let backtrace = if self.capture_enabled() {
145            Some(Backtrace::new())
146        } else {
147            None
148        };
149
150        Handler { backtrace }
151    }
152
153    fn capture_enabled(&self) -> bool {
154        env::var("RUST_LIB_BACKTRACE")
155            .or_else(|_| env::var("RUST_BACKTRACE"))
156            .map(|val| val != "0")
157            .unwrap_or(self.capture_backtrace_by_default)
158    }
159
160    /// Configures the default capture mode for `Backtraces` in error reports
161    pub fn capture_backtrace_by_default(mut self, cond: bool) -> Self {
162        self.capture_backtrace_by_default = cond;
163        self
164    }
165
166    /// Install the given hook as the global error report hook
167    pub fn install(self) -> Result<()> {
168        crate::eyre::set_hook(Box::new(move |e| Box::new(self.make_handler(e))))?;
169
170        Ok(())
171    }
172}
173
174impl Default for HookBuilder {
175    fn default() -> Self {
176        Self {
177            capture_backtrace_by_default: false,
178        }
179    }
180}
181
182/// Install the default error report hook provided by `stable-eyre`
183///
184/// # Details
185///
186/// This function must be called to enable the customization of `eyre::Report`
187/// provided by `stable-eyre`. This function should be called early, ideally
188/// before any errors could be encountered.
189///
190/// Only the first install will succeed. Calling this function after another
191/// report handler has been installed will cause an error. **Note**: This
192/// function _must_ be called before any `eyre::Report`s are constructed to
193/// prevent the default handler from being installed.
194pub fn install() -> Result<()> {
195    HookBuilder::default().install()
196}