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}