#![cfg(feature = "std")]
use core::error::Error;
use snafu::{ResultExt, Snafu};
use suzunari_error::{Location, StackError, StackReport};
#[derive(Debug, Snafu)]
struct NestedError {
source: std::io::Error,
#[snafu(implicit)]
location: Location,
}
#[derive(Debug, Snafu)]
enum TestError {
Simple {
#[snafu(implicit)]
location: Location,
},
#[snafu(display("{}", message))]
External {
message: String,
source: Box<dyn Error + Send + Sync>,
#[snafu(implicit)]
location: Location,
},
Internal {
source: NestedError,
#[snafu(implicit)]
location: Location,
},
}
impl StackError for TestError {
fn location(&self) -> Location {
match self {
TestError::External { location, .. } => location,
TestError::Internal { location, .. } => location,
TestError::Simple { location, .. } => location,
}
}
fn type_name(&self) -> &'static str {
match self {
TestError::External { .. } => "TestError::External",
TestError::Internal { .. } => "TestError::Internal",
TestError::Simple { .. } => "TestError::Simple",
}
}
fn stack_source(&self) -> Option<&dyn StackError> {
match self {
TestError::External { .. } => None,
TestError::Internal { source, .. } => Some(source),
TestError::Simple { .. } => None,
}
}
}
impl StackError for NestedError {
fn location(&self) -> Location {
self.location
}
fn type_name(&self) -> &'static str {
"NestedError"
}
}
#[test]
fn test_stack_error_basics() {
let error = SimpleSnafu {}.build();
assert_eq!(error.location().file(), file!());
}
#[test]
fn test_chain_context() {
let error = SimpleSnafu {}.build();
let normalized_path = error.location().file().replace('\\', "/");
assert!(normalized_path.ends_with("stack_error_test.rs"));
}
#[test]
fn test_stack_source_resolver_specialization() {
use suzunari_error::__private::StackSourceResolver;
let nested: NestedError = std::fs::read("nonexistent")
.context(NestedSnafu)
.unwrap_err();
let resolver = StackSourceResolver(&nested);
assert!(
resolver.resolve().is_some(),
"StackError type should resolve to Some"
);
let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
let resolver = StackSourceResolver(&io_err);
assert!(
resolver.resolve().is_none(),
"non-StackError type should resolve to None"
);
}
fn function_c() -> Result<Vec<u8>, NestedError> {
std::fs::read("not exist").context(NestedSnafu)
}
fn function_b() -> Result<(), Box<dyn Error + Send + Sync>> {
function_c().context(InternalSnafu)?;
Ok(())
}
fn function_a() -> Result<(), TestError> {
function_b().context(ExternalSnafu { message: "Whoops" })?;
Ok(())
}
#[test]
fn test_error_propagation() {
let result = function_a();
assert!(result.is_err());
let error = result.unwrap_err();
let file = file!();
let report = format!("{:?}", StackReport::from(error));
assert!(report.contains(&format!("Error: TestError::External: Whoops, at {file}:")));
assert!(report.contains("Caused by"));
assert!(report.contains("1| Internal"));
assert!(report.contains("2| NestedError"));
assert!(report.contains("3| "));
}