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}