use crate::console::Console;
use crate::panel::Panel;
use crate::style::Style;
use crate::text::Text;
use std::fmt;
pub struct GiltEyreHandler;
impl eyre::EyreHandler for GiltEyreHandler {
fn debug(
&self,
error: &(dyn std::error::Error + 'static),
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let width = f.width().unwrap_or(80);
let mut console = Console::builder()
.width(width)
.force_terminal(true)
.no_color(false)
.build();
console.begin_capture();
let title = format!("{error}");
let mut markup = format!("[bold red]Error:[/bold red] {title}");
let mut source = error.source();
if source.is_some() {
markup.push_str("\n\n[bold]Caused by:[/bold]");
}
let mut i = 0;
while let Some(err) = source {
markup.push_str(&format!("\n {i}. {err}"));
source = err.source();
i += 1;
}
let text = Text::from_markup(&markup).unwrap_or_else(|_| Text::new(&markup, Style::null()));
let mut panel = Panel::new(text);
panel.title = Some(Text::new("Error Report", Style::null()));
console.print(&panel);
let output = console.end_capture();
write!(f, "{}", output.trim_end_matches('\n'))
}
}
pub fn install() -> Result<(), eyre::InstallError> {
eyre::set_hook(Box::new(|_| Box::new(GiltEyreHandler)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handler_creation() {
let _handler = GiltEyreHandler;
}
#[test]
fn test_install_returns_ok_or_already_installed() {
let _ = install();
}
#[test]
fn test_handler_formats_simple_error() {
let _ = install();
#[derive(Debug, thiserror::Error)]
#[error("something went wrong")]
struct TestError;
let report = eyre::Report::new(TestError);
let output = format!("{report:?}");
assert!(
output.contains("something went wrong"),
"expected error message in output, got: {output}",
);
}
#[test]
fn test_handler_formats_chained_error() {
let _ = install();
#[derive(Debug, thiserror::Error)]
#[error("outer error")]
struct OuterError(#[from] InnerError);
#[derive(Debug, thiserror::Error)]
#[error("inner error")]
struct InnerError;
let inner = InnerError;
let outer = OuterError(inner);
let report = eyre::Report::new(outer);
let output = format!("{report:?}");
assert!(
output.contains("outer error"),
"expected outer error message in output, got: {output}",
);
}
#[test]
fn test_eyre_deeply_chained() {
#[derive(Debug, thiserror::Error)]
#[error("level {0}")]
struct Level(u8, #[source] Option<Box<Level>>);
let mut chain = Level(4, None);
for i in (0..4).rev() {
chain = Level(i, Some(Box::new(chain)));
}
let handler = GiltEyreHandler;
let output = format!("{}", DisplayViaDebugHandler(&handler, &chain));
for i in 0..5u8 {
let needle = format!("level {i}");
assert!(
output.contains(&needle),
"expected '{needle}' in output, got: {output}",
);
}
assert!(
output.contains("Caused by"),
"expected 'Caused by' in output, got: {output}",
);
}
#[test]
fn test_eyre_with_context() {
use eyre::WrapErr;
let _ = install();
#[derive(Debug, thiserror::Error)]
#[error("root cause")]
struct RootCause;
let report: eyre::Result<()> = Err(eyre::Report::new(RootCause));
let report = report.wrap_err("added context message");
let output = format!("{:?}", report.unwrap_err());
assert!(
output.contains("added context message"),
"expected context message in output, got: {output}",
);
}
#[test]
fn test_eyre_custom_sections() {
use eyre::WrapErr;
let _ = install();
#[derive(Debug, thiserror::Error)]
#[error("base error")]
struct BaseError;
let report: eyre::Result<()> = Err(eyre::Report::new(BaseError));
let report = report
.wrap_err("section: retry logic")
.wrap_err("section: network layer");
let output = format!("{:?}", report.unwrap_err());
assert!(
output.contains("network layer"),
"expected section header in output, got: {output}",
);
assert!(
output.contains("retry logic"),
"expected section header in output, got: {output}",
);
}
#[test]
fn test_eyre_display_format() {
let _ = install();
#[derive(Debug, thiserror::Error)]
#[error("display and debug error")]
struct DualFormatError;
let report = eyre::Report::new(DualFormatError);
let display_output = format!("{report}");
assert!(
display_output.contains("display and debug error"),
"expected error in Display output, got: {display_output}",
);
let debug_output = format!("{report:?}");
assert!(
debug_output.contains("display and debug error"),
"expected error in Debug output, got: {debug_output}",
);
assert!(
debug_output.len() >= display_output.len(),
"Debug output should be at least as long as Display output",
);
}
struct DisplayViaDebugHandler<'a, E: std::error::Error + 'static>(&'a GiltEyreHandler, &'a E);
impl<E: std::error::Error + 'static> fmt::Display for DisplayViaDebugHandler<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use eyre::EyreHandler;
self.0.debug(self.1, f)
}
}
}