use okerr::{Context, Result, derive::Error};
use std::io;
#[derive(Error, Debug)]
#[error("inner error: {0}")]
struct InnerError(String);
#[derive(Error, Debug)]
#[error("middle error")]
struct MiddleError {
#[source]
source: InnerError,
}
#[derive(Error, Debug)]
#[error("outer error")]
struct OuterError {
#[source]
source: MiddleError,
}
#[test]
fn nested_errors_with_source_attribute() {
let inner = InnerError("root cause".to_string());
let middle = MiddleError { source: inner };
let outer = OuterError { source: middle };
let err: &dyn std::error::Error = &outer;
assert!(err.source().is_some());
let middle_err = err.source().unwrap();
assert!(middle_err.to_string().contains("middle error"));
assert!(middle_err.source().is_some());
}
#[test]
fn error_chain_traversal() {
let inner = InnerError("deepest error".to_string());
let middle = MiddleError { source: inner };
let outer = OuterError { source: middle };
let err: &dyn std::error::Error = &outer;
let mut count = 1;
let mut current = err;
while let Some(source) = current.source() {
count += 1;
current = source;
}
assert_eq!(count, 3);
}
#[test]
fn nested_errors_in_result_chain() {
fn level3() -> std::result::Result<(), InnerError> {
Err(InnerError("level 3 failed".to_string()))
}
fn level2() -> std::result::Result<(), MiddleError> {
level3().map_err(|source| MiddleError { source })
}
fn level1() -> std::result::Result<(), OuterError> {
level2().map_err(|source| OuterError { source })
}
let result = level1();
assert!(result.is_err());
let err = result.unwrap_err();
let std_err: &dyn std::error::Error = &err;
assert!(std_err.source().is_some());
}
#[test]
fn anyhow_error_chain() {
fn inner_operation() -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::NotFound, "file.txt"))
}
fn middle_operation() -> Result<()> {
inner_operation().context("failed to read file")?;
Ok(())
}
fn outer_operation() -> Result<()> {
middle_operation().context("operation failed")?;
Ok(())
}
let result = outer_operation();
assert!(result.is_err());
let err = result.unwrap_err();
let chain: Vec<_> = err.chain().map(|e| e.to_string()).collect();
assert!(chain.len() >= 2);
}
#[test]
fn mixed_error_types_chain() {
#[derive(Error, Debug)]
enum AppError {
#[error("io error")]
Io(#[from] io::Error),
#[error("parse error")]
Parse(#[from] std::num::ParseIntError),
#[error("custom error: {0}")]
Custom(String),
}
fn operation() -> std::result::Result<i32, AppError> {
let io_err = io::Error::new(io::ErrorKind::NotFound, "config");
Err(io_err.into())
}
let _custom = AppError::Custom("test".to_string());
let result: Result<i32> = operation().map_err(|e| e.into());
assert!(result.is_err());
let err = result.unwrap_err();
let chain_count = err.chain().count();
assert!(chain_count >= 1);
}
#[test]
fn deep_nested_errors() {
#[derive(Error, Debug)]
#[error("level 1")]
struct Level1(#[source] Level2);
#[derive(Error, Debug)]
#[error("level 2")]
struct Level2(#[source] Level3);
#[derive(Error, Debug)]
#[error("level 3")]
struct Level3(#[source] Level4);
#[derive(Error, Debug)]
#[error("level 4")]
struct Level4(#[source] io::Error);
let l4 = Level4(io::Error::new(io::ErrorKind::Other, "deepest"));
let l3 = Level3(l4);
let l2 = Level2(l3);
let l1 = Level1(l2);
let err: &dyn std::error::Error = &l1;
let mut depth = 1;
let mut current = err;
while let Some(source) = current.source() {
depth += 1;
current = source;
}
assert_eq!(depth, 5);
}
#[test]
fn error_chain_with_context() {
fn step1() -> io::Result<String> {
Err(io::Error::new(io::ErrorKind::NotFound, "data.txt"))
}
fn step2() -> Result<String> {
step1().context("step 1 failed")?;
Ok("success".to_string())
}
fn step3() -> Result<String> {
step2().context("step 2 failed")?;
Ok("success".to_string())
}
let result = step3();
assert!(result.is_err());
let err = result.unwrap_err();
let messages: Vec<_> = err.chain().map(|e| e.to_string()).collect();
assert!(messages.len() >= 2);
}
#[test]
fn preserves_root_cause() {
let root_cause = InnerError("original problem".to_string());
let middle = MiddleError { source: root_cause };
let outer = OuterError { source: middle };
let err: &dyn std::error::Error = &outer;
let mut current = err;
while let Some(source) = current.source() {
current = source;
}
assert!(current.to_string().contains("original problem"));
}
#[test]
fn error_downcast_in_chain() {
#[derive(Error, Debug)]
#[error("wrapper")]
struct Wrapper {
#[source]
inner: InnerError,
}
let inner = InnerError("test".to_string());
let wrapper = Wrapper { inner };
let boxed: Box<dyn std::error::Error> = Box::new(wrapper);
assert!(boxed.is::<Wrapper>());
}
#[test]
fn multiple_branches_error_chain() {
#[derive(Error, Debug)]
enum ProcessError {
#[error("step A failed")]
StepA(#[source] io::Error),
#[error("step B failed")]
StepB(#[source] std::fmt::Error),
}
fn process_a() -> std::result::Result<(), ProcessError> {
let io_err = io::Error::new(io::ErrorKind::Other, "A");
Err(ProcessError::StepA(io_err))
}
let _step_b = ProcessError::StepB(std::fmt::Error);
let result = process_a();
assert!(result.is_err());
let err = result.unwrap_err();
match err {
ProcessError::StepA(ref source) => {
assert!(source.to_string().contains("A"));
}
_ => panic!("Wrong error variant"),
}
}
#[test]
fn chain_formatting() {
fn create_nested() -> Result<()> {
Err(io::Error::new(io::ErrorKind::NotFound, "file"))
.context("read failed")
.context("operation failed")
}
let result = create_nested();
assert!(result.is_err());
let err = result.unwrap_err();
let debug_output = format!("{:?}", err);
assert!(!debug_output.is_empty());
let display_output = format!("{}", err);
assert!(!display_output.is_empty());
}