use std::sync::atomic::{AtomicU64, Ordering};
use rootcause::{
ReportMut,
hooks::{
Hooks,
report_creation::{AttachmentCollector, ReportCreationHook},
},
markers::{Dynamic, Local, SendSync},
prelude::*,
};
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
#[derive(Debug)]
struct RequestId(u64);
impl core::fmt::Display for RequestId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Request ID: {}", self.0)
}
}
struct RequestIdCollector;
impl AttachmentCollector<RequestId> for RequestIdCollector {
type Handler = handlers::Display;
fn collect(&self) -> RequestId {
RequestId(REQUEST_COUNTER.load(Ordering::Relaxed))
}
}
struct RetryHint(&'static str);
impl core::fmt::Display for RetryHint {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "💡 {}", self.0)
}
}
impl core::fmt::Debug for RetryHint {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
struct RetryHintHook;
impl ReportCreationHook for RetryHintHook {
fn on_local_creation(&self, mut report: ReportMut<'_, Dynamic, Local>) {
if let Some(io_error) = report.downcast_current_context::<std::io::Error>() {
let hint = match io_error.kind() {
std::io::ErrorKind::ConnectionRefused
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::ConnectionReset => {
Some(RetryHint("This is a transient error - retry may succeed"))
}
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {
Some(RetryHint("This error is permanent - retrying won't help"))
}
_ => None,
};
if let Some(hint) = hint {
report
.attachments_mut()
.push(report_attachment!(hint).into());
}
}
}
fn on_sendsync_creation(&self, mut report: ReportMut<'_, Dynamic, SendSync>) {
if let Some(io_error) = report.downcast_current_context::<std::io::Error>() {
let hint = match io_error.kind() {
std::io::ErrorKind::ConnectionRefused
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::ConnectionReset => {
Some(RetryHint("This is a transient error - retry may succeed"))
}
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {
Some(RetryHint("This error is permanent - retrying won't help"))
}
_ => None,
};
if let Some(hint) = hint {
report
.attachments_mut()
.push(report_attachment!(hint).into());
}
}
}
}
fn simulate_api_request(request_id: u64) -> Result<String, Report> {
REQUEST_COUNTER.store(request_id, Ordering::Relaxed);
Err(report!(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"Failed to connect to API",
))
.into_dynamic())
}
fn file_operation(exists: bool) -> Result<(), Report> {
if exists {
Err(report!(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Access denied to file",
))
.into_dynamic())
} else {
Err(report!(std::io::Error::new(
std::io::ErrorKind::NotFound,
"File not found",
))
.into_dynamic())
}
}
fn main() {
println!("Example 1: AttachmentCollector - automatic request tracking\n");
Hooks::new()
.attachment_collector(RequestIdCollector)
.install()
.expect("failed to install hooks");
println!("Making first API request...");
match simulate_api_request(1001) {
Ok(_) => println!("Success"),
Err(error) => {
eprintln!("{error}\n");
println!("Notice: Request ID was automatically attached!\n");
}
}
println!("Making second API request...");
match simulate_api_request(1002) {
Ok(_) => println!("Success"),
Err(error) => {
eprintln!("{error}\n");
println!("Notice: Different request ID, automatically tracked\n");
}
}
println!("\nExample 2: ReportCreationHook - conditional retry hints\n");
Hooks::new().report_creation_hook(RetryHintHook).replace();
println!("Attempting transient network error...");
match simulate_api_request(1003) {
Ok(_) => println!("Success"),
Err(error) => {
eprintln!("{error}\n");
println!("Notice: Retry hint was added based on error type (ConnectionRefused)\n");
}
}
println!("Attempting permanent file error...");
match file_operation(false) {
Ok(()) => println!("Success"),
Err(error) => {
eprintln!("{error}\n");
println!("Notice: Different retry hint for permanent error (NotFound)");
}
}
}