Skip to main content

error_stack/
report.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2use core::{error::Error, marker::PhantomData, mem, 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::{Frames, FramesMut},
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    /// Returns an iterator over the [`Frame`] stack of the report with mutable elements.
596    pub fn frames_mut(&mut self) -> FramesMut<'_> {
597        FramesMut::new(&mut self.frames)
598    }
599
600    /// Creates an iterator of references of type `T` that have been [`attached`](Self::attach) or
601    /// that are [`provide`](Error::provide)d by [`Error`] objects.
602    #[cfg(nightly)]
603    pub fn request_ref<T: ?Sized + Send + Sync + 'static>(&self) -> RequestRef<'_, T> {
604        RequestRef::new(&self.frames)
605    }
606
607    /// Creates an iterator of values of type `T` that have been [`attached`](Self::attach) or
608    /// that are [`provide`](Error::provide)d by [`Error`] objects.
609    #[cfg(nightly)]
610    pub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> {
611        RequestValue::new(&self.frames)
612    }
613
614    /// Returns if `T` is the type held by any frame inside of the report.
615    ///
616    /// `T` could either be an attachment or a [`Error`] context.
617    ///
618    /// ## Example
619    ///
620    /// ```rust
621    /// # use std::{fs, io, path::Path};
622    /// # use error_stack::Report;
623    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
624    ///     # const _: &str = stringify! {
625    ///     ...
626    ///     # };
627    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
628    /// }
629    ///
630    /// let report = read_file("test.txt").unwrap_err();
631    /// assert!(report.contains::<io::Error>());
632    /// ```
633    #[must_use]
634    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
635        self.frames().any(Frame::is::<T>)
636    }
637
638    /// Searches the frame stack for a context provider `T` and returns the most recent context
639    /// found.
640    ///
641    /// `T` can either be an attachment or a new [`Error`] context.
642    ///
643    /// ## Example
644    ///
645    /// ```rust
646    /// # use std::{fs, path::Path};
647    /// # use error_stack::Report;
648    /// use std::io;
649    ///
650    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
651    ///     # const _: &str = stringify! {
652    ///     ...
653    ///     # };
654    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
655    /// }
656    ///
657    /// let report = read_file("test.txt").unwrap_err();
658    /// let io_error = report.downcast_ref::<io::Error>().unwrap();
659    /// assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
660    /// ```
661    #[must_use]
662    pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
663        self.frames().find_map(Frame::downcast_ref::<T>)
664    }
665
666    /// Searches the frame stack for an instance of type `T`, returning the most recent one found.
667    ///
668    /// `T` can either be an attachment or a new [`Error`] context.
669    #[must_use]
670    pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
671        self.frames_mut().find_map(Frame::downcast_mut::<T>)
672    }
673}
674
675impl<C> Report<[C]> {
676    /// Returns the direct current frames of this report.
677    ///
678    /// To get an iterator over the topological sorting of all frames refer to [`frames()`].
679    ///
680    /// This is not the same as [`Report::current_context`], this function gets the underlying
681    /// frames that make up this report, while [`Report::current_context`] traverses the stack of
682    /// frames to find the current context. A [`Report`] and be made up of multiple [`Frame`]s,
683    /// which stack on top of each other. Considering `PrintableA<PrintableA<Context>>`,
684    /// [`Report::current_frames`] will return the "outer" layer `PrintableA`, while
685    /// [`Report::current_context`] will return the underlying `Error` (the current type
686    /// parameter of this [`Report`]).
687    ///
688    /// Using [`Extend`], [`push()`] and [`append()`], a [`Report`] can additionally be made up of
689    /// multiple stacks of frames and builds a "group" of them, therefore this function returns a
690    /// slice instead, while [`Report::current_context`] only returns a single reference.
691    ///
692    /// [`push()`]: Self::push
693    /// [`append()`]: Self::append
694    /// [`frames()`]: Self::frames
695    /// [`extend_one()`]: Self::extend_one
696    #[must_use]
697    pub fn current_frames(&self) -> &[Frame] {
698        &self.frames
699    }
700
701    /// Pushes a new context to the `Report`.
702    ///
703    /// This function adds a new [`Frame`] to the current frames with the frame from the given
704    /// [`Report`].
705    ///
706    /// [`current_frames()`]: Self::current_frames
707    ///
708    /// ## Example
709    ///
710    /// ```rust
711    /// use std::{fmt, path::Path};
712    ///
713    /// use error_stack::{Report, ResultExt};
714    ///
715    /// #[derive(Debug)]
716    /// struct IoError;
717    ///
718    /// impl fmt::Display for IoError {
719    ///     # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
720    ///     #     const _: &str = stringify!(
721    ///             ...
722    ///     #     );
723    ///     #     fmt.write_str("Io Error")
724    ///     # }
725    /// }
726    ///
727    /// # impl core::error::Error for IoError {}
728    ///
729    /// # #[allow(unused_variables)]
730    /// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
731    ///     # #[cfg(any(miri, not(feature = "std")))]
732    ///     # return Err(error_stack::report!(IoError).attach("Not supported"));
733    ///     # #[cfg(all(not(miri), feature = "std"))]
734    ///     std::fs::read_to_string(path.as_ref())
735    ///         .change_context(IoError)
736    /// }
737    ///
738    /// let mut error1 = read_config("config.txt").unwrap_err().expand();
739    /// let error2 = read_config("config2.txt").unwrap_err();
740    /// let error3 = read_config("config3.txt").unwrap_err();
741    ///
742    /// error1.push(error2);
743    /// error1.push(error3);
744    /// ```
745    pub fn push(&mut self, mut report: Report<C>) {
746        self.frames.append(&mut report.frames);
747    }
748
749    /// Appends the frames from another `Report` to this one.
750    ///
751    /// This method combines the frames of the current `Report` with those of the provided `Report`,
752    /// effectively merging the two error reports.
753    ///
754    /// ## Example
755    ///
756    /// ```rust
757    /// use std::{fmt, path::Path};
758    ///
759    /// use error_stack::{Report, ResultExt};
760    ///
761    /// #[derive(Debug)]
762    /// struct IoError;
763    ///
764    /// impl fmt::Display for IoError {
765    ///     # fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
766    ///     #     const _: &str = stringify!(
767    ///             ...
768    ///     #     );
769    ///     #     fmt.write_str("Io Error")
770    ///     # }
771    /// }
772    ///
773    /// # impl core::error::Error for IoError {}
774    ///
775    /// # #[allow(unused_variables)]
776    /// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
777    ///     # #[cfg(any(miri, not(feature = "std")))]
778    ///     # return Err(error_stack::report!(IoError).attach("Not supported"));
779    ///     # #[cfg(all(not(miri), feature = "std"))]
780    ///     std::fs::read_to_string(path.as_ref())
781    ///         .change_context(IoError)
782    /// }
783    ///
784    /// let mut error1 = read_config("config.txt").unwrap_err().expand();
785    /// let error2 = read_config("config2.txt").unwrap_err();
786    /// let mut error3 = read_config("config3.txt").unwrap_err().expand();
787    ///
788    /// error1.push(error2);
789    /// error3.append(error1);
790    /// ```
791    pub fn append(&mut self, mut report: Self) {
792        self.frames.append(&mut report.frames);
793    }
794
795    /// Returns an iterator over the current contexts of the `Report`.
796    ///
797    /// This method is similar to [`current_context`], but instead of returning a single context,
798    /// it returns an iterator over all contexts in the `Report`.
799    ///
800    /// The order of the contexts should not be relied upon, as it is not guaranteed to be stable.
801    ///
802    /// ## Example
803    ///
804    /// ```rust
805    /// # use std::{fs, path::Path};
806    /// # use error_stack::Report;
807    /// use std::io;
808    ///
809    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
810    ///     # const _: &str = stringify! {
811    ///     ...
812    ///     # };
813    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
814    /// }
815    ///
816    /// let mut a = read_file("test.txt").unwrap_err().expand();
817    /// let b = read_file("test2.txt").unwrap_err();
818    ///
819    /// a.push(b);
820    ///
821    /// let io_error = a.current_contexts();
822    /// assert_eq!(io_error.count(), 2);
823    /// ```
824    ///
825    /// [`current_context`]: Self::current_context
826    pub fn current_contexts(&self) -> impl Iterator<Item = &C>
827    where
828        C: Send + Sync + 'static,
829    {
830        // this needs a manual traveral implementation, why?
831        // We know that each arm has a current context, but we don't know where that context is,
832        // therefore we need to search for it on each branch, but stop once we found it, that way
833        // we're able to return the current context, even if it is "buried" underneath a bunch of
834        // attachments.
835        let mut output = Vec::new();
836
837        // this implementation does some "weaving" in a sense, it goes L->R for the frames, then
838        // R->L for the sources, which means that some sources might be out of order, but this
839        // simplifies implementation.
840        let mut stack = vec![self.current_frames()];
841        while let Some(frames) = stack.pop() {
842            for frame in frames {
843                // check if the frame is the current context, in that case we don't need to follow
844                // the tree anymore
845                if let Some(context) = frame.downcast_ref::<C>() {
846                    output.push(context);
847                    continue;
848                }
849
850                // descend into the tree
851                let sources = frame.sources();
852                match sources {
853                    [] => unreachable!(
854                        "Report does not contain a context. This is considered a bug and should be \
855                        reported to https://github.com/hashintel/hash/issues/new/choose"
856                    ),
857                    sources => {
858                        stack.push(sources);
859                    }
860                }
861            }
862        }
863
864        output.into_iter()
865    }
866}
867
868impl<C: 'static> From<Report<C>> for Box<dyn Error> {
869    fn from(report: Report<C>) -> Self {
870        Box::new(report.into_error())
871    }
872}
873
874impl<C: 'static> From<Report<C>> for Box<dyn Error + Send> {
875    fn from(report: Report<C>) -> Self {
876        Box::new(report.into_error())
877    }
878}
879
880impl<C: 'static> From<Report<C>> for Box<dyn Error + Sync> {
881    fn from(report: Report<C>) -> Self {
882        Box::new(report.into_error())
883    }
884}
885
886impl<C: 'static> From<Report<C>> for Box<dyn Error + Send + Sync> {
887    fn from(report: Report<C>) -> Self {
888        Box::new(report.into_error())
889    }
890}
891
892impl<C> From<Report<C>> for Report<[C]> {
893    fn from(report: Report<C>) -> Self {
894        Self {
895            frames: report.frames,
896            _context: PhantomData,
897        }
898    }
899}
900
901#[cfg(feature = "std")]
902impl<C> std::process::Termination for Report<C> {
903    fn report(self) -> ExitCode {
904        #[cfg(not(nightly))]
905        return ExitCode::FAILURE;
906
907        #[cfg(nightly)]
908        self.request_ref::<ExitCode>()
909            .next()
910            .copied()
911            .unwrap_or(ExitCode::FAILURE)
912    }
913}
914
915impl<C> FromIterator<Report<C>> for Option<Report<[C]>> {
916    fn from_iter<T: IntoIterator<Item = Report<C>>>(iter: T) -> Self {
917        let mut iter = iter.into_iter();
918
919        let mut base = iter.next()?.expand();
920        for rest in iter {
921            base.push(rest);
922        }
923
924        Some(base)
925    }
926}
927
928impl<C> FromIterator<Report<[C]>> for Option<Report<[C]>> {
929    fn from_iter<T: IntoIterator<Item = Report<[C]>>>(iter: T) -> Self {
930        let mut iter = iter.into_iter();
931
932        let mut base = iter.next()?;
933        for mut rest in iter {
934            base.frames.append(&mut rest.frames);
935        }
936
937        Some(base)
938    }
939}
940
941impl<C> Extend<Report<C>> for Report<[C]> {
942    fn extend<T: IntoIterator<Item = Report<C>>>(&mut self, iter: T) {
943        for item in iter {
944            self.push(item);
945        }
946    }
947}
948
949impl<C> Extend<Self> for Report<[C]> {
950    fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T) {
951        for mut item in iter {
952            self.frames.append(&mut item.frames);
953        }
954    }
955}
956
957/// Provides unified way to convert an error-like structure to a [`Report`].
958///
959/// This trait allows both [`Report<C>`] instances and regular error types to be converted into a
960/// [`Report`]. It is automatically implemented for any type that can be converted into a [`Report`]
961/// via the [`Into`] trait.
962///
963/// This trait is particularly useful when working with functions that need to return a [`Report`],
964/// as it provides a consistent way to convert errors into reports without explicitly calling
965/// conversion methods.
966///
967/// # Examples
968///
969/// ```rust
970/// use std::io;
971///
972/// use error_stack::{IntoReport as _, Report};
973///
974/// # #[expect(dead_code)]
975/// fn example() -> Result<(), Report<io::Error>> {
976///     // io::Error implements Into<Report<io::Error>>, so we can use into_report()
977///     let err = io::Error::new(io::ErrorKind::Other, "oh no!");
978///     Err(err.into_report())
979/// }
980/// ```
981pub trait IntoReport {
982    /// The context type that will be used in the resulting [`Report`].
983    type Context: ?Sized;
984
985    /// Converts this value into a [`Report`].
986    fn into_report(self) -> Report<Self::Context>;
987}
988
989impl<C: ?Sized> IntoReport for Report<C> {
990    type Context = C;
991
992    #[track_caller]
993    fn into_report(self) -> Report<Self::Context> {
994        self
995    }
996}
997
998impl<E> IntoReport for E
999where
1000    E: Into<Report<E>>,
1001{
1002    type Context = E;
1003
1004    #[track_caller]
1005    fn into_report(self) -> Report<Self::Context> {
1006        self.into()
1007    }
1008}