use std::error::Error;
pub trait ErrorBoundary {
type Inner: Error + Send + Sync;
type Outer: Error + Send + Sync;
fn convert(inner: Self::Inner) -> Self::Outer;
}
#[macro_export]
macro_rules! error_boundary {
($inner:ty => $outer:ty, |$err:ident| $body:expr) => {
impl ::std::convert::From<$inner> for $outer {
fn from($err: $inner) -> $outer {
$body
}
}
};
}
#[cfg(test)]
mod tests {
use std::io;
#[derive(Debug, thiserror::Error, PartialEq)]
enum TestError {
#[error("IO: {0}")]
Io(String),
#[error("Parse: {0}")]
Parse(String),
#[error("Unknown: {0}")]
Unknown(String),
}
error_boundary!(io::Error => TestError, |e| {
TestError::Io(e.to_string())
});
#[test]
fn test_error_boundary_direct_conversion() {
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
let test_error: TestError = io_error.into();
match test_error {
TestError::Io(msg) => {
assert!(msg.contains("file not found"));
}
_ => panic!("Expected Io variant"),
}
}
#[test]
fn test_error_boundary_with_question_mark() {
fn do_io() -> Result<String, TestError> {
let content = std::fs::read_to_string("/nonexistent/path/that/does/not/exist")?;
Ok(content)
}
let result = do_io();
assert!(result.is_err());
match result.unwrap_err() {
TestError::Io(msg) => {
assert!(msg.contains("No such file") || msg.contains("cannot find"));
}
_ => panic!("Expected Io variant"),
}
}
#[test]
#[allow(non_local_definitions)]
fn test_multiple_error_boundaries() {
error_boundary!(std::num::ParseIntError => TestError, |e| {
TestError::Parse(e.to_string())
});
fn parse_number(s: &str) -> Result<i32, TestError> {
let num = s.parse::<i32>()?; Ok(num)
}
let result = parse_number("not_a_number");
assert!(result.is_err());
match result.unwrap_err() {
TestError::Parse(msg) => {
assert!(msg.contains("invalid digit"));
}
_ => panic!("Expected Parse variant"),
}
}
#[test]
#[allow(non_local_definitions)]
fn test_chained_error_conversions() {
error_boundary!(std::fmt::Error => TestError, |_e| {
TestError::Unknown("format error".to_string())
});
fn complex_operation() -> Result<(), TestError> {
let _content = std::fs::read_to_string("/nonexistent")?;
Ok(())
}
let result = complex_operation();
assert!(result.is_err());
}
#[test]
fn test_error_context_preservation() {
let io_error = io::Error::new(
io::ErrorKind::PermissionDenied,
"access denied to /etc/shadow",
);
let test_error: TestError = io_error.into();
match test_error {
TestError::Io(msg) => {
assert!(msg.contains("access denied"));
assert!(msg.contains("/etc/shadow"));
}
_ => panic!("Expected Io variant"),
}
}
}