#[repr(transparent)]
pub struct HookContext<T> { /* private fields */ }
Available on crate feature std only.
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
 src/fmt/hook.rs:49: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
 src/fmt/hook.rs:49: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
 src/fmt/hook.rs:31:14
 backtrace (1)
 computation for 2 (acc = 2, div = 0.5)
 computation for 3 (acc = 5, div = 0.16666667)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

backtrace no. 1
  [redacted]

Implementations

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
 src/fmt/hook.rs:24: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

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
 src/fmt/hook.rs:19:14
 backtrace (1)
 suggestion: try better next time
 sorry for the inconvenience!

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

backtrace no. 1
  [redacted]

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
 src/fmt/hook.rs:27: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]

Returns if the currently requested format should render the alternate representation.

This corresponds to the output of std::fmt::Formatter::alternate.

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 retained during the invocation of Debug.

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 retained during the invocation of Debug.

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.

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.

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
 src/fmt/hook.rs:17: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]

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
 src/fmt/hook.rs:17: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]

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Instruments this type with the current Span, returning an Instrumented wrapper. Read more

Calls U::from(self).

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

Set the foreground color generically Read more
Set the background color generically. Read more
Change the foreground color to black
Change the background color to black
Change the foreground color to red
Change the background color to red
Change the foreground color to green
Change the background color to green
Change the foreground color to yellow
Change the background color to yellow
Change the foreground color to blue
Change the background color to blue
Change the foreground color to magenta
Change the background color to magenta
Change the foreground color to purple
Change the background color to purple
Change the foreground color to cyan
Change the background color to cyan
Change the foreground color to white
Change the background color to white
Change the foreground color to the terminal default
Change the background color to the terminal default
Change the foreground color to bright black
Change the background color to bright black
Change the foreground color to bright red
Change the background color to bright red
Change the foreground color to bright green
Change the background color to bright green
Change the foreground color to bright yellow
Change the background color to bright yellow
Change the foreground color to bright blue
Change the background color to bright blue
Change the foreground color to bright magenta
Change the background color to bright magenta
Change the foreground color to bright purple
Change the background color to bright purple
Change the foreground color to bright cyan
Change the background color to bright cyan
Change the foreground color to bright white
Change the background color to bright white
Make the text bold
Make the text dim
Make the text italicized
Make the text italicized
Make the text blink
Make the text blink (but fast!)
Swap the foreground and background colors
Hide the text
Cross out the text
Set the foreground color at runtime. Only use if you do not know which color will be used at compile-time. If the color is constant, use either OwoColorize::fg or a color-specific method, such as OwoColorize::green, Read more
Set the background color at runtime. Only use if you do not know what color to use at compile-time. If the color is constant, use either OwoColorize::bg or a color-specific method, such as OwoColorize::on_yellow, Read more
Set the foreground color to a specific RGB value.
Set the background color to a specific RGB value.
Sets the foreground color to an RGB value.
Sets the background color to an RGB value.
Apply a runtime-determined style
Available on crate feature supports-colors only.
Apply a given transformation function to all formatters if the given stream supports at least basic ANSI colors, allowing you to conditionally apply given styles/colors. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more