error_stack/
lib.rs

1//! A context-aware error library with arbitrary attached user data.
2//!
3//! [![crates.io](https://img.shields.io/crates/v/error-stack)][crates.io]
4//! [![libs.rs](https://img.shields.io/badge/libs.rs-error--stack-orange)][libs.rs]
5//! [![rust-version](https://img.shields.io/static/v1?label=Rust&message=1.63.0/nightly-2024-07-08&color=blue)][rust-version]
6//! [![discord](https://img.shields.io/discord/840573247803097118)][discord]
7//!
8//! [crates.io]: https://crates.io/crates/error-stack
9//! [libs.rs]: https://lib.rs/crates/error-stack
10//! [rust-version]: https://www.rust-lang.org
11//! [discord]: https://hash.ai/discord?utm_medium=organic&utm_source=github_readme_hash-repo_error-stack
12//!
13//! # Overview
14//!
15//! `error-stack` is an error-handling library centered around the idea of building a [`Report`] of
16//! the error as it propagates. A [`Report`] is made up of two concepts:
17//!
18//!   1. Contexts
19//!   2. Attachments
20//!
21//! A [`Context`] is a view of the world, it helps describe how the current section of code
22//! interprets the error. This is used to capture how various scopes require differing levels of
23//! detail and understanding of the error as it propagates. A [`Report`] always captures the
24//! _current context_ in its generic argument.
25//!
26//! As the [`Report`] is built, various pieces of supporting information can be _attached_. These
27//! can be anything that can be shared between threads whether it be a supporting message or a
28//! custom-defined `Suggestion` struct.
29//!
30//! # Quick-Start Guide
31//!
32//! ## In a new project
33//!
34//! ```rust
35//! # #![allow(dead_code)]
36//! use error_stack::ResultExt;
37//! // using `thiserror` is not neccessary, but convenient
38//! use thiserror::Error;
39//!
40//! // Errors can enumerate variants users care about
41//! // but notably don't need to chain source/inner error manually.
42//! #[derive(Error, Debug)]
43//! enum AppError {
44//!     #[error("serious app error: {consequences}")]
45//!     Serious { consequences: &'static str },
46//!     #[error("trivial app error")]
47//!     Trivial,
48//! }
49//!
50//! type AppResult<T> = error_stack::Result<T, AppError>;
51//!
52//! // Errors can also be a plain `struct`, somewhat like in `anyhow`.
53//! #[derive(Error, Debug)]
54//! #[error("logic error")]
55//! struct LogicError;
56//!
57//! type LogicResult<T> = error_stack::Result<T, LogicError>;
58//!
59//! fn do_logic() -> LogicResult<()> {
60//!     Ok(())
61//! }
62//!
63//! fn main() -> AppResult<()> {
64//!     // `error-stack` requires developer to properly handle
65//!     // changing error contexts
66//!     do_logic().change_context(AppError::Serious {
67//!         consequences: "math no longer works",
68//!     })?;
69//!
70//!     Ok(())
71//! }
72//! ```
73//!
74//! ## Where to use a Report
75//!
76//! [`Report`] has been designed to be used as the [`Err`] variant of a `Result`. This crate
77//! provides a [`Result<E, C>`] type alias for convenience which uses [`Report<C>`] as the [`Err`]
78//! variant and can be used as a return type:
79//!
80//! ```rust
81//! # fn has_permission(_: (), _: ()) -> bool { true }
82//! # fn get_user() -> Result<(), AccessError> { Ok(()) }
83//! # fn get_resource() -> Result<(), AccessError> { Ok(()) }
84//! # #[derive(Debug)] enum AccessError { PermissionDenied((), ()) }
85//! # impl core::fmt::Display for AccessError {
86//! #    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
87//! # }
88//! # impl error_stack::Context for AccessError {}
89//! use error_stack::{ensure, Result};
90//!
91//! fn main() -> Result<(), AccessError> {
92//!     let user = get_user()?;
93//!     let resource = get_resource()?;
94//!
95//!     ensure!(
96//!         has_permission(user, resource),
97//!         AccessError::PermissionDenied(user, resource)
98//!     );
99//!
100//!     # const _: &str = stringify! {
101//!     ...
102//!     # }; Ok(())
103//! }
104//! ```
105//!
106//! ### Initializing a Report
107//!
108//! A [`Report`] can be created directly from anything that implements [`Context`] by using
109//! [`Report::new()`] or through any of the provided macros ([`report!`], [`bail!`], [`ensure!`]).
110//! Any [`Error`] can be used as a [`Context`], so it's possible to create [`Report`] from an
111//! existing [`Error`]:
112//!
113//! ```rust
114//! use std::{fs, io, path::Path};
115//!
116//! use error_stack::Report;
117//!
118//! // Note: For demonstration purposes this example does not use `error_stack::Result`.
119//! // As can be seen, it's possible to implicitly convert `io::Error` to `Report<io::Error>`
120//! fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
121//!     let content = fs::read_to_string(path)?;
122//!
123//!     # const _: &str = stringify! {
124//!     ...
125//!     # }; Ok(content)
126//! }
127//! # let report = read_file("test.txt").unwrap_err();
128//! # assert!(report.contains::<io::Error>());
129//! ```
130//!
131//! ## Using and Expanding the Report
132//!
133//! As mentioned, the library centers around the idea of building a [`Report`] as it propagates.
134//!
135//! ### Changing Context
136//!
137//! The generic parameter in [`Report`] is called the _current context_. When creating a new
138//! [`Report`], the [`Context`] that's provided will be set as the current context. The current
139//! context should encapsulate how the current code interprets the error. As the error propagates,
140//! it will cross boundaries where new information is available, and the previous level of detail is
141//! no longer applicable. These boundaries will often occur when crossing between major modules, or
142//! when execution crosses between crates. At this point the [`Report`] should start to operate in a
143//! new context. To change the context, [`Report::change_context()`] is used:
144//!
145//! (Again, for convenience, using [`ResultExt`] will do that on the [`Err`] variant)
146//!
147//! ```rust
148//! # use std::{fmt, fs, io, path::Path};
149//! use error_stack::{Context, Result, ResultExt};
150//! # pub type Config = String;
151//!
152//! #[derive(Debug)]
153//! struct ParseConfigError;
154//!
155//! impl fmt::Display for ParseConfigError {
156//!     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
157//!         fmt.write_str("could not parse configuration file")
158//!     }
159//! }
160//!
161//! // It's also possible to implement `Error` instead.
162//! impl Context for ParseConfigError {}
163//!
164//! // For clarification, this example is not using `error_stack::Result`.
165//! fn parse_config(path: impl AsRef<Path>) -> Result<Config, ParseConfigError> {
166//!     let content = fs::read_to_string(path.as_ref())
167//!         .change_context(ParseConfigError)?;
168//!
169//!     # const _: &str = stringify! {
170//!     ...
171//!     # }; Ok(content)
172//! }
173//! # let report = parse_config("test.txt").unwrap_err();
174//! # assert!(report.contains::<io::Error>());
175//! # assert!(report.contains::<ParseConfigError>());
176//! ```
177//!
178//! ### Building up the Report - Attachments
179//!
180//! Module/crate boundaries are not the only places where information can be embedded within the
181//! [`Report`] however. Additional information can be attached within the current context, whether
182//! this be a string, or any thread-safe object. These attachments are added by using
183//! [`Report::attach()`] and [`Report::attach_printable()`]:
184//!
185//! ```rust
186//! # // we only test the snapshot on nightly, therefore report is unused (so is render)
187//! # #![cfg_attr(not(nightly), allow(dead_code, unused_variables, unused_imports))]
188//! # use std::{fs, path::Path};
189//! # use error_stack::{Context, Report, ResultExt};
190//! # pub type Config = String;
191//! # #[derive(Debug)] struct ParseConfigError;
192//! # impl ParseConfigError { pub fn new() -> Self { Self } }
193//! # impl std::fmt::Display for ParseConfigError {
194//! #     fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195//! #         fmt.write_str("could not parse configuration file")
196//! #     }
197//! # }
198//! # impl Context for ParseConfigError {}
199//! # #[derive(Debug, PartialEq)]
200//! struct Suggestion(&'static str);
201//!
202//! fn parse_config(path: impl AsRef<Path>) -> Result<Config, Report<ParseConfigError>> {
203//!     let path = path.as_ref();
204//!
205//!     let content = fs::read_to_string(path)
206//!         .change_context(ParseConfigError::new())
207//!         .attach(Suggestion("use a file you can read next time!"))
208//!         .attach_printable_lazy(|| format!("could not read file {path:?}"))?;
209//!
210//!     Ok(content)
211//! }
212//! # let report = parse_config("test.txt").unwrap_err();
213//! # assert!(report.contains::<std::io::Error>());
214//! # assert_eq!(report.downcast_ref::<Suggestion>().unwrap(), &Suggestion("use a file you can read next time!"));
215//! # #[cfg(nightly)]
216//! # assert_eq!(report.request_ref::<Suggestion>().next().unwrap(), &Suggestion("use a file you can read next time!"));
217//! # #[cfg(nightly)]
218//! # assert_eq!(report.request_ref::<String>().next().unwrap(), "could not read file \"test.txt\"");
219//! # assert!(report.contains::<ParseConfigError>());
220//! #
221//! # Report::set_color_mode(error_stack::fmt::ColorMode::Emphasis);
222//! # fn render(value: String) -> String {
223//! #     let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?:  .*\n)*  .*").unwrap();
224//! #     let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap();
225//! #
226//! #     let value = backtrace.replace_all(&value, "backtrace no. $1\n  [redacted]");
227//! #     let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)");
228//! #
229//! #     ansi_to_html::convert(value.as_ref()).unwrap()
230//! # }
231//! #
232//! # #[cfg(nightly)]
233//! # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/lib__suggestion.snap")].assert_eq(&render(format!("{report:?}")));
234//! ```
235//!
236//! As seen above, there are ways on attaching more information to the [`Report`]: [`attach`] and
237//! [`attach_printable`]. These two functions behave similar, but the latter has a more restrictive
238//! bound on the attachment: [`Display`] and [`Debug`]. Depending on the function used, printing the
239//! [`Report`] will also use the [`Display`] and [`Debug`] traits to describe the attachment.
240//!
241//! This outputs something like:
242//!
243//! <pre>
244#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/lib__suggestion.snap"))]
245//! </pre>
246//!
247//! The `Suggestion` which was added via [`attach`] is not shown directly and only increases the
248//! counter of opaque attachments for the containing [`Context`]. The message which was passed to
249//! [`attach_printable`], however, is displayed in full. To be able to show attachments that have
250//! been added via [`attach`], one must make use of [hooks](#debug-and-display-hooks) instead.
251//!
252//! [`attach_printable`]: Report::attach_printable
253//! [`Display`]: core::fmt::Display
254//! [`Debug`]: core::fmt::Debug
255//!
256//! ### Multiple Errors
257//!
258//! [`Report`] supports the combination and propagation of multiple errors natively. This is useful
259//! in cases like parallel processing where multiple errors might happen independently from each
260//! other, in these use-cases you are able to use the implementations of [`Extend`] and
261//! [`extend_one()`] and are able to propagate all errors instead of just a single one.
262//!
263//! [`extend_one()`]: Report::extend_one
264//!
265//! ```rust
266//! # use std::{fs, path::Path};
267//! # use error_stack::Report;
268//! # pub type Config = String;
269//!
270//! fn parse_configs(paths: &[impl AsRef<Path>]) -> Result<Vec<Config>, Report<std::io::Error>> {
271//!     let mut configs = Vec::new();
272//!     let mut error: Option<Report<std::io::Error>> = None;
273//!
274//!     for path in paths {
275//!         let path = path.as_ref();
276//!
277//!         match fs::read_to_string(path) {
278//!             Ok(ok) => {
279//!                 configs.push(ok);
280//!             }
281//!             Err(err) => {
282//!                 if let Some(error) = error.as_mut() {
283//!                     error.extend_one(err.into());
284//!                 } else {
285//!                     error = Some(err.into());
286//!                 }
287//!             }
288//!         }
289//!     }
290//!
291//!     if let Some(error) = error {
292//!         return Err(error);
293//!     }
294//!
295//!     Ok(configs)
296//! }
297//!
298//! # let report = parse_configs(&["test.txt", "test2.txt", "test3.txt"]).unwrap_err();
299//! # assert!(report.contains::<std::io::Error>());
300//! ```
301//!
302//! # In-Depth Explanation
303//!
304//! ## Crate Philosophy
305//!
306//! This crate adds some development overhead in comparison to other error handling strategies,
307//! especially around creating custom root-errors (specifically `error-stack` does not allow using
308//! string-like types). The intention is that this reduces overhead at other parts of the process,
309//! whether that be implementing error-handling, debugging, or observability. The idea that
310//! underpins this is that errors should happen in well-scoped environments like reading a file or
311//! parsing a string into an integer. For these errors, a well-defined error type should be used
312//! (i.e. `io::Error` or `ParseIntError`) instead of creating an error from a string. Requiring a
313//! well-defined type forces users to be conscious about how they classify and group their
314//! **custom** error types, which improves their usability in error-_handling_.
315//!
316//! ### Improving Result::Err Types
317//!
318//! By capturing the current [`Context`] in the type parameter, return types in function signatures
319//! continue to explicitly capture the perspective of the current code. This means that **more often
320//! than not** the user is _forced_ to re-describe the error when entering a substantially different
321//! part of the code because the constraints of typed return types will require it. This will happen
322//! most often when crossing module/crate boundaries.
323//!
324//! An example of this is a `ConfigParseError` when produced when parsing a configuration file at
325//! a high-level in the code vs. the lower-level `io::Error` that occurs when reading the file from
326//! disk. The `io::Error` may no longer be valuable at the level of the code that's handling parsing
327//! a config, and re-framing the error in a new type allows the user to incorporate contextual
328//! information that's only available higher-up in the stack.
329//!
330//! ### Compatibility with other Libraries
331//!
332//! In `std` (or `nightly`) environments a blanket implementation for `Context` for any `Error` is
333//! provided. This blanket implementation for [`Error`] means `error-stack` is compatible with
334//! almost all other libraries that use the [`Error`] trait.
335//!
336//! This has the added benefit that migrating from other error libraries can often be incremental,
337//! as a lot of popular error library types will work within the [`Report`] struct.
338//!
339//! In addition, `error-stack` supports converting errors generated from the [`anyhow`] or [`eyre`]
340//! crate via [`IntoReportCompat`].
341//!
342//! ### Doing more
343//!
344//! Beyond making new [`Context`] types, the library supports the attachment of arbitrary
345//! thread-safe data. These attachments (and data that is [`provide`]d by the [`Context`] can be
346//! requested through [`Report::request_ref()`]. This gives a novel way to expand standard
347//! error-handling approaches, without decreasing the ergonomics of creating the actual error
348//! variants:
349//!
350//! ```rust
351//! # #![cfg_attr(not(nightly), allow(unused_variables, dead_code))]
352//! # use error_stack::Result;
353//! # struct Suggestion(&'static str);
354//! # fn parse_config(_: &str) -> Result<(), std::io::Error> { Ok(()) }
355//! fn main() {
356//!     if let Err(report) = parse_config("config.json") {
357//!         # #[cfg(nightly)]
358//!         for suggestion in report.request_ref::<Suggestion>() {
359//!             eprintln!("suggestion: {}", suggestion.0);
360//!         }
361//!     }
362//! }
363//! ```
364//!
365//! [`provide`]: Context::provide
366//!
367//! ## Additional Features
368//!
369//! The above examples will probably cover 90% of the common use case. This crate does have
370//! additional features for more specific scenarios:
371//!
372//! ### Automatic Backtraces
373//!
374//! When on a Rust 1.65 or later, [`Report`] will try to capture a [`Backtrace`] if `RUST_BACKTRACE`
375//! or `RUST_BACKTRACE_LIB` is set and the `backtrace` feature is enabled (by default this is the
376//! case). If on a nightly toolchain, it will use the [`Backtrace`] if provided by the base
377//! [`Context`], and will try to capture one otherwise.
378//!
379//! Unlike some other approaches, this does not require the user modifying their custom error types
380//! to be aware of backtraces, and doesn't require manual implementations to forward calls down any
381//! wrapped errors.
382//!
383//! ### No-Std compatible
384//!
385//! The complete crate is written for `no-std` environments, which can be used by setting
386//! `default-features = false` in _Cargo.toml_.
387//!
388//! ### Provider API
389//!
390//! This crate uses the [`Provider` API] to provide arbitrary data. This can be done either by
391//! [`attach`]ing them to a [`Report`] or by providing it directly when implementing [`Context`].
392//! The blanket implementation of [`Context`] for [`Error`] will provide any data provided by
393//! [`Error::provide`].
394//!
395//! To request a provided type, [`Report::request_ref`] or [`Report::request_value`] are used. Both
396//! return an iterator of all provided values with the specified type. The value, which was provided
397//! most recently will be returned first.
398//!
399//! [`attach`]: Report::attach
400//! [`Provider` API]: https://rust-lang.github.io/rfcs/3192-dyno.html
401//!
402//! ### Macros for Convenience
403//!
404//! Three macros are provided to simplify the generation of a [`Report`].
405//!
406//! - [`report!`] will only create a [`Report`] from its parameter. It will take into account if the
407//!   passed type itself is a [`Report`] or a [`Context`]. For the former case, it will retain the
408//!   details stored on a [`Report`], for the latter case it will create a new [`Report`] from the
409//!   [`Context`].
410//! - [`bail!`] acts like [`report!`] but also immediately returns the [`Report`] as [`Err`]
411//!   variant.
412//! - [`ensure!`] will check an expression and if it's evaluated to `false`, it will act like
413//!   [`bail!`].
414//!
415//! ### Span Traces
416//!
417//! The crate comes with built-in support for `tracing`s [`SpanTrace`]. If the `spantrace` feature
418//! is enabled and an [`ErrorLayer`] is set, a [`SpanTrace`] is either used when provided by the
419//! root [`Context`] or will be captured when creating the [`Report`].
420//!
421//! [`ErrorLayer`]: tracing_error::ErrorLayer
422//!
423//! ### Debug Hooks
424//!
425//! One can provide hooks for types added as attachments when the `std` feature is enabled. These
426//! hooks are then used while formatting [`Report`]. This functionality is also used internally by
427//! `error-stack` to render [`Backtrace`], and [`SpanTrace`], which means overwriting and
428//! customizing them is as easy as providing another hook.
429//!
430//! You can add new hooks with [`Report::install_debug_hook`]. Refer to the module-level
431//! documentation of [`fmt`] for further information.
432//!
433//! ### Additional Adaptors
434//!
435//! [`ResultExt`] is a convenient wrapper around `Result<_, impl Context>` and `Result<_,
436//! Report<impl Context>`. It offers [`attach`](ResultExt::attach) and
437//! [`change_context`](ResultExt::change_context) on the [`Result`] directly, but also a lazy
438//! variant that receives a function which is only called if an error happens.
439//!
440//! In addition to [`ResultExt`], this crate also comes with [`FutureExt`], which provides the same
441//! functionality for [`Future`]s.
442//!
443//! [`Future`]: core::future::Future
444//!
445//! ### Colored output and charset selection
446//!
447//! You can override the color support by using the [`Report::set_color_mode`]. To override the
448//! charset used, you can use [`Report::set_charset`]. The default color mode is emphasis.
449//! The default charset is `UTF-8`.
450//!
451//! To automatically detect support if your target output supports unicode and colors you can check
452//! out the `detect.rs` example.
453//!
454//! ### Feature Flags
455//!
456//!  Feature       | Description                                                         | default
457//! ---------------|---------------------------------------------------------------------|----------
458//! `std`          | Enables support for [`Error`]                                       | enabled
459//! `backtrace`    | Enables automatic capturing of [`Backtrace`]s (requires Rust 1.65+) | enabled
460//! `spantrace`    | Enables automatic capturing of [`SpanTrace`]s                       | disabled
461//! `hooks`        | Enables hooks on `no-std` platforms using spin locks                | disabled
462//! `serde`        | Enables serialization support for [`Report`]                        | disabled
463//! `anyhow`       | Provides `into_report` to convert [`anyhow::Error`] to [`Report`]   | disabled
464//! `eyre`         | Provides `into_report` to convert [`eyre::Report`] to [`Report`]    | disabled
465//!
466//!
467//! [`set_debug_hook`]: Report::set_debug_hook
468//!
469//! [`Error`]: core::error::Error
470//! [`Error::provide`]: core::error::Error::provide
471//! [`Backtrace`]: std::backtrace::Backtrace
472//! [`Display`]: core::fmt::Display
473//! [`Debug`]: core::fmt::Debug
474//! [`SpanTrace`]: tracing_error::SpanTrace
475#![cfg_attr(not(feature = "std"), no_std)]
476#![cfg_attr(
477    nightly,
478    feature(error_generic_member_access),
479    allow(clippy::incompatible_msrv)
480)]
481#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))]
482#![cfg_attr(all(nightly, feature = "std"), feature(backtrace_frames))]
483#![cfg_attr(
484    not(miri),
485    doc(test(attr(deny(warnings, clippy::pedantic, clippy::nursery))))
486)]
487#![allow(unsafe_code)]
488// This is an error handling library producing Results, not Errors
489#![allow(clippy::missing_errors_doc)]
490
491extern crate alloc;
492
493pub mod future;
494pub mod iter;
495
496mod compat;
497mod frame;
498mod macros;
499mod report;
500mod result;
501
502mod context;
503mod error;
504pub mod fmt;
505#[cfg(any(feature = "std", feature = "hooks"))]
506mod hook;
507#[cfg(feature = "serde")]
508mod serde;
509
510pub use self::{
511    compat::IntoReportCompat,
512    context::Context,
513    frame::{AttachmentKind, Frame, FrameKind},
514    macros::*,
515    report::Report,
516    result::Result,
517};
518#[doc(inline)]
519pub use self::{future::FutureExt, result::ResultExt};
520
521#[cfg(test)]
522#[allow(dead_code)]
523mod tests {
524
525    use core::mem;
526
527    use crate::Report;
528
529    const fn assert_send<T: Send>() {}
530
531    const fn assert_sync<T: Sync>() {}
532
533    const fn assert_static<T: 'static>() {}
534
535    const fn report() {
536        assert_send::<Report<()>>();
537        assert_sync::<Report<()>>();
538        assert_static::<Report<()>>();
539    }
540
541    #[test]
542    fn test_size() {
543        assert_eq!(mem::size_of::<Report<()>>(), mem::size_of::<*const ()>());
544    }
545}