error_stack/
report.rs

1#[cfg_attr(feature = "std", allow(unused_imports))]
2use alloc::{boxed::Box, vec, vec::Vec};
3#[cfg(rust_1_81)]
4use core::error::Error;
5use core::{fmt, marker::PhantomData, mem, panic::Location};
6#[cfg(feature = "backtrace")]
7use std::backtrace::{Backtrace, BacktraceStatus};
8#[cfg(all(feature = "std", not(rust_1_81)))]
9use std::error::Error;
10#[cfg(feature = "std")]
11use std::process::ExitCode;
12
13#[cfg(feature = "spantrace")]
14use tracing_error::{SpanTrace, SpanTraceStatus};
15
16#[cfg(any(feature = "std", rust_1_81))]
17use crate::context::SourceContext;
18#[cfg(nightly)]
19use crate::iter::{RequestRef, RequestValue};
20use crate::{
21    iter::{Frames, FramesMut},
22    Context, Frame,
23};
24
25/// Contains a [`Frame`] stack consisting of [`Context`]s and attachments.
26///
27/// Attachments can be added by using [`attach()`]. The [`Frame`] stack can be iterated by using
28/// [`frames()`].
29///
30/// When creating a `Report` by using [`new()`], the passed [`Context`] is used to set the _current
31/// context_ on the `Report`. To provide a new one, use [`change_context()`].
32///
33/// Attachments, and objects [`provide`]d by a [`Context`], are directly retrievable by calling
34/// [`request_ref()`] or [`request_value()`].
35///
36/// ## Formatting
37///
38/// `Report` implements [`Display`] and [`Debug`]. When utilizing the [`Display`] implementation,
39/// the current context of the `Report` is printed, e.g. `println!("{report}")`. For the alternate
40/// [`Display`] output (`"{:#}"`), all [`Context`]s are printed. To print the full stack of
41/// [`Context`]s and attachments, use the [`Debug`] implementation (`"{:?}"`). To customize the
42/// output of the attachments in the [`Debug`] output, please see the [`error_stack::fmt`] module.
43///
44/// Please see the examples below for more information.
45///
46/// [`Display`]: fmt::Display
47/// [`error_stack::fmt`]: crate::fmt
48///
49/// ## Multiple Errors
50///
51/// `Report` is able to represent multiple errors that have occurred. Errors can be combined using
52/// the [`extend_one()`], which will add the [`Frame`] stack of the other error as an additional
53/// source to the current report.
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()`]: 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, Result};
89///
90/// # #[allow(dead_code)]
91/// # fn fake_main() -> Result<String, std::io::Error> {
92/// let config_path = "./path/to/config.file";
93/// let content = std::fs::read_to_string(config_path)
94///     .attach_printable_lazy(|| 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::{fmt, path::{Path, PathBuf}};
105///
106/// use error_stack::{Context, 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 Context for RuntimeError {}
146/// impl Context 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#[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#[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#[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, Result};
213///
214/// # #[allow(unused_variables)]
215/// # fn main() -> Result<(), std::io::Error> {
216/// let config_path = "./path/to/config.file";
217/// let content = std::fs::read_to_string(config_path)
218///     .attach_printable_lazy(|| 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#[allow(clippy::field_scoped_visibility_modifiers)]
245pub struct Report<C> {
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    #[allow(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    #[allow(clippy::missing_panics_doc)] // Reason: No panic possible
267    pub fn new(context: C) -> Self
268    where
269        C: Context,
270    {
271        #[cfg(any(feature = "std", rust_1_81))]
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        #[allow(unused_mut)]
326        let mut report = Self {
327            frames: Box::new(vec![frame]),
328            _context: PhantomData,
329        };
330
331        if let Some(location) = location {
332            report = report.attach(*location);
333        }
334
335        #[cfg(feature = "backtrace")]
336        if let Some(backtrace) =
337            backtrace.filter(|bt| matches!(bt.status(), BacktraceStatus::Captured))
338        {
339            report = report.attach(backtrace);
340        }
341
342        #[cfg(feature = "spantrace")]
343        if let Some(span_trace) = span_trace.filter(|st| st.status() == SpanTraceStatus::CAPTURED) {
344            report = report.attach(span_trace);
345        }
346
347        report
348    }
349
350    /// Merge two [`Report`]s together
351    ///
352    /// This function appends the [`current_frames()`] of the other [`Report`] to the
353    /// [`current_frames()`] of this report.
354    /// Meaning `A.extend_one(B) -> A.current_frames() = A.current_frames() + B.current_frames()`
355    ///
356    /// [`current_frames()`]: Self::current_frames
357    ///
358    /// ```rust
359    /// use std::{
360    ///     fmt::{Display, Formatter},
361    ///     path::Path,
362    /// };
363    ///
364    /// use error_stack::{Context, Report, ResultExt};
365    ///
366    /// #[derive(Debug)]
367    /// struct IoError;
368    ///
369    /// impl Display for IoError {
370    ///     # fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
371    ///     #     const _: &str = stringify!(
372    ///             ...
373    ///     #     );
374    ///     #     f.write_str("Io Error")
375    ///     # }
376    /// }
377    ///
378    /// # impl Context for IoError {}
379    ///
380    /// # #[allow(unused_variables)]
381    /// fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
382    ///     # #[cfg(any(miri, not(feature = "std")))]
383    ///     # return Err(error_stack::report!(IoError).attach_printable("Not supported"));
384    ///     # #[cfg(all(not(miri), feature = "std"))]
385    ///     std::fs::read_to_string(path.as_ref())
386    ///         .change_context(IoError)
387    /// }
388    ///
389    /// let mut error1 = read_config("config.txt").unwrap_err();
390    /// let error2 = read_config("config2.txt").unwrap_err();
391    /// let mut error3 = read_config("config3.txt").unwrap_err();
392    ///
393    /// error1.extend_one(error2);
394    /// error3.extend_one(error1);
395    ///
396    /// // ^ This is equivalent to:
397    /// // error3.extend_one(error1);
398    /// // error3.extend_one(error2);
399    /// ```
400    ///
401    /// This function implements the same functionality as
402    /// [`Extend::extend_one` (#7261)](https://github.com/rust-lang/rust/issues/72631).
403    /// Once stabilised this function will be removed in favor of [`Extend`].
404    ///
405    /// [`extend_one()`]: Self::extend_one
406    // TODO: once #7261 is stabilized deprecate and remove this function
407    #[allow(clippy::same_name_method)]
408    pub fn extend_one(&mut self, mut report: Self) {
409        self.frames.append(&mut report.frames);
410    }
411
412    /// Adds additional information to the [`Frame`] stack.
413    ///
414    /// This behaves like [`attach_printable()`] but will not be shown when printing the [`Report`].
415    /// To benefit from seeing attachments in normal error outputs, use [`attach_printable()`]
416    ///
417    /// **Note:** [`attach_printable()`] will be deprecated when specialization is stabilized and
418    /// it becomes possible to merge these two methods.
419    ///
420    /// [`Display`]: core::fmt::Display
421    /// [`Debug`]: core::fmt::Debug
422    /// [`attach_printable()`]: Self::attach_printable
423    #[track_caller]
424    pub fn attach<A>(mut self, attachment: A) -> Self
425    where
426        A: Send + Sync + 'static,
427    {
428        let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
429        self.frames.push(Frame::from_attachment(
430            attachment,
431            old_frames.into_boxed_slice(),
432        ));
433        self
434    }
435
436    /// Adds additional (printable) information to the [`Frame`] stack.
437    ///
438    /// This behaves like [`attach()`] but the display implementation will be called when
439    /// printing the [`Report`].
440    ///
441    /// **Note:** This will be deprecated in favor of [`attach()`] when specialization is
442    /// stabilized it becomes possible to merge these two methods.
443    ///
444    /// [`attach()`]: Self::attach
445    ///
446    /// ## Example
447    ///
448    /// ```rust
449    /// use core::fmt;
450    /// use std::fs;
451    ///
452    /// use error_stack::ResultExt;
453    ///
454    /// #[derive(Debug)]
455    /// pub struct Suggestion(&'static str);
456    ///
457    /// impl fmt::Display for Suggestion {
458    ///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
459    ///         fmt.write_str(self.0)
460    ///     }
461    /// }
462    ///
463    /// let error = fs::read_to_string("config.txt")
464    ///     .attach(Suggestion("better use a file which exists next time!"));
465    /// # #[cfg_attr(not(nightly), allow(unused_variables))]
466    /// let report = error.unwrap_err();
467    /// # #[cfg(nightly)]
468    /// let suggestion = report.request_ref::<Suggestion>().next().unwrap();
469    ///
470    /// # #[cfg(nightly)]
471    /// assert_eq!(suggestion.0, "better use a file which exists next time!");
472    /// ```
473    #[track_caller]
474    pub fn attach_printable<A>(mut self, attachment: A) -> Self
475    where
476        A: fmt::Display + fmt::Debug + Send + Sync + 'static,
477    {
478        let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
479        self.frames.push(Frame::from_printable_attachment(
480            attachment,
481            old_frames.into_boxed_slice(),
482        ));
483        self
484    }
485
486    /// Add a new [`Context`] object to the top of the [`Frame`] stack, changing the type of the
487    /// `Report`.
488    ///
489    /// Please see the [`Context`] documentation for more information.
490    #[track_caller]
491    pub fn change_context<T>(mut self, context: T) -> Report<T>
492    where
493        T: Context,
494    {
495        let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
496        let context_frame = vec![Frame::from_context(context, old_frames.into_boxed_slice())];
497        self.frames.push(Frame::from_attachment(
498            *Location::caller(),
499            context_frame.into_boxed_slice(),
500        ));
501        Report {
502            frames: self.frames,
503            _context: PhantomData,
504        }
505    }
506
507    /// Return the direct current frames of this report,
508    /// to get an iterator over the topological sorting of all frames refer to [`frames()`]
509    ///
510    /// This is not the same as [`Report::current_context`], this function gets the underlying
511    /// frames that make up this report, while [`Report::current_context`] traverses the stack of
512    /// frames to find the current context. A [`Report`] and be made up of multiple [`Frame`]s,
513    /// which stack on top of each other. Considering `PrintableA<PrintableA<Context>>`,
514    /// [`Report::current_frames`] will return the "outer" layer `PrintableA`, while
515    /// [`Report::current_context`] will return the underlying `Context` (the current type
516    /// parameter of this [`Report`])
517    ///
518    /// Using [`Extend`] and [`extend_one()`], a [`Report`] can additionally be made up of multiple
519    /// stacks of frames and builds a "group" of them, but a [`Report`] can only ever have a single
520    /// `Context`, therefore this function returns a slice instead, while
521    /// [`Report::current_context`] only returns a single reference.
522    ///
523    /// [`frames()`]: Self::frames
524    /// [`extend_one()`]: Self::extend_one
525    #[must_use]
526    pub fn current_frames(&self) -> &[Frame] {
527        &self.frames
528    }
529
530    /// Returns an iterator over the [`Frame`] stack of the report.
531    pub fn frames(&self) -> Frames<'_> {
532        Frames::new(&self.frames)
533    }
534
535    /// Returns an iterator over the [`Frame`] stack of the report with mutable elements.
536    pub fn frames_mut(&mut self) -> FramesMut<'_> {
537        FramesMut::new(&mut self.frames)
538    }
539
540    /// Creates an iterator of references of type `T` that have been [`attached`](Self::attach) or
541    /// that are [`provide`](Error::provide)d by [`Context`] objects.
542    #[cfg(nightly)]
543    pub fn request_ref<T: ?Sized + Send + Sync + 'static>(&self) -> RequestRef<'_, T> {
544        RequestRef::new(&self.frames)
545    }
546
547    /// Creates an iterator of values of type `T` that have been [`attached`](Self::attach) or
548    /// that are [`provide`](Error::provide)d by [`Context`] objects.
549    #[cfg(nightly)]
550    pub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> {
551        RequestValue::new(&self.frames)
552    }
553
554    /// Returns if `T` is the type held by any frame inside of the report.
555    ///
556    /// `T` could either be an attachment or a [`Context`].
557    ///
558    /// ## Example
559    ///
560    /// ```rust
561    /// # use std::{fs, io, path::Path};
562    /// # use error_stack::Report;
563    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
564    ///     # const _: &str = stringify! {
565    ///     ...
566    ///     # };
567    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
568    /// }
569    ///
570    /// let report = read_file("test.txt").unwrap_err();
571    /// assert!(report.contains::<io::Error>());
572    /// ```
573    #[must_use]
574    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
575        self.frames().any(Frame::is::<T>)
576    }
577
578    /// Searches the frame stack for a context provider `T` and returns the most recent context
579    /// found.
580    ///
581    /// `T` can either be an attachment or a [`Context`].
582    ///
583    /// ## Example
584    ///
585    /// ```rust
586    /// # use std::{fs, path::Path};
587    /// # use error_stack::Report;
588    /// use std::io;
589    ///
590    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
591    ///     # const _: &str = stringify! {
592    ///     ...
593    ///     # };
594    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
595    /// }
596    ///
597    /// let report = read_file("test.txt").unwrap_err();
598    /// let io_error = report.downcast_ref::<io::Error>().unwrap();
599    /// assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
600    /// ```
601    #[must_use]
602    pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
603        self.frames().find_map(Frame::downcast_ref::<T>)
604    }
605
606    /// Searches the frame stack for an instance of type `T`, returning the most recent one found.
607    ///
608    /// `T` can either be an attachment or a [`Context`].
609    #[must_use]
610    pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
611        self.frames_mut().find_map(Frame::downcast_mut::<T>)
612    }
613
614    /// Returns the current context of the `Report`.
615    ///
616    /// If the user want to get the latest context, `current_context` can be called. If the user
617    /// wants to handle the error, the context can then be used to directly access the context's
618    /// type. This is only possible for the latest context as the Report does not have multiple
619    /// generics as this would either require variadic generics or a workaround like tuple-list.
620    ///
621    /// This is one disadvantage of the library in comparison to plain Errors, as in these cases,
622    /// all context types are known.
623    ///
624    /// ## Example
625    ///
626    /// ```rust
627    /// # use std::{fs, path::Path};
628    /// # use error_stack::Report;
629    /// use std::io;
630    ///
631    /// fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
632    ///     # const _: &str = stringify! {
633    ///     ...
634    ///     # };
635    ///     # fs::read_to_string(path.as_ref()).map_err(Report::from)
636    /// }
637    ///
638    /// let report = read_file("test.txt").unwrap_err();
639    /// let io_error = report.current_context();
640    /// assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
641    /// ```
642    #[must_use]
643    pub fn current_context(&self) -> &C
644    where
645        C: Send + Sync + 'static,
646    {
647        self.downcast_ref().unwrap_or_else(|| {
648            // Panics if there isn't an attached context which matches `T`. As it's not possible to
649            // create a `Report` without a valid context and this method can only be called when `T`
650            // is a valid context, it's guaranteed that the context is available.
651            unreachable!(
652                "Report does not contain a context. This is considered a bug and should be \
653                reported to https://github.com/hashintel/hash/issues/new/choose"
654            );
655        })
656    }
657
658    /// Converts this `Report` to an [`Error`].
659    #[must_use]
660    #[cfg(any(feature = "std", rust_1_81))]
661    pub fn into_error(self) -> impl Error + Send + Sync + 'static
662    where
663        C: 'static,
664    {
665        crate::error::ReportError::new(self)
666    }
667
668    /// Returns this `Report` as an [`Error`].
669    #[must_use]
670    #[cfg(any(feature = "std", rust_1_81))]
671    pub fn as_error(&self) -> &(impl Error + Send + Sync + 'static)
672    where
673        C: 'static,
674    {
675        crate::error::ReportError::from_ref(self)
676    }
677}
678
679#[cfg(any(feature = "std", rust_1_81))]
680impl<C: 'static> From<Report<C>> for Box<dyn Error> {
681    fn from(report: Report<C>) -> Self {
682        Box::new(report.into_error())
683    }
684}
685
686#[cfg(any(feature = "std", rust_1_81))]
687impl<C: 'static> From<Report<C>> for Box<dyn Error + Send> {
688    fn from(report: Report<C>) -> Self {
689        Box::new(report.into_error())
690    }
691}
692
693#[cfg(any(feature = "std", rust_1_81))]
694impl<C: 'static> From<Report<C>> for Box<dyn Error + Sync> {
695    fn from(report: Report<C>) -> Self {
696        Box::new(report.into_error())
697    }
698}
699
700#[cfg(any(feature = "std", rust_1_81))]
701impl<C: 'static> From<Report<C>> for Box<dyn Error + Send + Sync> {
702    fn from(report: Report<C>) -> Self {
703        Box::new(report.into_error())
704    }
705}
706
707#[cfg(feature = "std")]
708impl<Context> std::process::Termination for Report<Context> {
709    fn report(self) -> ExitCode {
710        #[cfg(not(nightly))]
711        return ExitCode::FAILURE;
712
713        #[cfg(nightly)]
714        self.request_ref::<ExitCode>()
715            .next()
716            .copied()
717            .unwrap_or(ExitCode::FAILURE)
718    }
719}
720
721impl<Context> FromIterator<Report<Context>> for Option<Report<Context>> {
722    fn from_iter<T: IntoIterator<Item = Report<Context>>>(iter: T) -> Self {
723        let mut iter = iter.into_iter();
724
725        let mut base = iter.next()?;
726        for rest in iter {
727            base.extend_one(rest);
728        }
729
730        Some(base)
731    }
732}
733
734impl<Context> Extend<Self> for Report<Context> {
735    fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T) {
736        for item in iter {
737            self.extend_one(item);
738        }
739    }
740}