Struct error_stack::fmt::HookContext

source ·
pub struct HookContext<T> { /* private fields */ }
Expand description

Carrier for contextual information used across hook invocations.

HookContext has two fundamental use-cases:

  1. Adding body entries and appendix entries
  2. 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>

source

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>

source

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).

source

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).

source

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.

source

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.

source

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]
source

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>

source

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.

source

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.

source

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
source

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]
source

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.

Auto Trait Implementations§

§

impl<T> Freeze for HookContext<T>

§

impl<T> !RefUnwindSafe for HookContext<T>

§

impl<T> !Send for HookContext<T>

§

impl<T> !Sync for HookContext<T>

§

impl<T> Unpin for HookContext<T>

§

impl<T> !UnwindSafe for HookContext<T>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.