use rootcause::{ReportConversion, markers, prelude::*};
use thiserror::Error;
#[derive(Error, Debug)]
#[expect(dead_code, reason = "example code")]
enum DatabaseError {
#[error("Connection timeout after {seconds}s")]
ConnectionTimeout { seconds: u64 },
#[error("Query timeout after {seconds}s")]
QueryTimeout { seconds: u64 },
}
#[derive(Error, Debug)]
#[expect(dead_code, reason = "example code")]
enum ConfigError {
#[error("Invalid format in {file}")]
InvalidFormat { file: String },
}
#[derive(Error, Debug)]
enum AppError1 {
#[error("Database error")]
Database(#[from] DatabaseError),
}
fn query_plain(_id: u32) -> Result<String, DatabaseError> {
Err(DatabaseError::ConnectionTimeout { seconds: 30 })
}
fn process_type_nested(user_id: u32) -> Result<String, Report<AppError1>> {
let data = query_plain(user_id).map_err(AppError1::from)?;
Ok(data)
}
fn handle_typed_error(user_id: u32) -> Result<String, Report<AppError1>> {
match process_type_nested(user_id) {
Ok(data) => Ok(data),
Err(report) => {
match report.current_context() {
AppError1::Database(DatabaseError::ConnectionTimeout { .. }) => {
println!(" → Detected connection timeout, could retry here");
Err(report)
}
AppError1::Database(DatabaseError::QueryTimeout { .. }) => {
println!(" → Query timeout, could adjust query parameters");
Err(report)
}
}
}
}
}
#[derive(Error, Debug)]
enum AppError2 {
#[error("Database error")]
Database(#[from] DatabaseError),
}
fn query_report(_id: u32) -> Result<String, Report<DatabaseError>> {
Err(report!(DatabaseError::ConnectionTimeout { seconds: 30 }))
}
fn process_early_report(user_id: u32) -> Result<String, Report<AppError2>> {
let data = query_report(user_id).context_transform(AppError2::Database)?;
Ok(data)
}
fn process_early_report_multiple_locations(user_id: u32) -> Result<String, Report<AppError2>> {
let data = query_report(user_id).context_transform_nested(AppError2::Database)?;
Ok(data)
}
impl<T> ReportConversion<DatabaseError, markers::Mutable, T> for AppError2
where
AppError2: markers::ObjectMarkerFor<T>,
{
fn convert_report(
report: Report<DatabaseError, markers::Mutable, T>,
) -> Report<Self, markers::Mutable, T> {
report.context_transform(AppError2::Database)
}
}
fn process_with_conversion(user_id: u32) -> Result<String, Report<AppError2>> {
let data = query_report(user_id).context_to::<AppError2>()?;
Ok(data)
}
#[derive(Error, Debug)]
enum AppError3 {
#[error("Database connection issue")]
DatabaseConnection,
#[error("Database operation failed")]
DatabaseOther,
#[error("System error")]
System,
}
impl<T> ReportConversion<DatabaseError, markers::Mutable, T> for AppError3
where
AppError3: markers::ObjectMarkerFor<T>,
{
fn convert_report(
report: Report<DatabaseError, markers::Mutable, T>,
) -> Report<Self, markers::Mutable, T> {
match report.current_context() {
DatabaseError::ConnectionTimeout { .. } => report.context(Self::DatabaseConnection),
DatabaseError::QueryTimeout { .. } => report.context(Self::DatabaseOther),
}
}
}
impl<T> ReportConversion<ConfigError, markers::Mutable, T> for AppError3
where
AppError3: markers::ObjectMarkerFor<T>,
{
fn convert_report(
report: Report<ConfigError, markers::Mutable, T>,
) -> Report<Self, markers::Mutable, T> {
report.context(Self::System)
}
}
fn main() {
println!("\n## Pattern 1: Type-nesting with #[from]\n");
println!("Only one location captured and it's not at the source:");
if let Err(report) = process_type_nested(123) {
eprintln!("{report}\n");
}
println!("Pattern matching on typed reports:");
let _ = handle_typed_error(123);
println!();
println!("\n## Pattern 2: Early Report creation\n");
println!("Locations captured close to the error source:");
if let Err(report) = process_early_report(123) {
eprintln!("{report}\n");
}
println!("Multiple locations captured using context_transform_nested:");
if let Err(report) = process_early_report_multiple_locations(123) {
eprintln!("{report}\n");
}
println!("Using ReportConversion trait:");
if let Err(report) = process_with_conversion(123) {
eprintln!("{report}\n");
}
println!("\n## Pattern 3: Flexible categorization\n");
println!("The data type of AppError3 does not have to be 1-to-1 with the underlying errors:");
if let Err(report) = query_report(123).context_to::<AppError3>() {
eprintln!("{report}\n");
}
}