#[macro_export]
macro_rules! custom_error {
(
$errtype:ident
$(
$field:ident
$( {
$( $attr_name:ident : $attr_type:ty ),*
} )*
=
$msg:expr
),*
) => {
#[derive(Debug)]
enum $errtype {
$(
$field
$( { $( $attr_name : $attr_type ),* } )*
),*
}
impl std::error::Error for $errtype {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)>
{
#[allow(unused_variables, unreachable_code)]
match self {$(
$errtype::$field $( { $( $attr_name ),* } )* => {
$( $( $crate::return_if_source!($attr_name, $attr_name) );* )*;
None
}
),*}
}
}
$(
$( $crate::impl_error_conversion!{$($attr_name, $attr_name, $attr_type,)* $errtype, $field} )*
)*
impl std::fmt::Display for $errtype {
fn fmt(&self, formatter: &mut std::fmt::Formatter)
-> std::fmt::Result
{
match self {$(
$errtype::$field $( { $( $attr_name ),* } )* => {
write!(
formatter,
concat!($msg $( $( , "{", stringify!($attr_name), ":.0}" )* )*)
$( $( , $attr_name = $attr_name.to_string() )* )*
)
}
),*}
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! return_if_source {
(source, $attr_name:ident) => { {return Some($attr_name)} };
($($_:tt)*) => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_error_conversion {
(source, $source:ident, $error_type:ty, $errtype:ident, $field:ident) => {
impl From<$error_type> for $errtype {
fn from(source: $error_type) -> Self {
$errtype::$field { source }
}
}
};
($($_:tt)*) => {};
}
#[cfg(test)]
mod tests {
#[test]
fn single_error_case() {
custom_error!(MyError Bad="bad");
assert_eq!("bad", MyError::Bad.to_string())
}
#[test]
#[allow(dead_code)]
fn three_error_cases() {
custom_error!(MyError NotPerfect=":/", Bad="bad", Awfull="arghhh");
assert_eq!("arghhh", MyError::Awfull.to_string())
}
#[test]
fn with_error_data() {
custom_error!(MyError
Bad = "bad",
Catastrophic{broken_things:u8} = "{broken_things} things are broken"
);
assert_eq!("bad", MyError::Bad.to_string());
assert_eq!(
"9 things are broken",
MyError::Catastrophic { broken_things: 9 }.to_string()
);
}
#[test]
fn with_multiple_error_data() {
custom_error!(E X{a:u8, b:u8, c:u8} = "{c} {b} {a}");
assert_eq!("3 2 1", E::X { a: 1, b: 2, c: 3 }.to_string());
}
#[test]
fn source() {
use std::{io, error::Error};
custom_error!(E A{source: io::Error}="");
let source: io::Error = io::ErrorKind::InvalidData.into();
assert_eq!(
source.to_string(),
E::A { source }.source().unwrap().to_string()
);
}
#[test]
fn from_source() {
use std::io;
custom_error!(E A{source: io::Error}="bella vita");
let source = io::Error::from(io::ErrorKind::InvalidData);
assert_eq!("bella vita", E::from(source).to_string());
}
#[test]
#[allow(dead_code)]
fn with_source_and_others() {
use std::{io, error::Error};
custom_error!(MyError Zero="", One{x:u8}="", Two{x:u8, source:io::Error}="{x}");
fn source() -> io::Error { io::ErrorKind::AlreadyExists.into() };
let my_err = MyError::Two { x: 42, source: source() };
assert_eq!("42", my_err.to_string());
assert_eq!(source().to_string(), my_err.source().unwrap().to_string());
}
}