error_stack/report.rs
1use alloc::{boxed::Box, vec, vec::Vec};
2use core::{error::Error, marker::PhantomData, mem, ops::ControlFlow, panic::Location};
3#[cfg(feature = "backtrace")]
4use std::backtrace::{Backtrace, BacktraceStatus};
5#[cfg(feature = "std")]
6use std::process::ExitCode;
7
8#[cfg(feature = "spantrace")]
9use tracing_error::{SpanTrace, SpanTraceStatus};
10
11#[cfg(nightly)]
12use crate::iter::{RequestRef, RequestValue};
13use crate::{
14 Attachment, Frame, OpaqueAttachment,
15 context::SourceContext,
16 iter::{self, Frames},
17};
18
19/// Contains a [`Frame`] stack consisting of [`Error`] contexts and attachments.
20///
21/// Attachments can be added by using [`attach_opaque()`]. The [`Frame`] stack can be iterated by
22/// using [`frames()`].
23///
24/// When creating a `Report` by using [`new()`], the passed [`Error`] context is used to set the
25/// _current context_ on the `Report`. To provide a new one, use [`change_context()`].
26///
27/// Attachments, and objects [`provide`]d by a [`Error`] context, are directly retrievable by
28/// calling [`request_ref()`] or [`request_value()`].
29///
30/// ## Formatting
31///
32/// `Report` implements [`Display`] and [`Debug`]. When utilizing the [`Display`] implementation,
33/// the current context of the `Report` is printed, e.g. `println!("{report}")`. For the alternate
34/// [`Display`] output (`"{:#}"`), all [`Error`] contexts are printed. To print the full stack of
35/// [`Error`] contexts and attachments, use the [`Debug`] implementation (`"{:?}"`). To customize
36/// the output of the attachments in the [`Debug`] output, please see the [`error_stack::fmt`]
37/// module.
38///
39/// Please see the examples below for more information.
40///
41/// [`Display`]: core::fmt::Display
42/// [`error_stack::fmt`]: crate::fmt
43///
44/// ## Multiple Errors
45///
46/// `Report` comes in two variants: `Report<C>` which represents a single error context, and
47/// `Report<[C]>` which can represent multiple error contexts. To combine multiple errors,
48/// first convert a `Report<C>` to `Report<[C]>` using [`expand()`], then use [`push()`] to
49/// add additional errors. This allows for representing complex error scenarios with multiple
50/// related simultaneous errors.
51///
52/// [`expand()`]: Self::expand
53/// [`push()`]: Self::push
54///
55/// ## `Backtrace` and `SpanTrace`
56///
57/// `Report` is able to [`provide`] a [`Backtrace`] and a [`SpanTrace`], which can be retrieved by
58/// calling [`request_ref::<Backtrace>()`] or [`request_ref::<SpanTrace>()`]
59/// ([`downcast_ref::<SpanTrace>()`] on stable) respectively. If the root context [`provide`]s a
60/// [`Backtrace`] or a [`SpanTrace`], those are returned, otherwise, if configured, an attempt is
61/// made to capture them when creating a `Report`. To enable capturing of the backtrace, make sure
62/// `RUST_BACKTRACE` or `RUST_LIB_BACKTRACE` is set according to the [`Backtrace`
63/// documentation][`Backtrace`]. To enable capturing of the span trace, an [`ErrorLayer`] has to be
64/// enabled. Please also see the [Feature Flags] section. A single `Report` can have multiple
65/// [`Backtrace`]s and [`SpanTrace`]s, depending on the amount of related errors the `Report`
66/// consists of. Therefore it isn't guaranteed that [`request_ref()`] will only ever return a single
67/// [`Backtrace`] or [`SpanTrace`].
68///
69/// [`provide`]: core::error::Error::provide
70/// [`ErrorLayer`]: tracing_error::ErrorLayer
71/// [`attach_opaque()`]: Self::attach
72/// [`extend_one()`]: Self::extend_one
73/// [`new()`]: Self::new
74/// [`frames()`]: Self::frames
75/// [`change_context()`]: Self::change_context
76/// [`request_ref()`]: Self::request_ref
77/// [`request_value()`]: Self::request_value
78/// [`request_ref::<Backtrace>()`]: Self::request_ref
79/// [`request_ref::<SpanTrace>()`]: Self::request_ref
80/// [`downcast_ref::<SpanTrace>()`]: Self::downcast_ref
81/// [Feature Flags]: index.html#feature-flags
82///
83/// # Examples
84///
85/// ## Provide a context for an error
86///
87/// ```rust
88/// use error_stack::ResultExt;
89///
90/// # #[allow(dead_code)]
91/// # fn fake_main() -> Result<String, error_stack::Report<std::io::Error>> {
92/// let config_path = "./path/to/config.file";
93/// let content = std::fs::read_to_string(config_path)
94/// .attach_with(|| format!("failed to read config file {config_path:?}"))?;
95///
96/// # const _: &str = stringify! {
97/// ...
98/// # }; Ok(content) }
99/// ```
100///
101/// ## Enforce a context for an error
102///
103/// ```rust
104/// use std::{error::Error, fmt, path::{Path, PathBuf}};
105///
106/// use error_stack::{Report, ResultExt};
107///
108/// #[derive(Debug)]
109/// # #[derive(PartialEq)]
110/// enum RuntimeError {
111/// InvalidConfig(PathBuf),
112/// # }
113/// # const _: &str = stringify! {
114/// ...
115/// }
116/// # ;
117///
118/// #[derive(Debug)]
119/// enum ConfigError {
120/// IoError,
121/// # }
122/// # const _: &str = stringify! {
123/// ...
124/// }
125/// # ;
126///
127/// impl fmt::Display for RuntimeError {
128/// # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
129/// # const _: &str = stringify! {
130/// ...
131/// # };
132/// # let Self::InvalidConfig(path) = self;
133/// # write!(fmt, "could not parse {path:?}")
134/// # }
135/// }
136/// impl fmt::Display for ConfigError {
137/// # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
138/// # const _: &str = stringify! {
139/// ...
140/// # };
141/// # fmt.write_str("config file is invalid")
142/// # }
143/// }
144///
145/// impl Error for RuntimeError {}
146/// impl Error for ConfigError {}
147///
148/// # #[allow(unused_variables)]
149/// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<ConfigError>> {
150/// std::fs::read_to_string(path.as_ref()).change_context(ConfigError::IoError)
151/// }
152///
153/// fn main() -> Result<(), Report<RuntimeError>> {
154/// # fn fake_main() -> Result<(), Report<RuntimeError>> {
155/// let config_path = "./path/to/config.file";
156/// # #[allow(unused_variables)]
157/// let config = read_config(config_path)
158/// .change_context_lazy(|| RuntimeError::InvalidConfig(PathBuf::from(config_path)))?;
159///
160/// # const _: &str = stringify! {
161/// ...
162/// # };
163/// # Ok(()) }
164/// # let report = fake_main().unwrap_err();
165/// # assert!(report.contains::<ConfigError>());
166/// # assert_eq!(report.downcast_ref::<RuntimeError>(), Some(&RuntimeError::InvalidConfig(PathBuf::from("./path/to/config.file"))));
167/// # Report::set_color_mode(error_stack::fmt::ColorMode::Emphasis);
168/// # #[cfg(nightly)]
169/// # fn render(value: String) -> String {
170/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap();
171/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap();
172/// #
173/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]");
174/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)");
175/// #
176/// # ansi_to_html::convert(value.as_ref()).unwrap()
177/// # }
178/// # #[cfg(nightly)]
179/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_display__doc.snap")].assert_eq(&render(format!("{report}")));
180/// # #[cfg(nightly)]
181/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_display_alt__doc.snap")].assert_eq(&render(format!("{report:#}")));
182/// # #[cfg(nightly)]
183/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_debug__doc.snap")].assert_eq(&render(format!("{report:?}")));
184/// # Ok(())
185/// }
186/// ```
187///
188/// ## Formatting
189///
190/// For the example from above, the report could be formatted as follows:
191///
192/// If the [`Display`] implementation of `Report` will be invoked, this will print something like:
193/// <pre>
194#[cfg_attr(doc, doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_display__doc.snap")))]
195/// </pre>
196///
197/// If the alternate [`Display`] implementation of `Report` is invoked (`{report:#}`), this will
198/// print something like:
199/// <pre>
200#[cfg_attr(doc, doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_display_alt__doc.snap")))]
201/// </pre>
202///
203/// The [`Debug`] implementation of `Report` will print something like:
204/// <pre>
205#[cfg_attr(doc, doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/report_debug__doc.snap")))]
206/// </pre>
207///
208///
209/// ## Get the attached [`Backtrace`] and [`SpanTrace`]:
210///
211/// ```rust,should_panic
212/// use error_stack::{ResultExt, Report};
213///
214/// # #[allow(unused_variables)]
215/// # fn main() -> Result<(), Report<std::io::Error>> {
216/// let config_path = "./path/to/config.file";
217/// let content = std::fs::read_to_string(config_path)
218/// .attach_with(|| format!("failed to read config file {config_path:?}"));
219///
220/// let content = match content {
221/// Err(err) => {
222/// # #[cfg(nightly)]
223/// for backtrace in err.request_ref::<std::backtrace::Backtrace>() {
224/// println!("backtrace: {backtrace}");
225/// }
226///
227/// # #[cfg(nightly)]
228/// for span_trace in err.request_ref::<tracing_error::SpanTrace>() {
229/// println!("span trace: {span_trace}")
230/// }
231///
232/// return Err(err)
233/// }
234///
235/// Ok(ok) => ok
236/// };
237///
238/// # const _: &str = stringify! {
239/// ...
240/// # }; Ok(())
241/// # }
242/// ```
243#[must_use]
244#[expect(clippy::field_scoped_visibility_modifiers)]
245pub struct Report<C: ?Sized> {
246 // The vector is boxed as this implies a memory footprint equal to a single pointer size
247 // instead of three pointer sizes. Even for small `Result::Ok` variants, the `Result` would
248 // still have at least the size of `Report`, even at the happy path. It's unexpected, that
249 // creating or traversing a report will happen in the hot path, so a double indirection is
250 // a good trade-off.
251 #[expect(clippy::box_collection)]
252 pub(super) frames: Box<Vec<Frame>>,
253 _context: PhantomData<fn() -> *const C>,
254}
255
256impl<C> Report<C> {
257 /// Creates a new `Report<Context>` from a provided scope.
258 ///
259 /// If `context` does not provide [`Backtrace`]/[`SpanTrace`] then this attempts to capture
260 /// them directly. Please see the [`Backtrace` and `SpanTrace` section] of the `Report`
261 /// documentation for more information.
262 ///
263 /// [`Backtrace` and `SpanTrace` section]: #backtrace-and-spantrace
264 #[inline]
265 #[track_caller]
266 #[expect(clippy::missing_panics_doc, reason = "No panic possible")]
267 pub fn new(context: C) -> Self
268 where
269 C: Error + Send + Sync + 'static,
270 {
271 if let Some(mut current_source) = context.source() {
272 // The sources needs to be applied in reversed order, so we buffer them in a vector
273 let mut sources = vec![SourceContext::from_error(current_source)];
274 while let Some(source) = current_source.source() {
275 sources.push(SourceContext::from_error(source));
276 current_source = source;
277 }
278
279 // We create a new report with the oldest source as the base
280 let mut report = Report::from_frame(Frame::from_context(
281 sources.pop().expect("At least one context is guaranteed"),
282 Box::new([]),
283 ));
284 // We then extend the report with the rest of the sources
285 while let Some(source) = sources.pop() {
286 report = report.change_context(source);
287 }
288 // Finally, we add the new context passed to this function
289 return report.change_context(context);
290 }
291
292 // We don't have any sources, directly create the `Report` from the context
293 Self::from_frame(Frame::from_context(context, Box::new([])))
294 }
295
296 #[track_caller]
297 pub(crate) fn from_frame(frame: Frame) -> Self {
298 #[cfg(nightly)]
299 let location = core::error::request_ref::<Location>(&frame.as_error())
300 .is_none()
301 .then_some(Location::caller());
302
303 #[cfg(not(nightly))]
304 let location = Some(Location::caller());
305
306 #[cfg(all(nightly, feature = "backtrace"))]
307 let backtrace = core::error::request_ref::<Backtrace>(&frame.as_error())
308 .is_none_or(|backtrace| backtrace.status() != BacktraceStatus::Captured)
309 .then(Backtrace::capture);
310
311 #[cfg(all(not(nightly), feature = "backtrace"))]
312 let backtrace = Some(Backtrace::capture());
313
314 #[cfg(all(nightly, feature = "spantrace"))]
315 let span_trace = core::error::request_ref::<SpanTrace>(&frame.as_error())
316 .is_none_or(|span_trace| span_trace.status() != SpanTraceStatus::CAPTURED)
317 .then(SpanTrace::capture);
318
319 #[cfg(all(not(nightly), feature = "spantrace"))]
320 let span_trace = Some(SpanTrace::capture());
321
322 let mut report = Self {
323 frames: Box::new(vec![frame]),
324 _context: PhantomData,
325 };
326
327 if let Some(location) = location {
328 report = report.attach_opaque(*location);
329 }
330
331 #[cfg(feature = "backtrace")]
332 if let Some(backtrace) =
333 backtrace.filter(|bt| matches!(bt.status(), BacktraceStatus::Captured))
334 {
335 report = report.attach_opaque(backtrace);
336 }
337
338 #[cfg(feature = "spantrace")]
339 if let Some(span_trace) = span_trace.filter(|st| st.status() == SpanTraceStatus::CAPTURED) {
340 report = report.attach_opaque(span_trace);
341 }
342
343 report
344 }
345
346 /// Converts a `Report` with a single context into a `Report` with multiple contexts.
347 ///
348 /// This function allows for the transformation of a `Report<C>` into a `Report<[C]>`,
349 /// enabling the report to potentially hold multiple current contexts of the same type.
350 ///
351 /// # Example
352 ///
353 /// ```
354 /// use error_stack::Report;
355 ///
356 /// #[derive(Debug)]
357 /// struct SystemFailure;
358 ///
359 /// impl std::fmt::Display for SystemFailure {
360 /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 /// f.write_str("System failure occured")
362 /// }
363 /// }
364 ///
365 /// impl core::error::Error for SystemFailure {}
366 ///
367 /// // Type annotations are used here to illustrate the types used, these are not required
368 /// let failure: Report<SystemFailure> = Report::new(SystemFailure);
369 /// let mut failures: Report<[SystemFailure]> = failure.expand();
370 ///
371 /// assert_eq!(failures.current_frames().len(), 1);
372 ///
373 /// let another_failure = Report::new(SystemFailure);
374 /// failures.push(another_failure);
375 ///
376 /// assert_eq!(failures.current_frames().len(), 2);
377 /// ```
378 pub fn expand(self) -> Report<[C]> {
379 Report {
380 frames: self.frames,
381 _context: PhantomData,
382 }
383 }
384
385 /// Returns the direct current frames of this report.
386 ///
387 /// To get an iterator over the topological sorting of all frames refer to [`frames()`].
388 ///
389 /// This is not the same as [`Report::current_context`], this function gets the underlying
390 /// frames that make up this report, while [`Report::current_context`] traverses the stack of
391 /// frames to find the current context. A [`Report`] and be made up of multiple [`Frame`]s,
392 /// which stack on top of each other. Considering `PrintableA<PrintableA<Context>>`,
393 /// [`Report::current_frame`] will return the "outer" layer `PrintableA`, while
394 /// [`Report::current_context`] will return the underlying `Error` (the current type
395 /// parameter of this [`Report`]).
396 ///
397 /// A report can be made up of multiple stacks of frames and builds a "group" of them, this can
398 /// be achieved through first calling [`Report::expand`] and then either using [`Extend`]
399 /// or [`Report::push`].
400 ///
401 /// [`frames()`]: Self::frames
402 #[must_use]
403 pub fn current_frame(&self) -> &Frame {
404 self.frames.first().unwrap_or_else(|| {
405 unreachable!(
406 "Report does not contain any frames. This should not happen as a Report must \
407 always contain at least one frame.\n\n
408 Please file an issue to https://github.com/hashintel/hash/issues/new?template=bug-report-error-stack.yml\n\n
409 Report:\n{self:?}",
410 )
411 })
412 }
413
414 /// Returns the current context of the `Report`.
415 ///
416 /// If the user want to get the latest context, `current_context` can be called. If the user
417 /// wants to handle the error, the context can then be used to directly access the context's
418 /// type. This is only possible for the latest context as the Report does not have multiple
419 /// generics as this would either require variadic generics or a workaround like tuple-list.
420 ///
421 /// This is one disadvantage of the library in comparison to plain Errors, as in these cases,
422 /// all context types are known.
423 ///
424 /// ## Example
425 ///
426 /// ```rust
427 /// # use std::{fs, path::Path};
428 /// # use error_stack::Report;
429 /// use std::io;
430 ///
431 /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
432 /// # const _: &str = stringify! {
433 /// ...
434 /// # };
435 /// # fs::read_to_string(path.as_ref()).map_err(Report::from)
436 /// }
437 ///
438 /// let report = read_file("test.txt").unwrap_err();
439 /// let io_error = report.current_context();
440 /// assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
441 /// ```
442 #[must_use]
443 pub fn current_context(&self) -> &C
444 where
445 C: Send + Sync + 'static,
446 {
447 self.downcast_ref().unwrap_or_else(|| {
448 // Panics if there isn't an attached context which matches `T`. As it's not possible to
449 // create a `Report` without a valid context and this method can only be called when `T`
450 // is a valid context, it's guaranteed that the context is available.
451 unreachable!(
452 "Report does not contain a context. This should not happen as a Report must \
453 always contain at least one frame.\n\n
454 Please file an issue to https://github.com/hashintel/hash/issues/new?template=bug-report-error-stack.yml\n\n
455 Report:\n{self:?}",
456 )
457 })
458 }
459
460 /// Converts this `Report` to an [`Error`].
461 #[must_use]
462 pub fn into_error(self) -> impl Error + Send + Sync + 'static
463 where
464 C: 'static,
465 {
466 crate::error::ReportError::new(self)
467 }
468
469 /// Returns this `Report` as an [`Error`].
470 #[must_use]
471 pub fn as_error(&self) -> &(impl Error + Send + Sync + 'static)
472 where
473 C: 'static,
474 {
475 crate::error::ReportError::from_ref(self)
476 }
477}
478
479impl<C: ?Sized> Report<C> {
480 /// Retrieves the current frames of the `Report`, regardless of its current type state.
481 ///
482 /// You should prefer using [`Report::current_frame`] or [`Report::current_frames`] instead of
483 /// this function, as those properly interact with the type state of the `Report`.
484 ///
485 /// # Use Cases
486 ///
487 /// This function is primarily used to implement traits that require access to the frames,
488 /// such as [`Debug`]. It allows for code reuse between `Report<C>` and `Report<[C]>`
489 /// implementations without duplicating logic.
490 #[must_use]
491 pub(crate) fn current_frames_unchecked(&self) -> &[Frame] {
492 &self.frames
493 }
494
495 /// Adds additional (printable) information to the [`Frame`] stack.
496 ///
497 /// This behaves like [`attach_opaque()`] but the display implementation will be called when
498 /// printing the [`Report`].
499 ///
500 /// **Note:** [`attach_opaque()`] will be deprecated when specialization is stabilized and
501 /// it becomes possible to merge these two methods.
502 ///
503 /// [`attach_opaque()`]: Self::attach
504 ///
505 /// ## Example
506 ///
507 /// ```rust
508 /// use core::fmt;
509 /// use std::fs;
510 ///
511 /// use error_stack::ResultExt;
512 ///
513 /// #[derive(Debug)]
514 /// pub struct Suggestion(&'static str);
515 ///
516 /// impl fmt::Display for Suggestion {
517 /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
518 /// fmt.write_str(self.0)
519 /// }
520 /// }
521 ///
522 /// let error = fs::read_to_string("config.txt")
523 /// .attach(Suggestion("better use a file which exists next time!"));
524 /// # #[cfg_attr(not(nightly), allow(unused_variables))]
525 /// let report = error.unwrap_err();
526 /// # #[cfg(nightly)]
527 /// let suggestion = report.request_ref::<Suggestion>().next().unwrap();
528 ///
529 /// # #[cfg(nightly)]
530 /// assert_eq!(suggestion.0, "better use a file which exists next time!");
531 /// ```
532 #[track_caller]
533 pub fn attach<A>(mut self, attachment: A) -> Self
534 where
535 A: Attachment,
536 {
537 let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
538 self.frames.push(Frame::from_printable_attachment(
539 attachment,
540 old_frames.into_boxed_slice(),
541 ));
542 self
543 }
544
545 /// Adds additional information to the [`Frame`] stack.
546 ///
547 /// This behaves like [`attach()`] but will not be shown when printing the [`Report`].
548 /// To benefit from seeing attachments in normal error outputs, use [`attach()`].
549 ///
550 /// **Note:** This will be deprecated in favor of [`attach()`] when specialization is
551 /// stabilized it becomes possible to merge these two methods.
552 ///
553 /// [`Display`]: core::fmt::Display
554 /// [`Debug`]: core::fmt::Debug
555 /// [`attach()`]: Self::attach
556 #[track_caller]
557 pub fn attach_opaque<A>(mut self, attachment: A) -> Self
558 where
559 A: OpaqueAttachment,
560 {
561 let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
562 self.frames.push(Frame::from_attachment(
563 attachment,
564 old_frames.into_boxed_slice(),
565 ));
566 self
567 }
568
569 /// Add a new [`Error`] object to the top of the [`Frame`] stack, changing the type of the
570 /// `Report`.
571 ///
572 /// Please see the [`Error`] documentation for more information.
573 #[track_caller]
574 pub fn change_context<T>(mut self, context: T) -> Report<T>
575 where
576 T: Error + Send + Sync + 'static,
577 {
578 let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
579 let context_frame = vec![Frame::from_context(context, old_frames.into_boxed_slice())];
580 self.frames.push(Frame::from_attachment(
581 *Location::caller(),
582 context_frame.into_boxed_slice(),
583 ));
584 Report {
585 frames: self.frames,
586 _context: PhantomData,
587 }
588 }
589
590 /// Returns an iterator over the [`Frame`] stack of the report.
591 pub fn frames(&self) -> Frames<'_> {
592 Frames::new(&self.frames)
593 }
594
595 /// Visits every [`Frame`] of the report mutably, in the same order as [`frames()`].
596 ///
597 /// Returning [`ControlFlow::Break`] from `visitor` stops the traversal early. The break is
598 /// propagated to the caller, a full traversal returns [`ControlFlow::Continue`].
599 ///
600 /// This is deliberately not an [`Iterator`]: an iterator over `&mut Frame` hands out
601 /// references whose lifetimes are independent of each other, so a frame and one of its
602 /// sources (reachable via [`Frame::sources_mut`]) could be borrowed mutably at the same
603 /// time. Scoping mutable access to a visitor rules that out.
604 ///
605 /// ## Example
606 ///
607 /// ```rust
608 /// # use std::{fs, io, path::Path};
609 /// # use core::ops::ControlFlow;
610 /// # use error_stack::Report;
611 /// # fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
612 /// # fs::read_to_string(path.as_ref()).map_err(Report::from)
613 /// # }
614 /// let mut report = read_file("test.txt").unwrap_err();
615 /// let flow = report.frames_mut(|frame| {
616 /// if let Some(io_error) = frame.downcast_mut::<io::Error>() {
617 /// *io_error = io::Error::from(io::ErrorKind::Other);
618 /// return ControlFlow::Break(());
619 /// }
620 /// ControlFlow::Continue(())
621 /// });
622 /// assert!(flow.is_break());
623 /// ```
624 ///
625 /// [`frames()`]: Self::frames
626 pub fn frames_mut(
627 &mut self,
628 mut visitor: impl FnMut(&mut Frame) -> ControlFlow<()>,
629 ) -> ControlFlow<()> {
630 // If lending iterators ever land in `core`, this should become one: yielding `&mut Frame`
631 // bound to the `next()` borrow is sound and more ergonomic than a visitor.
632 let mut stack = vec![self.frames.iter_mut()];
633 while let Some(frame) = iter::next(&mut stack) {
634 visitor(frame)?;
635 stack.push(frame.sources_mut().iter_mut());
636 }
637 ControlFlow::Continue(())
638 }
639
640 /// Creates an iterator of references of type `T` that have been [`attached`](Self::attach) or
641 /// that are [`provide`](Error::provide)d by [`Error`] objects.
642 #[cfg(nightly)]
643 pub fn request_ref<T: ?Sized + Send + Sync + 'static>(&self) -> RequestRef<'_, T> {
644 RequestRef::new(&self.frames)
645 }
646
647 /// Creates an iterator of values of type `T` that have been [`attached`](Self::attach) or
648 /// that are [`provide`](Error::provide)d by [`Error`] objects.
649 #[cfg(nightly)]
650 pub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> {
651 RequestValue::new(&self.frames)
652 }
653
654 /// Returns if `T` is the type held by any frame inside of the report.
655 ///
656 /// `T` could either be an attachment or a [`Error`] context.
657 ///
658 /// ## Example
659 ///
660 /// ```rust
661 /// # use std::{fs, io, path::Path};
662 /// # use error_stack::Report;
663 /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
664 /// # const _: &str = stringify! {
665 /// ...
666 /// # };
667 /// # fs::read_to_string(path.as_ref()).map_err(Report::from)
668 /// }
669 ///
670 /// let report = read_file("test.txt").unwrap_err();
671 /// assert!(report.contains::<io::Error>());
672 /// ```
673 #[must_use]
674 pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
675 self.frames().any(Frame::is::<T>)
676 }
677
678 /// Searches the frame stack for a context provider `T` and returns the most recent context
679 /// found.
680 ///
681 /// `T` can either be an attachment or a new [`Error`] context.
682 ///
683 /// ## Example
684 ///
685 /// ```rust
686 /// # use std::{fs, path::Path};
687 /// # use error_stack::Report;
688 /// use std::io;
689 ///
690 /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
691 /// # const _: &str = stringify! {
692 /// ...
693 /// # };
694 /// # fs::read_to_string(path.as_ref()).map_err(Report::from)
695 /// }
696 ///
697 /// let report = read_file("test.txt").unwrap_err();
698 /// let io_error = report.downcast_ref::<io::Error>().unwrap();
699 /// assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
700 /// ```
701 #[must_use]
702 pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
703 self.frames().find_map(Frame::downcast_ref::<T>)
704 }
705
706 /// Searches the frame stack for an instance of type `T`, returning the most recent one found.
707 ///
708 /// `T` can either be an attachment or a new [`Error`] context.
709 #[must_use]
710 pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
711 // This cannot use `frames_mut` as the found reference needs to outlive the visitor
712 // closure. Each frame is either returned or descended into, never both, so the borrow
713 // checker accepts this without `unsafe`.
714 let mut stack = vec![self.frames.iter_mut()];
715 while let Some(frame) = iter::next(&mut stack) {
716 if frame.is::<T>() {
717 return frame.downcast_mut();
718 }
719 stack.push(frame.sources_mut().iter_mut());
720 }
721 None
722 }
723}
724
725impl<C> Report<[C]> {
726 /// Returns the direct current frames of this report.
727 ///
728 /// To get an iterator over the topological sorting of all frames refer to [`frames()`].
729 ///
730 /// This is not the same as [`Report::current_context`], this function gets the underlying
731 /// frames that make up this report, while [`Report::current_context`] traverses the stack of
732 /// frames to find the current context. A [`Report`] and be made up of multiple [`Frame`]s,
733 /// which stack on top of each other. Considering `PrintableA<PrintableA<Context>>`,
734 /// [`Report::current_frames`] will return the "outer" layer `PrintableA`, while
735 /// [`Report::current_context`] will return the underlying `Error` (the current type
736 /// parameter of this [`Report`]).
737 ///
738 /// Using [`Extend`], [`push()`] and [`append()`], a [`Report`] can additionally be made up of
739 /// multiple stacks of frames and builds a "group" of them, therefore this function returns a
740 /// slice instead, while [`Report::current_context`] only returns a single reference.
741 ///
742 /// [`push()`]: Self::push
743 /// [`append()`]: Self::append
744 /// [`frames()`]: Self::frames
745 /// [`extend_one()`]: Self::extend_one
746 #[must_use]
747 pub fn current_frames(&self) -> &[Frame] {
748 &self.frames
749 }
750
751 /// Pushes a new context to the `Report`.
752 ///
753 /// This function adds a new [`Frame`] to the current frames with the frame from the given
754 /// [`Report`].
755 ///
756 /// [`current_frames()`]: Self::current_frames
757 ///
758 /// ## Example
759 ///
760 /// ```rust
761 /// use std::{fmt, path::Path};
762 ///
763 /// use error_stack::{Report, ResultExt};
764 ///
765 /// #[derive(Debug)]
766 /// struct IoError;
767 ///
768 /// impl fmt::Display for IoError {
769 /// # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
770 /// # const _: &str = stringify!(
771 /// ...
772 /// # );
773 /// # fmt.write_str("Io Error")
774 /// # }
775 /// }
776 ///
777 /// # impl core::error::Error for IoError {}
778 ///
779 /// # #[allow(unused_variables)]
780 /// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
781 /// # #[cfg(any(miri, not(feature = "std")))]
782 /// # return Err(error_stack::report!(IoError).attach("Not supported"));
783 /// # #[cfg(all(not(miri), feature = "std"))]
784 /// std::fs::read_to_string(path.as_ref())
785 /// .change_context(IoError)
786 /// }
787 ///
788 /// let mut error1 = read_config("config.txt").unwrap_err().expand();
789 /// let error2 = read_config("config2.txt").unwrap_err();
790 /// let error3 = read_config("config3.txt").unwrap_err();
791 ///
792 /// error1.push(error2);
793 /// error1.push(error3);
794 /// ```
795 pub fn push(&mut self, mut report: Report<C>) {
796 self.frames.append(&mut report.frames);
797 }
798
799 /// Appends the frames from another `Report` to this one.
800 ///
801 /// This method combines the frames of the current `Report` with those of the provided `Report`,
802 /// effectively merging the two error reports.
803 ///
804 /// ## Example
805 ///
806 /// ```rust
807 /// use std::{fmt, path::Path};
808 ///
809 /// use error_stack::{Report, ResultExt};
810 ///
811 /// #[derive(Debug)]
812 /// struct IoError;
813 ///
814 /// impl fmt::Display for IoError {
815 /// # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
816 /// # const _: &str = stringify!(
817 /// ...
818 /// # );
819 /// # fmt.write_str("Io Error")
820 /// # }
821 /// }
822 ///
823 /// # impl core::error::Error for IoError {}
824 ///
825 /// # #[allow(unused_variables)]
826 /// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
827 /// # #[cfg(any(miri, not(feature = "std")))]
828 /// # return Err(error_stack::report!(IoError).attach("Not supported"));
829 /// # #[cfg(all(not(miri), feature = "std"))]
830 /// std::fs::read_to_string(path.as_ref())
831 /// .change_context(IoError)
832 /// }
833 ///
834 /// let mut error1 = read_config("config.txt").unwrap_err().expand();
835 /// let error2 = read_config("config2.txt").unwrap_err();
836 /// let mut error3 = read_config("config3.txt").unwrap_err().expand();
837 ///
838 /// error1.push(error2);
839 /// error3.append(error1);
840 /// ```
841 pub fn append(&mut self, mut report: Self) {
842 self.frames.append(&mut report.frames);
843 }
844
845 /// Returns an iterator over the current contexts of the `Report`.
846 ///
847 /// This method is similar to [`current_context`], but instead of returning a single context,
848 /// it returns an iterator over all contexts in the `Report`.
849 ///
850 /// The order of the contexts should not be relied upon, as it is not guaranteed to be stable.
851 ///
852 /// ## Example
853 ///
854 /// ```rust
855 /// # use std::{fs, path::Path};
856 /// # use error_stack::Report;
857 /// use std::io;
858 ///
859 /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
860 /// # const _: &str = stringify! {
861 /// ...
862 /// # };
863 /// # fs::read_to_string(path.as_ref()).map_err(Report::from)
864 /// }
865 ///
866 /// let mut a = read_file("test.txt").unwrap_err().expand();
867 /// let b = read_file("test2.txt").unwrap_err();
868 ///
869 /// a.push(b);
870 ///
871 /// let io_error = a.current_contexts();
872 /// assert_eq!(io_error.count(), 2);
873 /// ```
874 ///
875 /// [`current_context`]: Self::current_context
876 pub fn current_contexts(&self) -> impl Iterator<Item = &C>
877 where
878 C: Send + Sync + 'static,
879 {
880 // this needs a manual traveral implementation, why?
881 // We know that each arm has a current context, but we don't know where that context is,
882 // therefore we need to search for it on each branch, but stop once we found it, that way
883 // we're able to return the current context, even if it is "buried" underneath a bunch of
884 // attachments.
885 let mut output = Vec::new();
886
887 // this implementation does some "weaving" in a sense, it goes L->R for the frames, then
888 // R->L for the sources, which means that some sources might be out of order, but this
889 // simplifies implementation.
890 let mut stack = vec![self.current_frames()];
891 while let Some(frames) = stack.pop() {
892 for frame in frames {
893 // check if the frame is the current context, in that case we don't need to follow
894 // the tree anymore
895 if let Some(context) = frame.downcast_ref::<C>() {
896 output.push(context);
897 continue;
898 }
899
900 // descend into the tree
901 let sources = frame.sources();
902 match sources {
903 [] => unreachable!(
904 "Report does not contain a context. This is considered a bug and should be \
905 reported to https://github.com/hashintel/hash/issues/new/choose"
906 ),
907 sources => {
908 stack.push(sources);
909 }
910 }
911 }
912 }
913
914 output.into_iter()
915 }
916}
917
918impl<C: 'static> From<Report<C>> for Box<dyn Error> {
919 fn from(report: Report<C>) -> Self {
920 Box::new(report.into_error())
921 }
922}
923
924impl<C: 'static> From<Report<C>> for Box<dyn Error + Send> {
925 fn from(report: Report<C>) -> Self {
926 Box::new(report.into_error())
927 }
928}
929
930impl<C: 'static> From<Report<C>> for Box<dyn Error + Sync> {
931 fn from(report: Report<C>) -> Self {
932 Box::new(report.into_error())
933 }
934}
935
936impl<C: 'static> From<Report<C>> for Box<dyn Error + Send + Sync> {
937 fn from(report: Report<C>) -> Self {
938 Box::new(report.into_error())
939 }
940}
941
942impl<C> From<Report<C>> for Report<[C]> {
943 fn from(report: Report<C>) -> Self {
944 Self {
945 frames: report.frames,
946 _context: PhantomData,
947 }
948 }
949}
950
951#[cfg(feature = "std")]
952impl<C> std::process::Termination for Report<C> {
953 fn report(self) -> ExitCode {
954 #[cfg(not(nightly))]
955 return ExitCode::FAILURE;
956
957 #[cfg(nightly)]
958 self.request_ref::<ExitCode>()
959 .next()
960 .copied()
961 .unwrap_or(ExitCode::FAILURE)
962 }
963}
964
965impl<C> FromIterator<Report<C>> for Option<Report<[C]>> {
966 fn from_iter<T: IntoIterator<Item = Report<C>>>(iter: T) -> Self {
967 let mut iter = iter.into_iter();
968
969 let mut base = iter.next()?.expand();
970 for rest in iter {
971 base.push(rest);
972 }
973
974 Some(base)
975 }
976}
977
978impl<C> FromIterator<Report<[C]>> for Option<Report<[C]>> {
979 fn from_iter<T: IntoIterator<Item = Report<[C]>>>(iter: T) -> Self {
980 let mut iter = iter.into_iter();
981
982 let mut base = iter.next()?;
983 for mut rest in iter {
984 base.frames.append(&mut rest.frames);
985 }
986
987 Some(base)
988 }
989}
990
991impl<C> Extend<Report<C>> for Report<[C]> {
992 fn extend<T: IntoIterator<Item = Report<C>>>(&mut self, iter: T) {
993 for item in iter {
994 self.push(item);
995 }
996 }
997}
998
999impl<C> Extend<Self> for Report<[C]> {
1000 fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T) {
1001 for mut item in iter {
1002 self.frames.append(&mut item.frames);
1003 }
1004 }
1005}
1006
1007/// Provides unified way to convert an error-like structure to a [`Report`].
1008///
1009/// This trait allows both [`Report<C>`] instances and regular error types to be converted into a
1010/// [`Report`]. It is automatically implemented for any type that can be converted into a [`Report`]
1011/// via the [`Into`] trait.
1012///
1013/// This trait is particularly useful when working with functions that need to return a [`Report`],
1014/// as it provides a consistent way to convert errors into reports without explicitly calling
1015/// conversion methods.
1016///
1017/// # Examples
1018///
1019/// ```rust
1020/// use std::io;
1021///
1022/// use error_stack::{IntoReport as _, Report};
1023///
1024/// # #[expect(dead_code)]
1025/// fn example() -> Result<(), Report<io::Error>> {
1026/// // io::Error implements Into<Report<io::Error>>, so we can use into_report()
1027/// let err = io::Error::new(io::ErrorKind::Other, "oh no!");
1028/// Err(err.into_report())
1029/// }
1030/// ```
1031pub trait IntoReport {
1032 /// The context type that will be used in the resulting [`Report`].
1033 type Context: ?Sized;
1034
1035 /// Converts this value into a [`Report`].
1036 fn into_report(self) -> Report<Self::Context>;
1037}
1038
1039impl<C: ?Sized> IntoReport for Report<C> {
1040 type Context = C;
1041
1042 #[track_caller]
1043 fn into_report(self) -> Report<Self::Context> {
1044 self
1045 }
1046}
1047
1048impl<E> IntoReport for E
1049where
1050 E: Into<Report<E>>,
1051{
1052 type Context = E;
1053
1054 #[track_caller]
1055 fn into_report(self) -> Report<Self::Context> {
1056 self.into()
1057 }
1058}