error_stack/
report.rs

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