#[macro_export]
macro_rules! custom_error {
(
$( #[$meta_attribute:meta] )*
$visibility:vis
$errtype:ident
$( < $(
$type_param:tt
),*
> )?
$(
$( #[$field_meta:meta] )*
$field:ident
$( { $(
$attr_name:ident
:
$attr_type:ty
),* } )?
=
$( @{ $($msg_fun:tt)* } )?
$($msg:expr)?
),*
$(,)*
) => {
$( #[$meta_attribute] )*
#[derive(Debug)]
$visibility enum $errtype $( < $($type_param),* > )* {
$(
$( #[$field_meta] )*
$field
$( { $( $attr_name : $attr_type ),* } )*
),*
}
$crate::add_type_bounds! {
( $($($type_param),*)* )
(std::fmt::Debug + std::fmt::Display)
{ impl <} {> std::error::Error
for $errtype $( < $($type_param),* > )*
{
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!{
( $errtype $(< $($type_param),* >)* )
$([
$field,
$($(
$attr_name,
$attr_name,
$attr_type
),*),*
])*
}
$crate::add_type_bounds! {
( $($($type_param),*)* )
(std::string::ToString)
{ impl <} {> std::fmt::Display
for $errtype $( < $($type_param),* > )*
{
fn fmt(&self, formatter: &mut std::fmt::Formatter)
-> std::fmt::Result
{
match self {$(
$errtype::$field $( { $( $attr_name ),* } )* => {
$(write!(formatter, "{}", ($($msg_fun)*) )?;)*
$crate::display_message!(formatter, $($($attr_name),*),* | $($msg)*);
Ok(())
}
),*}
}
}
}}
};
(
$( #[$meta_attribute:meta] )*
$visibility:vis
$errtype:ident
$( < $(
$type_param:tt
),*
> )?
{ $(
$( #[$field_meta:meta] )*
$field_name:ident
:
$field_type:ty
),* }
=
$( @{ $($msg_fun:tt)* } )?
$($msg:expr)?
$(,)*
) => {
$( #[$meta_attribute] )*
#[derive(Debug)]
$visibility struct $errtype $( < $($type_param),* > )* {
$(
$( #[$field_meta] )*
pub $field_name : $field_type
),*
}
$crate::add_type_bounds! {
( $($($type_param),*)* )
(std::fmt::Debug + std::fmt::Display)
{ impl <} {> std::error::Error
for $errtype $( < $($type_param),* > )*
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)>
{
#[allow(unused_variables, unreachable_code)]
match self {
$errtype { $( $field_name ),* } => {
$({
$crate::return_if_source!($field_name, $field_name)
});*
None
}
}
}
}
}}
$crate::impl_error_conversion_for_struct!{
$errtype $(< $($type_param),* >)*,
$( $field_name: $field_type ),*
}
$crate::add_type_bounds! {
( $($($type_param),*)* )
(std::string::ToString)
{ impl <} {> std::fmt::Display
for $errtype $( < $($type_param),* > )*
{
fn fmt(&self, formatter: &mut std::fmt::Formatter)
-> std::fmt::Result
{
$(
let $field_name = &self.$field_name;
);*
$(write!(formatter, "{}", ($($msg_fun)*) )?;)*
$crate::display_message!(formatter, $($field_name),* | $($msg)*);
Ok(())
}
}
}}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! return_if_source {
(source, $attr_name:ident) => {{
return Some(std::borrow::Borrow::borrow($attr_name));
}};
($_attr_name:ident, $_repeat:ident ) => {()};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_error_conversion {
( ( $($prefix:tt)* ) [ $($field_data:tt)* ] $($rest:tt)* ) => {
$crate::impl_error_conversion!{$($prefix)*, $($field_data)*}
$crate::impl_error_conversion!{ ($($prefix)*) $($rest)*}
};
( ( $($prefix:tt)* ) ) => {};
(
$errtype:ident $( < $($type_param:tt),* > )*,
$field:ident,
source,
$source:ident,
$source_type:ty
) => {
impl $( < $($type_param),* > )* From<$source_type>
for $errtype $( < $($type_param),* > )* {
fn from(source: $source_type) -> Self {
$errtype::$field { source }
}
}
};
(
$_errtype:ident $( < $($_errtype_type_param:tt),* > )*,
$_field:ident,
$(
$_:ident,
$_repeated:ident,
$_type:ty
),*
) => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_error_conversion_for_struct {
( ( $($prefix:tt)* ) ) => {};
(
$errtype:ident, $( < $($type_param:tt),* > )*
source: $source_type:ty
) => {
impl $( < $($type_param),* > )* From<$source_type>
for $errtype $( < $($type_param),* > )* {
fn from(source: $source_type) -> Self { $errtype { source } }
}
};
(
$_errtype:ident $( < $($_errtype_type_param:tt),* > )*,
$( $_field:ident: $_type:ty ),*
) => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! display_message {
($formatter:expr, $($attr:ident),* | $msg:expr) => {
write!(
$formatter,
concat!($msg $(, "{", stringify!($attr), ":.0}" )*)
$( , $attr = $attr.to_string() )*
)?;
};
($formatter:expr, $($attr:ident),* | ) => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! add_type_bounds {
(
( $typ:ident $(, $rest:tt)* )
( $($bounds:tt)* )
{ $($prefix:tt)* }
{ $($suffix:tt)* }
) => {
add_type_bounds!{
( $(, $rest)* )
( $($bounds)* )
{ $($prefix)* $typ : $($bounds)*}
{ $($suffix)* }
}
};
(
( $lifetime:tt $(, $rest:tt)* )
( $($bounds:tt)* )
{ $($prefix:tt)* }
{ $($suffix:tt)* }
) => {
add_type_bounds!{
( $(, $rest)* )
( $($bounds)* )
{ $($prefix)* $lifetime }
{ $($suffix)* }
}
};
(
( , $($rest:tt)* )
( $($bounds:tt)* )
{ $($prefix:tt)* }
{ $($suffix:tt)* }
) => {
add_type_bounds!{
( $($rest)* )
( $($bounds)* )
{ $($prefix)* , }
{ $($suffix)* }
}
};
(
( )
( $($bounds:tt)* )
{ $($prefix:tt)* }
{ $($suffix:tt)* }
) => {
$($prefix)* $($suffix)*
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use std::str::FromStr;
#[test]
fn single_error_case() {
custom_error!(MyError Bad="bad");
assert_eq!("bad", MyError::Bad.to_string());
}
#[test]
fn single_error_struct_case() {
custom_error!(MyError {} = "bad");
assert_eq!("bad", MyError {}.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_doc_comments_for_variants() {
custom_error! {MyError
Bad="bad",
Terrible="terrible"
}
assert!(MyError::Bad.source().is_none());
assert!(MyError::Terrible.source().is_none());
}
#[test]
fn enum_with_field_lifetime() {
custom_error!(MyError
Problem{description: &'static str} = "{description}"
);
assert_eq!("bad", MyError::Problem { description: "bad" }.to_string());
}
#[test]
fn struct_with_field_lifetime() {
custom_error!(MyError {description: &'static str} = "{}");
assert_eq!("bad", MyError { description: "bad" }.to_string());
}
#[test]
fn enum_with_derive() {
custom_error! {
#[derive(PartialEq, PartialOrd)]
#[derive(Clone)]
MyError A = "A", B{b:u8} = "B({b})", C = "C"
};
assert_eq!(MyError::A, MyError::A);
assert_eq!(MyError::B { b: 1 }, MyError::B { b: 1 });
assert_ne!(MyError::B { b: 0 }, MyError::B { b: 1 });
assert_ne!(MyError::A, MyError::B { b: 1 });
assert!(MyError::A < MyError::B { b: 1 });
assert!(MyError::B { b: 1 } < MyError::B { b: 2 });
assert!(MyError::B { b: 2 } < MyError::C);
assert_eq!(MyError::A.clone(), MyError::A);
}
#[test]
fn enum_with_pub_derive_and_doc_comment() {
custom_error! {
#[derive(PartialEq, PartialOrd)]
pub MyError A = "A"
};
assert_eq!(MyError::A, MyError::A);
}
#[test]
fn enum_with_box_dyn_source() {
custom_error! {pub MyError
Dynamic { source: Box<dyn Error> } = "dynamic",
}
let err = u8::from_str("x").unwrap_err();
assert_eq!(
err.to_string(),
MyError::Dynamic {
source: Box::new(err)
}.source().unwrap().to_string());
}
#[test]
fn struct_with_box_dyn_source() {
custom_error! {pub MyError
{ source: Box<dyn Error> } = "dynamic",
}
let err = u8::from_str("x").unwrap_err();
assert_eq!(
err.to_string(),
MyError {
source: Box::new(err)
}.source().unwrap().to_string());
}
#[test]
fn struct_with_public_field() {
mod inner {
custom_error! {pub MyError {x: &'static str} = "{}"}
}
assert_eq!("hello", inner::MyError{x: "hello"}.to_string());
}
#[test]
fn struct_with_field_documentation() {
custom_error! {
pub MyError {
x: &'static str
} = "{}"
}
assert_eq!("hello", MyError{x: "hello"}.to_string());
}
#[test]
fn struct_with_error_data() {
custom_error!(MyError { broken_things: u8 } = "{broken_things} things are broken");
assert_eq!(
"9 things are broken",
MyError { broken_things: 9 }.to_string()
);
}
#[test]
fn struct_with_derive() {
custom_error!(
#[derive(PartialEq, PartialOrd, Clone, Default)]
MyError { x: u8 } = ":("
);
assert_eq!(MyError { x: 9 }, MyError { x: 9 });
assert_eq!(MyError { x: 0 }.clone(), MyError { x: 0 });
assert_eq!(MyError::default(), MyError { x: 0 });
assert!(MyError { x: 0 } < MyError { x: 1 });
}
#[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 struct_with_multiple_error_data() {
custom_error!(
E {
a: u8,
b: u8,
c: u8
} = "{c} {b} {a}"
);
assert_eq!("3 2 1", E { a: 1, b: 2, c: 3 }.to_string());
}
#[test]
fn source() {
use std::{error::Error, io};
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 struct_source() {
use std::{error::Error, io};
custom_error!(E { source: io::Error } = "");
let source: io::Error = io::ErrorKind::InvalidData.into();
assert_eq!(
source.to_string(),
E { 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]
fn struct_from_source() {
use std::io;
custom_error!(E { 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::{error::Error, io};
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());
}
#[test]
#[allow(dead_code)]
fn struct_with_source_and_others() {
use std::{error::Error, io};
custom_error!(
MyError {
x: u8,
source: io::Error
} = "{x}"
);
fn source() -> io::Error {
io::ErrorKind::AlreadyExists.into()
};
let my_err = MyError {
x: 42,
source: source(),
};
assert_eq!("42", my_err.to_string());
assert_eq!(source().to_string(), my_err.source().unwrap().to_string());
}
#[test]
fn pub_error() {
mod my_mod {
custom_error! {pub MyError Case1="case1"}
}
assert_eq!("case1", my_mod::MyError::Case1.to_string());
}
#[test]
fn pub_error_struct() {
mod my_mod {
custom_error! {pub MyError{} = "case1"}
}
assert_eq!("case1", my_mod::MyError {}.to_string());
}
#[test]
fn pub_error_struct_fields() {
mod my_mod {
custom_error! {pub MyError{x:u8} = "x={x}"}
}
assert_eq!("x=9", my_mod::MyError { x: 9 }.to_string());
}
#[test]
fn generic_error() {
custom_error! {MyError<X,Y> E1{x:X,y:Y}="x={x} y={y}", E2="e2"}
assert_eq!("x=42 y=42", MyError::E1 { x: 42u8, y: 42u8 }.to_string());
assert_eq!("e2", MyError::E2::<u8, u8>.to_string());
}
#[test]
fn generic_error_struct() {
custom_error! {MyError<X,Y>{x:X,y:Y}="x={x} y={y}"}
assert_eq!("x=42 y=42", MyError { x: 42u8, y: 42u8 }.to_string());
}
#[test]
fn single_error_case_with_braces() {
custom_error! {MyError Bad="bad"}
assert_eq!("bad", MyError::Bad.to_string());
}
#[test]
fn single_error_struct_case_with_braces() {
custom_error! {MyError{} ="bad"}
assert_eq!("bad", MyError {}.to_string())
}
#[test]
fn trailing_comma() {
custom_error! {MyError1 A="a",}
custom_error! {MyError2 A="a", B="b",}
assert_eq!("a", MyError1::A.to_string());
assert_eq!("a", MyError2::A.to_string());
assert_eq!("b", MyError2::B.to_string());
}
#[test]
fn with_custom_formatting() {
custom_error! {MyError
Complex{a:u8, b:u8} = @{
if a+b == 0 {
"zero".to_string()
} else {
(a+b).to_string()
}
},
Simple = "simple"
}
assert_eq!("zero", MyError::Complex { a: 0, b: 0 }.to_string());
assert_eq!("3", MyError::Complex { a: 2, b: 1 }.to_string());
assert_eq!("simple", MyError::Simple.to_string());
}
#[test]
fn struct_with_custom_formatting() {
custom_error! {MyError{a:u8, b:u8} = @{
if a+b == 0 {
"zero".to_string()
} else {
(a+b).to_string()
}
}
}
assert_eq!("zero", MyError { a: 0, b: 0 }.to_string());
assert_eq!("3", MyError { a: 2, b: 1 }.to_string());
}
#[test]
fn custom_format_source() {
use std::io;
custom_error! {MyError
Io{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())}
}
assert_eq!(
"IO Error occurred: Interrupted",
MyError::Io {
source: io::ErrorKind::Interrupted.into()
}
.to_string()
)
}
#[test]
fn struct_custom_format_source() {
use std::io;
custom_error! {MyError{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())} }
assert_eq!(
"IO Error occurred: Interrupted",
MyError {
source: io::ErrorKind::Interrupted.into()
}
.to_string()
)
}
#[test]
fn lifetime_source_param() {
#[derive(Debug)]
struct SourceError<'my_lifetime> {
x: &'my_lifetime str,
}
impl<'a> std::fmt::Display for SourceError<'a> {
fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
Ok(())
}
}
impl<'a> std::error::Error for SourceError<'a> {}
custom_error! { MyError<'source_lifetime>
Sourced { lifetimed : SourceError<'source_lifetime> } = @{ lifetimed.x },
Other { source: std::fmt::Error } = "other error"
}
let sourced = MyError::Sourced {
lifetimed: SourceError {
x: "I am the source",
},
};
assert_eq!("I am the source", sourced.to_string());
let other_err: MyError = std::fmt::Error.into();
assert_eq!("other error", other_err.to_string());
}
#[test]
fn struct_lifetime_source_param() {
#[derive(Debug)]
struct SourceError<'my_lifetime> {
x: &'my_lifetime str,
}
impl<'a> std::fmt::Display for SourceError<'a> {
fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
Ok(())
}
}
impl<'a> std::error::Error for SourceError<'a> {}
custom_error! { MyError<'source_lifetime>{
lifetimed : SourceError<'source_lifetime>
} = @{ lifetimed.x },}
let sourced = MyError {
lifetimed: SourceError {
x: "I am the source",
},
};
assert_eq!("I am the source", sourced.to_string());
}
#[test]
fn lifetime_param_and_type_param() {
#[derive(Debug)]
struct MyType<'a, T> {
data: &'a str,
_y: T,
}
custom_error! { MyError<'a,T>
X { d: MyType<'a,T> } = @{ format!("error x: {}", d.data) },
Y { d: T } = "error y"
}
let err = MyError::X {
d: MyType {
data: "hello",
_y: 42i8,
},
};
assert_eq!("error x: hello", err.to_string());
let err_y = MyError::Y {
d: String::from("my string"),
};
assert_eq!("error y", err_y.to_string());
}
#[test]
fn struct_lifetime_param_and_type_param() {
#[derive(Debug)]
struct MyType<'a, T> {
data: &'a str,
_y: T,
}
custom_error! { MyError<'a,T> {
d: MyType<'a,T>
} = @{ format!("error x: {}", d.data) } }
let err = MyError {
d: MyType {
data: "hello",
_y: 42i8,
},
};
assert_eq!("error x: hello", err.to_string());
}
}