pub struct HookContext<T> { /* private fields */ }
Expand description
Carrier for contextual information used across hook invocations.
HookContext
has two fundamental use-cases:
- Adding body entries and appendix entries
- Storage
§Adding body entries and appendix entries
A Debug
backtrace consists of two different sections, a rendered tree of objects (the
body) and additional text/information that is too large to fit into the tree (the
appendix).
Entries for the body can be attached to the rendered tree of objects via
HookContext::push_body
. An appendix entry can be attached via
HookContext::push_appendix
.
§Example
use std::io::{Error, ErrorKind};
use error_stack::Report;
struct Warning(&'static str);
struct HttpResponseStatusCode(u64);
struct Suggestion(&'static str);
struct Secret(&'static str);
Report::install_debug_hook::<HttpResponseStatusCode>(|HttpResponseStatusCode(value), context| {
// Create a new appendix, which is going to be displayed when someone requests the alternate
// version (`:#?`) of the report.
if context.alternate() {
context.push_appendix(format!("error {value}: {} error", if *value < 500 {"client"} else {"server"}))
}
// This will push a new entry onto the body with the specified value
context.push_body(format!("error code: {value}"));
});
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
let idx = context.increment_counter();
// Create a new appendix, which is going to be displayed when someone requests the alternate
// version (`:#?`) of the report.
if context.alternate() {
context.push_body(format!("suggestion {idx}:\n {value}"));
}
// This will push a new entry onto the body with the specified value
context.push_body(format!("suggestion ({idx})"));
});
Report::install_debug_hook::<Warning>(|Warning(value), context| {
// You can add multiples entries to the body (and appendix) in the same hook.
context.push_body("abnormal program execution detected");
context.push_body(format!("warning: {value}"));
});
// By not adding anything you are able to hide an attachment
// (it will still be counted towards opaque attachments)
Report::install_debug_hook::<Secret>(|_, _| {});
let report = Report::new(Error::from(ErrorKind::InvalidInput))
.attach(HttpResponseStatusCode(404))
.attach(Suggestion("do you have a connection to the internet?"))
.attach(HttpResponseStatusCode(405))
.attach(Warning("unable to determine environment"))
.attach(Secret("pssst, don't tell anyone else c;"))
.attach(Suggestion("execute the program from the fish shell"))
.attach(HttpResponseStatusCode(501))
.attach(Suggestion("try better next time!"));
println!("{report:?}");
println!("{report:#?}");
The output of println!("{report:?}")
:
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:51:14 ├╴backtrace (1) ├╴error code: 404 ├╴suggestion (0) ├╴error code: 405 ├╴abnormal program execution detected ├╴warning: unable to determine environment ├╴suggestion (1) ├╴error code: 501 ╰╴suggestion (2) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
The output of println!("{report:#?}")
:
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:51:14 ├╴backtrace (1) ├╴error code: 404 ├╴suggestion 0: │ do you have a connection to the internet? ├╴suggestion (0) ├╴error code: 405 ├╴abnormal program execution detected ├╴warning: unable to determine environment ├╴suggestion 1: │ execute the program from the fish shell ├╴suggestion (1) ├╴error code: 501 ├╴suggestion 2: │ try better next time! ╰╴suggestion (2) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted] error 404: client error error 405: client error error 501: server error
§Storage
HookContext
can be used to store and retrieve values that are going to be used on multiple
hook invocations in a single Debug
call.
Every hook can request their corresponding HookContext
.
This is especially useful for incrementing/decrementing values, but can also be used to store
any arbitrary value for the duration of the Debug
invocation.
All data stored in HookContext
is completely separated from all other hooks and can store
any arbitrary data of any type, and even data of multiple types at the same time.
§Example
use std::io::ErrorKind;
use error_stack::Report;
struct Computation(u64);
Report::install_debug_hook::<Computation>(|Computation(value), context| {
// Get a value of type `u64`, if we didn't insert one yet, default to 0
let mut acc = context.get::<u64>().copied().unwrap_or(0);
acc += *value;
// Get a value of type `f64`, if we didn't insert one yet, default to 1.0
let mut div = context.get::<f32>().copied().unwrap_or(1.0);
div /= *value as f32;
// Insert the calculated `u64` and `f32` back into storage, so that we can use them
// in the invocations following this one (for the same `Debug` call)
context.insert(acc);
context.insert(div);
context.push_body(format!(
"computation for {value} (acc = {acc}, div = {div})"
));
});
let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput))
.attach(Computation(2))
.attach(Computation(3));
println!("{report:?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:32:14 ├╴backtrace (1) ├╴computation for 2 (acc = 2, div = 0.5) ╰╴computation for 3 (acc = 5, div = 0.16666667) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
Implementations§
source§impl<T> HookContext<T>
impl<T> HookContext<T>
sourcepub fn cast<U>(&mut self) -> &mut HookContext<U>
pub fn cast<U>(&mut self) -> &mut HookContext<U>
Cast the HookContext
to a new type U
.
The storage of HookContext
is partitioned, meaning that if T
and U
are different
types the values stored in HookContext<_, T>
will be separated from values in
HookContext<_, U>
.
In most situations this functions isn’t needed, as it transparently casts between different partitions of the storage. Only hooks that share storage with hooks of different types should need to use this function.
§Example
use std::io::ErrorKind;
use error_stack::Report;
struct Warning(&'static str);
struct Error(&'static str);
Report::install_debug_hook::<Error>(|Error(frame), context| {
let idx = context.increment_counter() + 1;
context.push_body(format!("[{idx}] [ERROR] {frame}"));
});
Report::install_debug_hook::<Warning>(|Warning(frame), context| {
// We want to share the same counter with `Error`, so that we're able to have
// a global counter to keep track of all errors and warnings in order, this means
// we need to access the storage of `Error` using `cast()`.
let context = context.cast::<Error>();
let idx = context.increment_counter() + 1;
context.push_body(format!("[{idx}] [WARN] {frame}"))
});
let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput))
.attach(Error("unable to reach remote host"))
.attach(Warning("disk nearly full"))
.attach(Error("cannot resolve example.com: unknown host"));
println!("{report:?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:28:14 ├╴backtrace (1) ├╴[1] [ERROR] unable to reach remote host ├╴[2] [WARN] disk nearly full ╰╴[3] [ERROR] cannot resolve example.com: unknown host ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
source§impl<T: 'static> HookContext<T>
impl<T: 'static> HookContext<T>
sourcepub fn get<U: 'static>(&self) -> Option<&U>
pub fn get<U: 'static>(&self) -> Option<&U>
Return a reference to a value of type U
, if a value of that type exists.
Values returned are isolated and “bound” to T
, this means that HookContext<_, Warning>
and HookContext<_, Error>
do not share the same values. Values are only valid during the
invocation of the corresponding call (e.g. Debug
).
sourcepub fn get_mut<U: 'static>(&mut self) -> Option<&mut U>
pub fn get_mut<U: 'static>(&mut self) -> Option<&mut U>
Return a mutable reference to a value of type U
, if a value of that type exists.
Values returned are isolated and “bound” to T
, this means that HookContext<_, Warning>
and HookContext<_, Error>
do not share the same values. Values are only valid during the
invocation of the corresponding call (e.g. Debug
).
sourcepub fn insert<U: 'static>(&mut self, value: U) -> Option<U>
pub fn insert<U: 'static>(&mut self, value: U) -> Option<U>
Insert a new value of type U
into the storage of HookContext
.
The returned value will the previously stored value of the same type U
scoped over type
T
, if it existed, did no such value exist it will return None
.
sourcepub fn remove<U: 'static>(&mut self) -> Option<U>
pub fn remove<U: 'static>(&mut self) -> Option<U>
Remove the value of type U
from the storage of HookContext
if it existed.
The returned value will be the previously stored value of the same type U
if it existed in
the scope of T
, did no such value exist, it will return None
.
sourcepub fn increment_counter(&mut self) -> isize
pub fn increment_counter(&mut self) -> isize
One of the most common interactions with HookContext
is a counter to reference previous
frames in an entry to the appendix that was added using HookContext::push_appendix
.
This is a utility method, which uses the other primitive methods provided to automatically
increment a counter, if the counter wasn’t initialized this method will return 0
.
use std::io::ErrorKind;
use error_stack::Report;
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
let idx = context.increment_counter();
context.push_body(format!("suggestion {idx}: {value}"));
});
let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput))
.attach(Suggestion("use a file you can read next time!"))
.attach(Suggestion("don't press any random keys!"));
println!("{report:?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:18:14 ├╴backtrace (1) ├╴suggestion 0: use a file you can read next time! ╰╴suggestion 1: don't press any random keys! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
sourcepub fn decrement_counter(&mut self) -> isize
pub fn decrement_counter(&mut self) -> isize
One of the most common interactions with HookContext
is a counter to reference previous
frames in an entry to the appendix that was added using HookContext::push_appendix
.
This is a utility method, which uses the other primitive method provided to automatically
decrement a counter, if the counter wasn’t initialized this method will return -1
to stay
consistent with HookContext::increment_counter
.
use std::io::ErrorKind;
use error_stack::Report;
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
let idx = context.decrement_counter();
context.push_body(format!("suggestion {idx}: {value}"));
});
let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput))
.attach(Suggestion("use a file you can read next time!"))
.attach(Suggestion("don't press any random keys!"));
println!("{report:?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:18:14 ├╴backtrace (1) ├╴suggestion -1: use a file you can read next time! ╰╴suggestion -2: don't press any random keys! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
source§impl<T> HookContext<T>
impl<T> HookContext<T>
sourcepub const fn color_mode(&self) -> ColorMode
pub const fn color_mode(&self) -> ColorMode
The requested ColorMode
for this invocation of hooks.
Hooks can be invoked in different color modes, which represent the preferences of an end-user.
sourcepub const fn charset(&self) -> Charset
pub const fn charset(&self) -> Charset
The requested Charset
for this invocation of hooks
Hooks can be invoked in using different charsets, which reflect the capabilities of the terminal.
sourcepub fn push_appendix(&mut self, content: impl Into<String>)
pub fn push_appendix(&mut self, content: impl Into<String>)
The contents of the appendix are going to be displayed after the body in the order they have
been pushed into the HookContext
.
This is useful for dense information like backtraces, or span traces.
§Example
use std::io::ErrorKind;
use error_stack::Report;
struct Error {
code: usize,
reason: &'static str,
}
Report::install_debug_hook::<Error>(|Error { code, reason }, context| {
if context.alternate() {
// Add an entry to the appendix
context.push_appendix(format!("error {code}:\n {reason}"));
}
context.push_body(format!("error {code}"));
});
let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput))
.attach(Error {
code: 404,
reason: "not found - server cannot find requested resource",
})
.attach(Error {
code: 405,
reason: "bad request - server cannot or will not process request",
});
println!("{report:#?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:25:14 ├╴backtrace (1) ├╴error 404 ╰╴error 405 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted] error 404: not found - server cannot find requested resource error 405: bad request - server cannot or will not process request
sourcepub fn push_body(&mut self, content: impl Into<String>)
pub fn push_body(&mut self, content: impl Into<String>)
Add a new entry to the body.
§Example
use std::io;
use error_stack::Report;
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
context.push_body(format!("suggestion: {value}"));
// We can push multiples entries in a single hook, these lines will be added one after
// another.
context.push_body("sorry for the inconvenience!");
});
let report = Report::new(io::Error::from(io::ErrorKind::InvalidInput))
.attach(Suggestion("try better next time"));
println!("{report:?}");
invalid input parameter ├╴at libs/error-stack/src/fmt/hook.rs:20:14 ├╴backtrace (1) ├╴suggestion: try better next time ╰╴sorry for the inconvenience! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ backtrace no. 1 [redacted]
sourcepub const fn alternate(&self) -> bool
pub const fn alternate(&self) -> bool
Returns if the currently requested format should render the alternate representation.
This corresponds to the output of std::fmt::Formatter::alternate
.