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}