Skip to main content

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}