Add a support footer¶
Register the closure¶
fn main() -> miette::Result<()> {
rtb_error::hook::install_with_footer(|| {
"Report bugs at https://gitlab.com/example/greet/-/issues".to_string()
});
rtb_error::hook::install_panic_hook();
run()?;
Ok(())
}
install_with_footer installs the report handler for you, so there is no need
to call install_report_handler as well. Every rendered diagnostic now ends
with a blank line and the footer:
Error: greet::no_name
x no name given
help: pass a name: `greet Ada`
Report bugs at https://gitlab.com/example/greet/-/issues
Panics get it too, as long as install_panic_hook was also called — they
render through the same handler.
Set it before anything can fail¶
miette's hook slot is filled by the first Report constructed anywhere in
the process. If that happens before your install call, this crate's handler is
never installed and the footer is never read — with no error and no warning.
Register the footer as the first statement in main.
Change the footer once configuration is loaded¶
Call install_with_footer again. The most recent closure wins, and the
handler reads it at render time rather than install time, so the change applies
to every diagnostic from that point on:
fn main() -> miette::Result<()> {
rtb_error::hook::install_with_footer(|| {
"Report bugs at https://gitlab.com/example/greet/-/issues".to_string()
});
let config = load_config()?;
let channel = config.support_channel.clone();
rtb_error::hook::install_with_footer(move || format!("Support: {channel}"));
run()?;
Ok(())
}
The closure must be Send + Sync + 'static, so anything it captures has to be
owned — move and a cloned String, not a borrow of config.
Suppress the footer for one render¶
Return an empty string. No blank line is emitted:
rtb_error::hook::install_with_footer(|| {
if std::env::var_os("GREET_QUIET").is_some() {
String::new()
} else {
"Report bugs at https://gitlab.com/example/greet/-/issues".to_string()
}
});
The closure takes no arguments, so this is the only conditional available — it cannot see which diagnostic is being rendered.
Keep the closure trivial¶
It runs on every render, inside the rendering path, while a lock is held. If it panics, the panic is caught and the footer is dropped for that render, and nothing reports the suppression — the diagnostic body renders normally and the footer is simply absent. A footer that quietly stops appearing is very hard to trace back.
Format a string that is already in hand. Do not read a file, call a network, or
unwrap an Option in there.
Test that it appears¶
Assert against Report's Debug output, which is the path that goes through
the hook:
rtb_error::hook::install_with_footer(|| "support: #team".to_string());
let report = miette::Report::new(GreetError::NoName);
assert!(format!("{report:?}").contains("support: #team"));
GraphicalReportHandler::render_report called directly bypasses the hook and
will never show the footer, which makes it the right tool for tests that want
deterministic diagnostic output without one.
Because miette's hook is process-global and set-once, tests that install
different hooks interfere with each other under a shared-process test runner.
This repository uses cargo nextest, which runs each test
in its own process; cargo test alone may give different results.