error2/lib.rs
1//! Comprehensive error handling library with detailed backtrace tracking.
2//!
3//! `error2` provides enhanced error handling capabilities for Rust applications,
4//! focusing on detailed error propagation tracking and ergonomic error conversion.
5//!
6//! # Features
7//!
8//! - **Backtrace Tracking** - Automatically capture error creation location; manually record propagation with `.attach()`
9//! - **Error Chaining** - Chain errors from different libraries while preserving context
10//! - **Derive Macro** - `#[derive(Error2)]` for easy error type creation
11//! - **Type Conversion** - `Result<T, E1> -> Result<T, E2>`, `Option<T> -> Result<T, E>` with `.context()`
12//! - **Type Erasure** - `BoxedError2` for anyhow-like ergonomics
13//!
14//! # Quick Start
15//!
16//! Add to your `Cargo.toml`:
17//!
18//! ```toml
19//! [dependencies]
20//! error2 = "0.13.2"
21//! ```
22//!
23//! Define your error types:
24//!
25//! ```
26//! use std::io;
27//!
28//! use error2::prelude::*;
29//!
30//! #[derive(Debug, Error2)]
31//! pub enum MyError {
32//! #[error2(display("IO error: {source}"))]
33//! Io {
34//! source: io::Error,
35//! backtrace: Backtrace,
36//! },
37//!
38//! #[error2(display("not found: {key}"))]
39//! NotFound { key: String, backtrace: Backtrace },
40//! }
41//! ```
42//!
43//! Use in your functions:
44//!
45//! ```
46//! # use error2::prelude::*;
47//! # use std::io;
48//! # #[derive(Debug, Error2)]
49//! # pub enum MyError {
50//! # #[error2(display("IO error"))]
51//! # Io { source: io::Error, backtrace: Backtrace },
52//! # #[error2(display("not found: {key}"))]
53//! # NotFound { key: String, backtrace: Backtrace },
54//! # }
55//! fn read_config(path: &str) -> Result<String, MyError> {
56//! // Convert io::Error to MyError::Io
57//! let content = std::fs::read_to_string(path).context(Io2)?;
58//!
59//! // Convert Option to Result
60//! let value = content
61//! .lines()
62//! .next()
63//! .context(NotFound2 { key: "first line" })?;
64//!
65//! Ok(value.to_string())
66//! }
67//! ```
68//!
69//! # Three Error Patterns
70//!
71//! Error2 supports three types of errors based on their field structure:
72//!
73//! ## 1. Root Error (New Error Origin)
74//!
75//! Use when creating a new error (not wrapping another):
76//!
77//! ```
78//! # use error2::prelude::*;
79//! #[derive(Debug, Error2)]
80//! pub enum AppError {
81//! #[error2(display("invalid ID: {id}"))]
82//! InvalidId {
83//! id: i64,
84//! backtrace: Backtrace, // Only backtrace, no source
85//! },
86//! }
87//! ```
88//!
89//! ## 2. Std Error (Wrapping std::error::Error)
90//!
91//! Use when wrapping standard library or third-party errors:
92//!
93//! ```
94//! # use error2::prelude::*;
95//! # use std::io;
96//! #[derive(Debug, Error2)]
97//! pub enum AppError {
98//! #[error2(display("file error"))]
99//! FileError {
100//! source: io::Error, // Wrapped error
101//! backtrace: Backtrace, // New backtrace
102//! },
103//! }
104//! ```
105//!
106//! ## 3. Error2 Error (Chaining Error2 Types)
107//!
108//! Use when wrapping another Error2 type (reuses backtrace):
109//!
110//! ```
111//! # use error2::prelude::*;
112//! # #[derive(Debug, Error2)]
113//! # #[error2(display("config error"))]
114//! # pub struct ConfigError { backtrace: Backtrace }
115//! #[derive(Debug, Error2)]
116//! pub enum AppError {
117//! #[error2(display("configuration failed"))]
118//! Config {
119//! source: ConfigError, // Only source, backtrace reused
120//! },
121//! }
122//! ```
123//!
124//! # Core Traits
125//!
126//! - [`Error2`] - Extends `std::error::Error` with backtrace support
127//! - [`Context`] - Type conversion: `Result<T, Source> -> Result<T, Target>`, `Option<T> -> Result<T, E>`
128//! - [`Attach`] - Record error propagation locations
129//! - [`RootError`] - Convenience methods for creating root errors
130//!
131//! # Type Erasure
132//!
133//! [`BoxedError2`] provides anyhow-like ergonomics:
134//!
135//! ```
136//! use error2::prelude::*;
137//!
138//! fn do_something() -> Result<(), BoxedError2> {
139//! std::fs::read_to_string("file.txt").context(ViaStd)?; // Convert to BoxedError2
140//! Ok(())
141//! }
142//! ```
143//!
144//! # Location Tracking
145//!
146//! Use `.attach()` to record error propagation:
147//!
148//! ```
149//! # use error2::prelude::*;
150//! # use std::io;
151//! # #[derive(Debug, Error2)]
152//! # #[error2(display("error"))]
153//! # struct MyError { source: io::Error, backtrace: Backtrace }
154//! # fn inner() -> Result<(), MyError> { Ok(()) }
155//! fn outer() -> Result<(), MyError> {
156//! let result = inner().attach()?; // Records this location
157//! Ok(result)
158//! }
159//! ```
160//!
161//! The backtrace shows multiple locations:
162//!
163//! ```
164//! # use error2::prelude::*;
165//! # use std::io;
166//! # #[derive(Debug, Error2)]
167//! # #[error2(display("error"))]
168//! # struct MyError { source: io::Error, backtrace: Backtrace }
169//! # fn inner() -> Result<(), MyError> {
170//! # let err = io::Error::new(io::ErrorKind::NotFound, "not found");
171//! # Err(err).context(MyError2)
172//! # }
173//! # fn outer() -> Result<(), MyError> { inner().attach() }
174//! # fn main() {
175//! use regex::Regex;
176//!
177//! if let Err(e) = outer() {
178//! let msg = e.backtrace().error_message();
179//!
180//! // Full error format with multiple locations:
181//! // MyError: error
182//! // at /path/to/file.rs:496:14
183//! // at /path/to/file.rs:498:45
184//! // std::io::error::Error: not found
185//!
186//! let re = Regex::new(concat!(
187//! r"(?s)^.+MyError: error",
188//! r"\n at .+\.rs:\d+:\d+",
189//! r"\n at .+\.rs:\d+:\d+",
190//! r"\nstd::io::error::Error: not found$",
191//! ))
192//! .unwrap();
193//! assert!(re.is_match(msg.as_ref()));
194//! }
195//! # }
196//! ```
197
198#![cfg_attr(docsrs, feature(doc_cfg))]
199
200mod _attach;
201mod backtrace;
202mod boxed;
203mod context;
204mod error2;
205mod extract;
206mod location;
207mod macros;
208mod root_error;
209mod str_id;
210
211/// Attach adapters for iterators, futures, and streams.
212pub mod attach;
213/// Error kind enum for downcasting [`BoxedError2`].
214///
215/// See [`ErrorKind`](kind::ErrorKind) for details.
216pub mod kind;
217/// Internal transformation traits (not for direct use).
218pub mod transform;
219
220/// Re-exports of commonly used types and traits.
221///
222/// Import with `use error2::prelude::*;` to get:
223/// - [`Error2`] trait
224/// - [`Context`], [`Attach`], [`RootError`] traits
225/// - [`Backtrace`], [`BoxedError2`] types
226/// - [`ViaRoot`], [`ViaStd`], [`ViaErr2`] wrappers
227/// - `#[derive(Error2)]` macro (if `derive` feature enabled)
228pub mod prelude {
229 #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
230 #[cfg(feature = "derive")]
231 pub use ::error2_derive::Error2;
232
233 // traits
234 pub use crate::{Attach as _, Context as _, RootError as _, error2::Error2};
235 // types
236 pub use crate::{Backtrace, BoxedError2, ViaErr2, ViaRoot, ViaStd};
237}
238
239#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
240#[cfg(feature = "derive")]
241pub use ::error2_derive::Error2;
242
243pub use self::{
244 _attach::Attach,
245 backtrace::Backtrace,
246 boxed::{BoxedError2, ViaErr2, ViaRoot, ViaStd},
247 context::Context,
248 error2::Error2,
249 location::Location,
250 root_error::RootError,
251};
252pub(crate) use self::{backtrace::BakctraceEntry, extract::extract_error_message, str_id::StrId};
253
254pub(crate) mod private {
255 #[derive(Debug, Clone, Copy)]
256 pub enum ViaPartial {}
257
258 #[derive(Debug, Clone, Copy)]
259 pub enum ViaFull {}
260}
261
262#[doc(hidden)]
263pub fn push_error<E: Error2 + ?Sized>(error: &mut E, location: Location) {
264 let display = error.to_string();
265 let backtrace = error.backtrace_mut();
266 let type_name = core::any::type_name::<E>();
267
268 backtrace.push_error(type_name, display, location);
269}