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