pub struct Report<C: ?Sized> { /* private fields */ }
Expand description
Contains a Frame
stack consisting of Context
s and attachments.
Attachments can be added by using attach_opaque()
. The Frame
stack can be iterated by
using frames()
.
When creating a Report
by using new()
, the passed Context
is used to set the current
context on the Report
. To provide a new one, use change_context()
.
Attachments, and objects provide
d by a Context
, are directly retrievable by calling
request_ref()
or request_value()
.
§Formatting
Report
implements Display
and Debug
. When utilizing the Display
implementation,
the current context of the Report
is printed, e.g. println!("{report}")
. For the alternate
Display
output ("{:#}"
), all Context
s are printed. To print the full stack of
Context
s and attachments, use the Debug
implementation ("{:?}"
). To customize the
output of the attachments in the Debug
output, please see the error_stack::fmt
module.
Please see the examples below for more information.
§Multiple Errors
Report
comes in two variants: Report<C>
which represents a single error context, and
Report<[C]>
which can represent multiple error contexts. To combine multiple errors,
first convert a Report<C>
to Report<[C]>
using expand()
, then use push()
to
add additional errors. This allows for representing complex error scenarios with multiple
related simultaneous errors.
§Backtrace
and SpanTrace
Report
is able to provide
a Backtrace
and a SpanTrace
, which can be retrieved by
calling request_ref::<Backtrace>()
or request_ref::<SpanTrace>()
(downcast_ref::<SpanTrace>()
on stable) respectively. If the root context provide
s a
Backtrace
or a SpanTrace
, those are returned, otherwise, if configured, an attempt is
made to capture them when creating a Report
. To enable capturing of the backtrace, make sure
RUST_BACKTRACE
or RUST_LIB_BACKTRACE
is set according to the Backtrace
documentation. To enable capturing of the span trace, an ErrorLayer
has to be
enabled. Please also see the Feature Flags section. A single Report
can have multiple
Backtrace
s and SpanTrace
s, depending on the amount of related errors the Report
consists of. Therefore it isn’t guaranteed that request_ref()
will only ever return a single
Backtrace
or SpanTrace
.
§Examples
§Provide a context for an error
use error_stack::ResultExt;
let config_path = "./path/to/config.file";
let content = std::fs::read_to_string(config_path)
.attach_with(|| format!("failed to read config file {config_path:?}"))?;
...
§Enforce a context for an error
use std::{error::Error, fmt, path::{Path, PathBuf}};
use error_stack::{Report, ResultExt};
#[derive(Debug)]
enum RuntimeError {
InvalidConfig(PathBuf),
...
}
#[derive(Debug)]
enum ConfigError {
IoError,
...
}
impl fmt::Display for RuntimeError {
...
}
impl fmt::Display for ConfigError {
...
}
impl Error for RuntimeError {}
impl Error for ConfigError {}
fn read_config(path: impl AsRef<Path>) -> Result<String, Report<ConfigError>> {
std::fs::read_to_string(path.as_ref()).change_context(ConfigError::IoError)
}
fn main() -> Result<(), Report<RuntimeError>> {
let config_path = "./path/to/config.file";
let config = read_config(config_path)
.change_context_lazy(|| RuntimeError::InvalidConfig(PathBuf::from(config_path)))?;
...
}
§Formatting
For the example from above, the report could be formatted as follows:
If the Display
implementation of Report
will be invoked, this will print something like:
could not parse "./path/to/config.file"
If the alternate Display
implementation of Report
is invoked ({report:#}
), this will
print something like:
could not parse "./path/to/config.file": config file is invalid: No such file or directory (os error 2)
The Debug
implementation of Report
will print something like:
could not parse "./path/to/config.file" ├╴at libs/error-stack/src/report.rs:59:14 │ ├─▶ config file is invalid │ ╰╴at libs/error-stack/src/report.rs:51:44 │ ╰─▶ No such file or directory (os error 2) ├╴at libs/error-stack/src/report.rs:51:44 ╰╴backtrace (1) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
§Get the attached Backtrace
and SpanTrace
:
use error_stack::{ResultExt, Report};
let config_path = "./path/to/config.file";
let content = std::fs::read_to_string(config_path)
.attach_with(|| format!("failed to read config file {config_path:?}"));
let content = match content {
Err(err) => {
for backtrace in err.request_ref::<std::backtrace::Backtrace>() {
println!("backtrace: {backtrace}");
}
for span_trace in err.request_ref::<tracing_error::SpanTrace>() {
println!("span trace: {span_trace}")
}
return Err(err)
}
Ok(ok) => ok
};
...
Implementations§
Source§impl<C> Report<C>
impl<C> Report<C>
Sourcepub fn new(context: C) -> Selfwhere
C: Context,
pub fn new(context: C) -> Selfwhere
C: Context,
Creates a new Report<Context>
from a provided scope.
If context
does not provide Backtrace
/SpanTrace
then this attempts to capture
them directly. Please see the Backtrace
and SpanTrace
section of the Report
documentation for more information.
Examples found in repository?
More examples
66fn start_experiments(
67 experiment_ids: &[usize],
68 experiment_descriptions: &[&str],
69) -> Result<Vec<u64>, Report<[ExperimentError]>> {
70 let experiments = experiment_ids
71 .iter()
72 .map(|exp_id| {
73 let description = experiment_descriptions.get(*exp_id).ok_or_else(|| {
74 Report::new(ExperimentError)
75 .attach(format!("experiment {exp_id} has no valid description"))
76 })?;
77
78 let experiments = parse_experiment(description)
79 .attach(format!("experiment {exp_id} could not be parsed"))
80 .change_context(ExperimentError)?;
81
82 let experiments = experiments
83 .into_iter()
84 .map(|(lhs, rhs)| move || lhs * rhs)
85 .collect::<Vec<_>>();
86
87 Ok(experiments)
88 })
89 .fold(
90 Ok(vec![]),
91 |accum, value: Result<_, Report<ExperimentError>>| match (accum, value) {
92 (Ok(mut accum), Ok(value)) => {
93 accum.extend(value);
94
95 Ok(accum)
96 }
97 (Ok(_), Err(err)) => Err(err.expand()),
98 (Err(accum), Ok(_)) => Err(accum),
99 (Err(mut accum), Err(err)) => {
100 accum.push(err);
101
102 Err(accum)
103 }
104 },
105 )
106 .attach("unable to set up experiments")?;
107
108 Ok(experiments.iter().map(|experiment| experiment()).collect())
109}
Sourcepub fn expand(self) -> Report<[C]>
pub fn expand(self) -> Report<[C]>
Converts a Report
with a single context into a Report
with multiple contexts.
This function allows for the transformation of a Report<C>
into a Report<[C]>
,
enabling the report to potentially hold multiple current contexts of the same type.
§Example
use error_stack::Report;
#[derive(Debug)]
struct SystemFailure;
impl std::fmt::Display for SystemFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("System failure occured")
}
}
impl core::error::Error for SystemFailure {}
// Type annotations are used here to illustrate the types used, these are not required
let failure: Report<SystemFailure> = Report::new(SystemFailure);
let mut failures: Report<[SystemFailure]> = failure.expand();
assert_eq!(failures.current_frames().len(), 1);
let another_failure = Report::new(SystemFailure);
failures.push(another_failure);
assert_eq!(failures.current_frames().len(), 2);
Examples found in repository?
23fn parse_experiment(description: &str) -> Result<Vec<(u64, u64)>, Report<ParseExperimentError>> {
24 let values = description
25 .split(' ')
26 .map(|value| {
27 value
28 .parse::<u64>()
29 .attach_with(|| format!("{value:?} could not be parsed as experiment"))
30 })
31 .map(|value| value.map(|ok| (ok, 2 * ok)))
32 .fold(Ok(vec![]), |accum, value| match (accum, value) {
33 (Ok(mut accum), Ok(value)) => {
34 accum.push(value);
35
36 Ok(accum)
37 }
38 (Ok(_), Err(err)) => Err(err.expand()),
39 (Err(accum), Ok(_)) => Err(accum),
40 (Err(mut accum), Err(err)) => {
41 accum.push(err);
42
43 Err(accum)
44 }
45 })
46 .change_context(ParseExperimentError)?;
47
48 Ok(values)
49}
50
51#[derive(Debug)]
52struct ExperimentError;
53
54impl fmt::Display for ExperimentError {
55 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
56 fmt.write_str("experiment error: could not run experiment")
57 }
58}
59
60impl Error for ExperimentError {}
61
62#[expect(
63 clippy::manual_try_fold,
64 reason = "false-positive, try_fold is fail-fast, our implementation is fail-slow"
65)]
66fn start_experiments(
67 experiment_ids: &[usize],
68 experiment_descriptions: &[&str],
69) -> Result<Vec<u64>, Report<[ExperimentError]>> {
70 let experiments = experiment_ids
71 .iter()
72 .map(|exp_id| {
73 let description = experiment_descriptions.get(*exp_id).ok_or_else(|| {
74 Report::new(ExperimentError)
75 .attach(format!("experiment {exp_id} has no valid description"))
76 })?;
77
78 let experiments = parse_experiment(description)
79 .attach(format!("experiment {exp_id} could not be parsed"))
80 .change_context(ExperimentError)?;
81
82 let experiments = experiments
83 .into_iter()
84 .map(|(lhs, rhs)| move || lhs * rhs)
85 .collect::<Vec<_>>();
86
87 Ok(experiments)
88 })
89 .fold(
90 Ok(vec![]),
91 |accum, value: Result<_, Report<ExperimentError>>| match (accum, value) {
92 (Ok(mut accum), Ok(value)) => {
93 accum.extend(value);
94
95 Ok(accum)
96 }
97 (Ok(_), Err(err)) => Err(err.expand()),
98 (Err(accum), Ok(_)) => Err(accum),
99 (Err(mut accum), Err(err)) => {
100 accum.push(err);
101
102 Err(accum)
103 }
104 },
105 )
106 .attach("unable to set up experiments")?;
107
108 Ok(experiments.iter().map(|experiment| experiment()).collect())
109}
Sourcepub fn current_frame(&self) -> &Frame
pub fn current_frame(&self) -> &Frame
Return the direct current frames of this report,
to get an iterator over the topological sorting of all frames refer to frames()
This is not the same as Report::current_context
, this function gets the underlying
frames that make up this report, while Report::current_context
traverses the stack of
frames to find the current context. A Report
and be made up of multiple Frame
s,
which stack on top of each other. Considering PrintableA<PrintableA<Context>>
,
Report::current_frame
will return the “outer” layer PrintableA
, while
Report::current_context
will return the underlying Error
(the current type
parameter of this Report
)
A report can be made up of multiple stacks of frames and builds a “group” of them, this can
be achieved through first calling Report::expand
and then either using Extend
or Report::push
.
Sourcepub fn current_context(&self) -> &C
pub fn current_context(&self) -> &C
Returns the current context of the Report
.
If the user want to get the latest context, current_context
can be called. If the user
wants to handle the error, the context can then be used to directly access the context’s
type. This is only possible for the latest context as the Report does not have multiple
generics as this would either require variadic generics or a workaround like tuple-list.
This is one disadvantage of the library in comparison to plain Errors, as in these cases, all context types are known.
§Example
use std::io;
fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
...
}
let report = read_file("test.txt").unwrap_err();
let io_error = report.current_context();
assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
Source§impl<C: ?Sized> Report<C>
impl<C: ?Sized> Report<C>
Sourcepub fn attach<A>(self, attachment: A) -> Selfwhere
A: Attachment,
pub fn attach<A>(self, attachment: A) -> Selfwhere
A: Attachment,
Adds additional (printable) information to the Frame
stack.
This behaves like attach_opaque()
but the display implementation will be called when
printing the Report
.
Note: attach_opaque()
will be deprecated when specialization is stabilized and
it becomes possible to merge these two methods.
§Example
use core::fmt;
use std::fs;
use error_stack::ResultExt;
#[derive(Debug)]
pub struct Suggestion(&'static str);
impl fmt::Display for Suggestion {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.0)
}
}
let error = fs::read_to_string("config.txt")
.attach(Suggestion("better use a file which exists next time!"));
let report = error.unwrap_err();
let suggestion = report.request_ref::<Suggestion>().next().unwrap();
assert_eq!(suggestion.0, "better use a file which exists next time!");
Examples found in repository?
More examples
66fn start_experiments(
67 experiment_ids: &[usize],
68 experiment_descriptions: &[&str],
69) -> Result<Vec<u64>, Report<[ExperimentError]>> {
70 let experiments = experiment_ids
71 .iter()
72 .map(|exp_id| {
73 let description = experiment_descriptions.get(*exp_id).ok_or_else(|| {
74 Report::new(ExperimentError)
75 .attach(format!("experiment {exp_id} has no valid description"))
76 })?;
77
78 let experiments = parse_experiment(description)
79 .attach(format!("experiment {exp_id} could not be parsed"))
80 .change_context(ExperimentError)?;
81
82 let experiments = experiments
83 .into_iter()
84 .map(|(lhs, rhs)| move || lhs * rhs)
85 .collect::<Vec<_>>();
86
87 Ok(experiments)
88 })
89 .fold(
90 Ok(vec![]),
91 |accum, value: Result<_, Report<ExperimentError>>| match (accum, value) {
92 (Ok(mut accum), Ok(value)) => {
93 accum.extend(value);
94
95 Ok(accum)
96 }
97 (Ok(_), Err(err)) => Err(err.expand()),
98 (Err(accum), Ok(_)) => Err(accum),
99 (Err(mut accum), Err(err)) => {
100 accum.push(err);
101
102 Err(accum)
103 }
104 },
105 )
106 .attach("unable to set up experiments")?;
107
108 Ok(experiments.iter().map(|experiment| experiment()).collect())
109}
Sourcepub fn attach_opaque<A>(self, attachment: A) -> Selfwhere
A: OpaqueAttachment,
pub fn attach_opaque<A>(self, attachment: A) -> Selfwhere
A: OpaqueAttachment,
Adds additional information to the Frame
stack.
This behaves like attach()
but will not be shown when printing the Report
.
To benefit from seeing attachments in normal error outputs, use attach()
Note: This will be deprecated in favor of attach()
when specialization is
stabilized it becomes possible to merge these two methods.
pub fn attach_printable<A>(self, attachment: A) -> Selfwhere
A: Attachment,
attach
instead. attach
was renamed to attach_opaque
and attach_printable
was renamed to attach
Sourcepub fn change_context<T>(self, context: T) -> Report<T>where
T: Context,
pub fn change_context<T>(self, context: T) -> Report<T>where
T: Context,
Sourcepub fn frames_mut(&mut self) -> FramesMut<'_> ⓘ
pub fn frames_mut(&mut self) -> FramesMut<'_> ⓘ
Returns an iterator over the Frame
stack of the report with mutable elements.
Sourcepub fn request_ref<T: ?Sized + Send + Sync + 'static>(
&self,
) -> RequestRef<'_, T> ⓘ
Available on nightly
only.
pub fn request_ref<T: ?Sized + Send + Sync + 'static>( &self, ) -> RequestRef<'_, T> ⓘ
nightly
only.Sourcepub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> ⓘ
Available on nightly
only.
pub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> ⓘ
nightly
only.Sourcepub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T>
pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T>
Searches the frame stack for a context provider T
and returns the most recent context
found.
T
can either be an attachment or a Context
.
§Example
use std::io;
fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
...
}
let report = read_file("test.txt").unwrap_err();
let io_error = report.downcast_ref::<io::Error>().unwrap();
assert_eq!(io_error.kind(), io::ErrorKind::NotFound);
Source§impl<C> Report<[C]>
impl<C> Report<[C]>
Sourcepub fn current_frames(&self) -> &[Frame]
pub fn current_frames(&self) -> &[Frame]
Return the direct current frames of this report,
to get an iterator over the topological sorting of all frames refer to frames()
This is not the same as Report::current_context
, this function gets the underlying
frames that make up this report, while Report::current_context
traverses the stack of
frames to find the current context. A Report
and be made up of multiple Frame
s,
which stack on top of each other. Considering PrintableA<PrintableA<Context>>
,
Report::current_frames
will return the “outer” layer PrintableA
, while
Report::current_context
will return the underlying Error
(the current type
parameter of this Report
)
Using Extend
, push()
and append()
, a Report
can additionally be made up of
multiple stacks of frames and builds a “group” of them, therefore this function returns a
slice instead, while Report::current_context
only returns a single reference.
Sourcepub fn push(&mut self, report: Report<C>)
pub fn push(&mut self, report: Report<C>)
Pushes a new context to the Report
.
This function adds a new Frame
to the current frames with the frame from the given
Report
.
§Example
use std::{fmt, path::Path};
use error_stack::{Report, ResultExt};
#[derive(Debug)]
struct IoError;
impl fmt::Display for IoError {
...
}
fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
std::fs::read_to_string(path.as_ref())
.change_context(IoError)
}
let mut error1 = read_config("config.txt").unwrap_err().expand();
let error2 = read_config("config2.txt").unwrap_err();
let error3 = read_config("config3.txt").unwrap_err();
error1.push(error2);
error1.push(error3);
Examples found in repository?
23fn parse_experiment(description: &str) -> Result<Vec<(u64, u64)>, Report<ParseExperimentError>> {
24 let values = description
25 .split(' ')
26 .map(|value| {
27 value
28 .parse::<u64>()
29 .attach_with(|| format!("{value:?} could not be parsed as experiment"))
30 })
31 .map(|value| value.map(|ok| (ok, 2 * ok)))
32 .fold(Ok(vec![]), |accum, value| match (accum, value) {
33 (Ok(mut accum), Ok(value)) => {
34 accum.push(value);
35
36 Ok(accum)
37 }
38 (Ok(_), Err(err)) => Err(err.expand()),
39 (Err(accum), Ok(_)) => Err(accum),
40 (Err(mut accum), Err(err)) => {
41 accum.push(err);
42
43 Err(accum)
44 }
45 })
46 .change_context(ParseExperimentError)?;
47
48 Ok(values)
49}
50
51#[derive(Debug)]
52struct ExperimentError;
53
54impl fmt::Display for ExperimentError {
55 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
56 fmt.write_str("experiment error: could not run experiment")
57 }
58}
59
60impl Error for ExperimentError {}
61
62#[expect(
63 clippy::manual_try_fold,
64 reason = "false-positive, try_fold is fail-fast, our implementation is fail-slow"
65)]
66fn start_experiments(
67 experiment_ids: &[usize],
68 experiment_descriptions: &[&str],
69) -> Result<Vec<u64>, Report<[ExperimentError]>> {
70 let experiments = experiment_ids
71 .iter()
72 .map(|exp_id| {
73 let description = experiment_descriptions.get(*exp_id).ok_or_else(|| {
74 Report::new(ExperimentError)
75 .attach(format!("experiment {exp_id} has no valid description"))
76 })?;
77
78 let experiments = parse_experiment(description)
79 .attach(format!("experiment {exp_id} could not be parsed"))
80 .change_context(ExperimentError)?;
81
82 let experiments = experiments
83 .into_iter()
84 .map(|(lhs, rhs)| move || lhs * rhs)
85 .collect::<Vec<_>>();
86
87 Ok(experiments)
88 })
89 .fold(
90 Ok(vec![]),
91 |accum, value: Result<_, Report<ExperimentError>>| match (accum, value) {
92 (Ok(mut accum), Ok(value)) => {
93 accum.extend(value);
94
95 Ok(accum)
96 }
97 (Ok(_), Err(err)) => Err(err.expand()),
98 (Err(accum), Ok(_)) => Err(accum),
99 (Err(mut accum), Err(err)) => {
100 accum.push(err);
101
102 Err(accum)
103 }
104 },
105 )
106 .attach("unable to set up experiments")?;
107
108 Ok(experiments.iter().map(|experiment| experiment()).collect())
109}
Sourcepub fn append(&mut self, report: Self)
pub fn append(&mut self, report: Self)
Appends the frames from another Report
to this one.
This method combines the frames of the current Report
with those of the provided Report
,
effectively merging the two error reports.
§Example
use std::{fmt, path::Path};
use error_stack::{Report, ResultExt};
#[derive(Debug)]
struct IoError;
impl fmt::Display for IoError {
...
}
fn read_config(path: impl AsRef<Path>) -> Result<String, Report<IoError>> {
std::fs::read_to_string(path.as_ref())
.change_context(IoError)
}
let mut error1 = read_config("config.txt").unwrap_err().expand();
let error2 = read_config("config2.txt").unwrap_err();
let mut error3 = read_config("config3.txt").unwrap_err().expand();
error1.push(error2);
error3.append(error1);
Sourcepub fn current_contexts(&self) -> impl Iterator<Item = &C>
pub fn current_contexts(&self) -> impl Iterator<Item = &C>
Returns an iterator over the current contexts of the Report
.
This method is similar to current_context
, but instead of returning a single context,
it returns an iterator over all contexts in the Report
.
The order of the contexts should not be relied upon, as it is not guaranteed to be stable.
§Example
use std::io;
fn read_file(path: impl AsRef<Path>) -> Result<String, Report<io::Error>> {
...
}
let mut a = read_file("test.txt").unwrap_err().expand();
let b = read_file("test2.txt").unwrap_err();
a.push(b);
let io_error = a.current_contexts();
assert_eq!(io_error.count(), 2);
Source§impl Report<()>
impl Report<()>
Sourcepub fn set_charset(charset: Charset)
pub fn set_charset(charset: Charset)
Set the charset preference
The value defaults to Charset::Utf8
.
§Example
use std::io::{Error, ErrorKind};
use error_stack::{Report, IntoReport};
use error_stack::fmt::{Charset};
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
match context.charset() {
Charset::Utf8 => context.push_body(format!("📝 {value}")),
Charset::Ascii => context.push_body(format!("suggestion: {value}"))
};
});
let report =
Error::from(ErrorKind::InvalidInput).into_report().attach_opaque(Suggestion("oh no, try again"));
Report::set_charset(Charset::Utf8);
println!("{report:?}");
Report::set_charset(Charset::Ascii);
println!("{report:?}");
Which will result in something like:
invalid input parameter ├╴at libs/error-stack/src/fmt/charset.rs:24:42 ├╴backtrace (1) ╰╴📝 oh no, try again ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
invalid input parameter |-at libs/error-stack/src/fmt/charset.rs:24:42 |-backtrace (1) |-suggestion: oh no, try again ======================================== backtrace no. 1 [redacted]
Examples found in repository?
41fn main() {
42 // error-stack only uses ANSI codes for colors
43 let supports_color = supports_color::on_cached(supports_color::Stream::Stdout)
44 .is_some_and(|level| level.has_basic);
45
46 let color_mode = if supports_color {
47 ColorMode::Color
48 } else {
49 ColorMode::None
50 };
51
52 let supports_unicode = supports_unicode::on(supports_unicode::Stream::Stdout);
53
54 let charset = if supports_unicode {
55 Charset::Utf8
56 } else {
57 Charset::Ascii
58 };
59
60 Report::set_color_mode(color_mode);
61 Report::set_charset(charset);
62
63 if let Err(err) = parse_config("config.json") {
64 // if you would use `eprintln!` instead, you should check support on `Stream::Stderr`
65 // instead.
66 println!("{err:?}");
67 }
68}
Source§impl Report<()>
impl Report<()>
Sourcepub fn set_color_mode(mode: ColorMode)
pub fn set_color_mode(mode: ColorMode)
Set the color mode preference
If no ColorMode
is set, it defaults to ColorMode::Emphasis
.
§Example
use std::io::{Error, ErrorKind};
use owo_colors::OwoColorize;
use error_stack::{Report, IntoReport};
use error_stack::fmt::ColorMode;
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
let body = format!("suggestion: {value}");
match context.color_mode() {
ColorMode::Color => context.push_body(body.green().to_string()),
ColorMode::Emphasis => context.push_body(body.italic().to_string()),
ColorMode::None => context.push_body(body)
};
});
let report =
Error::from(ErrorKind::InvalidInput).into_report().attach_opaque(Suggestion("oh no, try again"));
Report::set_color_mode(ColorMode::None);
println!("{report:?}");
Report::set_color_mode(ColorMode::Emphasis);
println!("{report:?}");
Report::set_color_mode(ColorMode::Color);
println!("{report:?}");
Which will result in something like:
invalid input parameter ├╴at libs/error-stack/src/fmt/color.rs:27:42 ├╴backtrace (1) ╰╴suggestion: oh no, try again ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
invalid input parameter ├╴at libs/error-stack/src/fmt/color.rs:27:42 ├╴backtrace (1) ╰╴suggestion: oh no, try again ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
invalid input parameter ├╴at libs/error-stack/src/fmt/color.rs:27:42 ├╴backtrace (1) ╰╴suggestion: oh no, try again ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
Examples found in repository?
41fn main() {
42 // error-stack only uses ANSI codes for colors
43 let supports_color = supports_color::on_cached(supports_color::Stream::Stdout)
44 .is_some_and(|level| level.has_basic);
45
46 let color_mode = if supports_color {
47 ColorMode::Color
48 } else {
49 ColorMode::None
50 };
51
52 let supports_unicode = supports_unicode::on(supports_unicode::Stream::Stdout);
53
54 let charset = if supports_unicode {
55 Charset::Utf8
56 } else {
57 Charset::Ascii
58 };
59
60 Report::set_color_mode(color_mode);
61 Report::set_charset(charset);
62
63 if let Err(err) = parse_config("config.json") {
64 // if you would use `eprintln!` instead, you should check support on `Stream::Stderr`
65 // instead.
66 println!("{err:?}");
67 }
68}
Source§impl Report<()>
impl Report<()>
Sourcepub fn install_debug_hook<T: Send + Sync + 'static>(
hook: impl Fn(&T, &mut HookContext<T>) + Send + Sync + 'static,
)
Available on crate features std
or hooks
only.
pub fn install_debug_hook<T: Send + Sync + 'static>( hook: impl Fn(&T, &mut HookContext<T>) + Send + Sync + 'static, )
std
or hooks
only.Can be used to globally set a Debug
format hook, for a specific type T
.
This hook will be called on every Debug
call, if an attachment with the same type has
been found.
§Examples
use std::io::{Error, ErrorKind};
use error_stack::{
Report, IntoReport,
};
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|value, context| {
context.push_body(format!("suggestion: {}", value.0));
});
let report =
Error::from(ErrorKind::InvalidInput).into_report().attach_opaque(Suggestion("oh no, try again"));
println!("{report:?}");
Which will result in something like:
invalid input parameter ├╴at libs/error-stack/src/hook/mod.rs:22:42 ├╴backtrace (1) ╰╴suggestion: oh no, try again ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
This example showcases the ability of hooks to be invoked for values provided via the
Provider API using Error::provide
.
#![feature(error_generic_member_access)]
use core::error::{Request, Error};
use core::fmt;
use error_stack::{Report, IntoReport};
struct Suggestion(&'static str);
#[derive(Debug)]
struct ErrorCode(u64);
#[derive(Debug)]
struct UserError {
code: ErrorCode
}
impl fmt::Display for UserError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("invalid user input")
}
}
impl Error for UserError {
fn provide<'a>(&'a self, req: &mut Request<'a>) {
req.provide_value(Suggestion("try better next time!"));
req.provide_ref(&self.code);
}
}
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
context.push_body(format!("suggestion: {value}"));
});
Report::install_debug_hook::<ErrorCode>(|ErrorCode(value), context| {
context.push_body(format!("error code: {value}"));
});
let report = UserError {code: ErrorCode(420)}.into_report();
println!("{report:?}");
Which will result in something like:
invalid user input ├╴suggestion: try better next time! ├╴error code: 420 ├╴at libs/error-stack/src/hook/mod.rs:51:47 ╰╴backtrace (1) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
error-stack
comes with some built-in hooks which can be overwritten. This is useful if you
want to change the output of the built-in hooks, or if you want to add additional
information to the output. For example, you can override the built-in hook for Location
to hide the file path:
use std::{
io::{Error, ErrorKind},
panic::Location,
};
use error_stack::IntoReport;
error_stack::Report::install_debug_hook::<Location>(|_location, _context| {
// Intentionally left empty so nothing will be printed
});
let report = Error::from(ErrorKind::InvalidInput).into_report();
println!("{report:?}");
Which will result in something like:
invalid input parameter ╰╴backtrace (1) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
Trait Implementations§
Source§impl<C> Extend<Report<[C]>> for Report<[C]>
impl<C> Extend<Report<[C]>> for Report<[C]>
Source§fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T)
fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T)
Source§fn extend_one(&mut self, item: A)
fn extend_one(&mut self, item: A)
extend_one
)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one
)Source§impl<C> Extend<Report<C>> for Report<[C]>
impl<C> Extend<Report<C>> for Report<[C]>
Source§fn extend<T: IntoIterator<Item = Report<C>>>(&mut self, iter: T)
fn extend<T: IntoIterator<Item = Report<C>>>(&mut self, iter: T)
Source§fn extend_one(&mut self, item: A)
fn extend_one(&mut self, item: A)
extend_one
)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one
)