Skip to main content

error2/backtrace/
mod.rs

1mod double_locations;
2mod message;
3
4use std::{any, error::Error, fmt, mem};
5
6use self::{double_locations::DoubleLocations, message::Message};
7use crate::Location;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub(crate) enum BakctraceEntry {
12    Message(Message),
13    Locations(DoubleLocations),
14}
15
16const _: () = {
17    ["Size of `Message`"][mem::size_of::<Message>() - 24usize];
18    ["Size of `DoubleLocations`"][mem::size_of::<DoubleLocations>() - 24usize];
19    ["`Message` and `DoubleLocations` must have the same size"]
20        [mem::size_of::<Message>() - mem::size_of::<DoubleLocations>()];
21    ["Size of `BakctraceEntry`"][mem::size_of::<BakctraceEntry>() - 32usize];
22};
23
24/// A backtrace that tracks error propagation through the call stack.
25///
26/// `Backtrace` stores a complete history of an error's journey, including:
27/// - Error messages and type names
28/// - Source code locations (file:line:column)
29/// - Nested error chains
30///
31/// # Overview
32///
33/// Unlike `std::backtrace::Backtrace` which captures the call stack,
34/// `error2::Backtrace` tracks the logical error propagation path through
35/// your application, showing where errors were created, converted, and propagated.
36///
37/// # Creation
38///
39/// Backtraces are typically created automatically by the `#[derive(Error2)]` macro:
40///
41/// ```
42/// use error2::prelude::*;
43///
44/// #[derive(Debug, Error2)]
45/// #[error2(display("my error"))]
46/// struct MyError {
47///     backtrace: Backtrace,
48/// }
49/// ```
50///
51/// # Accessing Error Information
52///
53/// Use `.error_message()` to get a formatted error chain:
54///
55/// ```
56/// # use error2::prelude::*;
57/// # use std::io;
58/// # #[derive(Debug, Error2)]
59/// # #[error2(display("test error"))]
60/// # struct TestError { source: io::Error, backtrace: Backtrace }
61/// # fn operation() -> Result<(), TestError> {
62/// #     let err = io::Error::new(io::ErrorKind::NotFound, "file not found");
63/// #     Err(err).context(TestError2)
64/// # }
65/// use regex::Regex;
66///
67/// if let Err(e) = operation() {
68///     let msg = e.backtrace().error_message();
69///
70///     // Full error format with location tracking:
71///     // TestError: test error
72///     //     at /path/to/file.rs:128:14
73///     // std::io::error::Error: file not found
74///
75///     let re = Regex::new(concat!(
76///         r"(?s)^.+TestError: test error",
77///         r"\n    at .+\.rs:\d+:\d+",
78///         r"\nstd::io::error::Error: file not found$",
79///     ))
80///     .unwrap();
81///     assert!(re.is_match(msg.as_ref()));
82/// }
83/// ```
84///
85/// # Location Tracking
86///
87/// The backtrace automatically records the location where an error is first created.
88/// To track propagation through your call stack, you must manually call:
89/// - `.attach()` - Records the current location when an error passes through
90/// - `.attach_location()` - Records a specific location
91///
92/// When converting errors:
93/// - `.context()` automatically captures where the conversion happens
94/// - `.build()` / `.fail()` automatically captures where the root error is created
95///
96/// These methods use `#[track_caller]` to capture the caller's location without manual intervention.
97///
98/// # Example with Nested Errors
99///
100/// ```
101/// use std::io;
102///
103/// use error2::prelude::*;
104/// use regex::Regex;
105///
106/// #[derive(Debug, Error2)]
107/// pub enum ConfigError {
108///     #[error2(display("Failed to read: {path}"))]
109///     Read {
110///         path: String,
111///         source: io::Error,
112///         backtrace: Backtrace,
113///     },
114/// }
115///
116/// #[derive(Debug, Error2)]
117/// pub enum AppError {
118///     #[error2(display("Config error"))]
119///     Config { source: ConfigError },
120/// }
121///
122/// fn read_config() -> Result<String, ConfigError> {
123///     std::fs::read_to_string("config.toml").context(Read2 {
124///         path: "config.toml",
125///     })
126/// }
127///
128/// fn start_app() -> Result<String, AppError> {
129///     read_config().context(Config2)
130/// }
131///
132/// if let Err(e) = start_app() {
133///     let msg = e.backtrace().error_message();
134///
135///     // Full error chain format:
136///     // AppError: Config error
137///     //     at /path/to/file.rs:184:19
138///     // ConfigError: Failed to read: config.toml
139///     //     at /path/to/file.rs:178:44
140///     // std::io::error::Error: No such file or directory (os error 2)
141///
142///     let re = Regex::new(concat!(
143///         r"(?s)^.+AppError: Config error",
144///         r"\n    at .+\.rs:\d+:\d+",
145///         r"\n.+ConfigError: Failed to read: config\.toml",
146///         r"\n    at .+\.rs:\d+:\d+",
147///         r"\nstd::io::error::Error: No such file or directory \(os error 2\)$",
148///     ))
149///     .unwrap();
150///     assert!(re.is_match(msg.as_ref()));
151/// }
152/// ```
153#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155pub struct Backtrace {
156    entries: Vec<BakctraceEntry>,
157}
158
159impl fmt::Debug for Backtrace {
160    #[inline]
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        write!(f, "Backtrace {{ ... }}")
163    }
164}
165
166impl Backtrace {
167    #[doc(hidden)]
168    #[inline]
169    pub const fn new() -> Self {
170        Self {
171            entries: Vec::new(),
172        }
173    }
174
175    #[doc(hidden)]
176    pub fn with_head<E: Error>(source: &E) -> Backtrace {
177        fn inner(type_name: &'static str, display: String) -> Backtrace {
178            Backtrace {
179                entries: vec![BakctraceEntry::Message(Message::new(type_name, display))],
180            }
181        }
182
183        let type_name = any::type_name::<E>();
184        let display = source.to_string();
185
186        inner(type_name, display)
187    }
188
189    pub(crate) fn push_error(
190        &mut self,
191        type_name: &'static str,
192        display: String,
193        location: Location,
194    ) {
195        self.entries
196            .push(BakctraceEntry::Message(Message::new(type_name, display)));
197
198        self.entries
199            .push(BakctraceEntry::Locations(DoubleLocations::new(location)));
200    }
201
202    pub(crate) const fn head_and_entries(&self) -> (Option<&Message>, &[BakctraceEntry]) {
203        let entries = self.entries.as_slice();
204
205        match entries {
206            [] => (None, &[]),
207            [BakctraceEntry::Locations(_), ..] => unreachable!(),
208            [BakctraceEntry::Message(first), rest @ ..] => match rest {
209                [] => (Some(first), &[]),
210                [BakctraceEntry::Message(_second), ..] => (Some(first), rest),
211                [BakctraceEntry::Locations(_second), ..] => (None, entries),
212            },
213        }
214    }
215
216    pub(crate) fn push_location(&mut self, location: Location) {
217        debug_assert!(matches!(
218            self.entries.first(),
219            Some(BakctraceEntry::Message(_))
220        ));
221
222        let entry = self
223            .entries
224            .last_mut()
225            .expect("there is must at least one message entry");
226
227        match entry {
228            BakctraceEntry::Locations(locations) if !locations.is_full() => {
229                let l = locations.push(location);
230                debug_assert!(l.is_none());
231            }
232            BakctraceEntry::Message(_) | BakctraceEntry::Locations(_) => {
233                self.entries
234                    .push(BakctraceEntry::Locations(DoubleLocations::new(location)));
235            }
236        }
237    }
238
239    /// Returns a formatted string containing the complete error chain.
240    ///
241    /// This method produces a human-readable representation of the entire error
242    /// history, including all nested errors and their locations.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// # use error2::prelude::*;
248    /// # use std::io;
249    /// # #[derive(Debug, Error2)]
250    /// # #[error2(display("test error"))]
251    /// # struct TestError { source: io::Error, backtrace: Backtrace }
252    /// # fn operation() -> Result<(), TestError> {
253    /// #     let err = io::Error::new(io::ErrorKind::NotFound, "file not found");
254    /// #     Err(err).context(TestError2)
255    /// # }
256    /// use regex::Regex;
257    ///
258    /// if let Err(e) = operation() {
259    ///     let msg = e.backtrace().error_message();
260    ///
261    ///     // Full error format with location tracking:
262    ///     // TestError: test error
263    ///     //     at /path/to/file.rs:197:14
264    ///     // std::io::error::Error: file not found
265    ///
266    ///     let re = Regex::new(concat!(
267    ///         r"(?s)^.+TestError: test error",
268    ///         r"\n    at .+\.rs:\d+:\d+",
269    ///         r"\nstd::io::error::Error: file not found$",
270    ///     ))
271    ///     .unwrap();
272    ///     assert!(re.is_match(msg.as_ref()));
273    /// }
274    /// ```
275    pub fn error_message(&self) -> Box<str> {
276        crate::extract_error_message(self)
277    }
278}