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