#[macro_export]
macro_rules! define_errors {
(
$(#[$enum_meta:meta])*
$vis:vis enum $name:ident {
$(
#[error($msg:literal $(, level = $level:ident)? $(, target = $target:literal)? $(, source)?)]
$variant:ident $({
$(
$(#[$field_meta:meta])*
$field_name:ident: $field_type:ty
),* $(,)?
})?,
)*
}
) => {
#[derive(thiserror::Error, Debug)]
$(#[$enum_meta])*
$vis enum $name {
$(
#[error($msg)]
$variant $({
$(
$(#[$field_meta])*
$field_name: $field_type
),*
})?,
)*
}
impl $name {
pub fn log(&self) {
match self {
$(
Self::$variant { .. } => {
let code = self.code();
let message = self.to_string();
define_errors!(@log_thiserror $($level)? $($target)? ; code, message);
},
)*
}
}
pub fn code(&self) -> &'static str {
match self {
$(
Self::$variant { .. } => stringify!($variant),
)*
}
}
pub fn error_info(&self) -> (&'static str, &'static str, &'static str) {
match self {
$(
Self::$variant { .. } => {
let code = stringify!($variant);
define_errors!(@extract_thiserror_info $($level)? $($target)? ; code)
},
)*
}
}
}
};
(
$first_name:ident {
$($first_tokens:tt)*
}
$($rest_name:ident {
$($rest_tokens:tt)*
})+
) => {
define_errors! {
$first_name {
$($first_tokens)*
}
}
define_errors! {
$($rest_name {
$($rest_tokens)*
})+
}
};
(
$name:ident {
$($tokens:tt)*
}
) => {
define_errors!(@collect
name: $name,
variants: [],
tokens: [$($tokens)*]
);
};
(@collect
name: $name:ident,
variants: [$($variants:tt)*],
tokens: [
$variant:ident { $($field_name:ident : $field_type:ty),* $(,)? } : $msg:literal $([$($attr:tt)*])?
$(, $($rest:tt)*)?
]
) => {
define_errors!(@collect
name: $name,
variants: [$($variants)*
($variant, $msg, ($($field_name : $field_type),*), $([$($attr)*])?)
],
tokens: [$($($rest)*)?]
);
};
(@collect
name: $name:ident,
variants: [$($variants:tt)*],
tokens: []
) => {
define_errors!(@build $name; $($variants)*);
};
(@build $name:ident; $(($variant:ident, $msg:literal, (), $([$($attr:tt)*])?))*) => {
#[derive(thiserror::Error, Debug)]
pub enum $name {
$(
#[error($msg)]
$variant,
)*
}
impl $name {
pub fn log(&self) {
match self {
$(
Self::$variant => {
let code = self.code();
let message = self.to_string();
define_errors!(@log_simple $([$($attr)*])? ; code, message);
},
)*
}
}
pub fn code(&self) -> &'static str {
match self {
$(
Self::$variant => stringify!($variant),
)*
}
}
pub fn error_info(&self) -> (&'static str, &'static str, &'static str) {
match self {
$(
Self::$variant => {
let code = stringify!($variant);
define_errors!(@extract_info $([$($attr)*])? ; code)
},
)*
}
}
}
};
(@build $name:ident; $(($variant:ident, $msg:literal, ($($field_name:ident : $field_type:ty),+), $([$($attr:tt)*])?))*) => {
#[derive(thiserror::Error, Debug)]
pub enum $name {
$(
#[error($msg)]
$variant {
$($field_name : $field_type),+
},
)*
}
impl $name {
pub fn log(&self) {
match self {
$(
Self::$variant { .. } => {
let code = self.code();
let message = self.to_string();
define_errors!(@log_simple $([$($attr)*])? ; code, message);
},
)*
}
}
pub fn code(&self) -> &'static str {
match self {
$(
Self::$variant { .. } => stringify!($variant),
)*
}
}
pub fn error_info(&self) -> (&'static str, &'static str, &'static str) {
match self {
$(
Self::$variant { .. } => {
let code = stringify!($variant);
define_errors!(@extract_info $([$($attr)*])? ; code)
},
)*
}
}
}
};
(@build $name:ident; $(($variant:ident, $msg:literal, ($($field_name:ident : $field_type:ty),*), $([$($attr:tt)*])?))*) => {
define_errors!(@separate_mixed $name;
unit_variants: [];
struct_variants: [];
remaining: [$(($variant, $msg, ($($field_name : $field_type),*), $([$($attr)*])?))*]
);
};
(@separate_mixed $name:ident;
unit_variants: [$($unit_processed:tt)*];
struct_variants: [$($struct_processed:tt)*];
remaining: [($variant:ident, $msg:literal, (), $([$($attr:tt)*])?) $($rest:tt)*]
) => {
define_errors!(@separate_mixed $name;
unit_variants: [$($unit_processed)* ($variant, $msg, $([$($attr)*])?)];
struct_variants: [$($struct_processed)*];
remaining: [$($rest)*]
);
};
(@separate_mixed $name:ident;
unit_variants: [$($unit_processed:tt)*];
struct_variants: [$($struct_processed:tt)*];
remaining: [($variant:ident, $msg:literal, ($($field_name:ident : $field_type:ty),+), $([$($attr:tt)*])?) $($rest:tt)*]
) => {
define_errors!(@separate_mixed $name;
unit_variants: [$($unit_processed)*];
struct_variants: [$($struct_processed)* ($variant, $msg, ($($field_name : $field_type),+), $([$($attr)*])?)];
remaining: [$($rest)*]
);
};
(@separate_mixed $name:ident;
unit_variants: [$(($unit_variant:ident, $unit_msg:literal, $([$($unit_attr:tt)*])?))*];
struct_variants: [$(($struct_variant:ident, $struct_msg:literal, ($($struct_field_name:ident : $struct_field_type:ty),+), $([$($struct_attr:tt)*])?))*];
remaining: []
) => {
#[derive(thiserror::Error, Debug)]
pub enum $name {
$(
#[error($unit_msg)]
$unit_variant,
)*
$(
#[error($struct_msg)]
$struct_variant {
$($struct_field_name : $struct_field_type),+
},
)*
}
impl $name {
pub fn log(&self) {
match self {
$(
Self::$unit_variant => {
let code = self.code();
let message = self.to_string();
define_errors!(@log_simple $([$($unit_attr)*])? ; code, message);
},
)*
$(
Self::$struct_variant { .. } => {
let code = self.code();
let message = self.to_string();
define_errors!(@log_simple $([$($struct_attr)*])? ; code, message);
},
)*
}
}
pub fn code(&self) -> &'static str {
match self {
$(
Self::$unit_variant => stringify!($unit_variant),
)*
$(
Self::$struct_variant { .. } => stringify!($struct_variant),
)*
}
}
pub fn error_info(&self) -> (&'static str, &'static str, &'static str) {
match self {
$(
Self::$unit_variant => {
let code = stringify!($unit_variant);
define_errors!(@extract_info $([$($unit_attr)*])? ; code)
},
)*
$(
Self::$struct_variant { .. } => {
let code = stringify!($struct_variant);
define_errors!(@extract_info $([$($struct_attr)*])? ; code)
},
)*
}
}
}
};
(@log_simple [$($attr:tt)*] ; $code:expr, $message:expr) => {
define_errors!(@log_with_attrs $($attr)* ; $code, $message);
};
(@log_simple ; $code:expr, $message:expr) => {
$crate::error!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs level = error, target = $target:literal ; $code:expr, $message:expr) => {
$crate::error!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs level = warn, target = $target:literal ; $code:expr, $message:expr) => {
$crate::warn!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs level = info, target = $target:literal ; $code:expr, $message:expr) => {
$crate::info!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs level = debug, target = $target:literal ; $code:expr, $message:expr) => {
$crate::debug!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs level = trace, target = $target:literal ; $code:expr, $message:expr) => {
$crate::trace!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs level = error ; $code:expr, $message:expr) => {
$crate::error!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs level = warn ; $code:expr, $message:expr) => {
$crate::warn!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs level = info ; $code:expr, $message:expr) => {
$crate::info!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs level = debug ; $code:expr, $message:expr) => {
$crate::debug!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs level = trace ; $code:expr, $message:expr) => {
$crate::trace!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_with_attrs target = $target:literal ; $code:expr, $message:expr) => {
$crate::error!(target: $target, "[{}] {}", $code, $message);
};
(@log_with_attrs ; $code:expr, $message:expr) => {
$crate::error!(target: module_path!(), "[{}] {}", $code, $message);
};
(@log_thiserror $level:ident $target:literal ; $code:expr, $message:expr) => {
define_errors!(@log_with_attrs level = $level, target = $target ; $code, $message);
};
(@log_thiserror $level:ident ; $code:expr, $message:expr) => {
define_errors!(@log_with_attrs level = $level ; $code, $message);
};
(@log_thiserror $target:literal ; $code:expr, $message:expr) => {
define_errors!(@log_with_attrs target = $target ; $code, $message);
};
(@log_thiserror ; $code:expr, $message:expr) => {
define_errors!(@log_with_attrs ; $code, $message);
};
(@extract_thiserror_info $level:ident $target:literal ; $code:expr) => {
($code, stringify!($level), $target)
};
(@extract_thiserror_info $level:ident ; $code:expr) => {
($code, stringify!($level), module_path!())
};
(@extract_thiserror_info $target:literal ; $code:expr) => {
($code, "error", $target)
};
(@extract_thiserror_info ; $code:expr) => {
($code, "error", module_path!())
};
(@extract_info [level = $level:ident, target = $target:literal] ; $code:expr) => {
($code, stringify!($level), $target)
};
(@extract_info [target = $target:literal, level = $level:ident] ; $code:expr) => {
($code, stringify!($level), $target)
};
(@extract_info [level = $level:ident] ; $code:expr) => {
($code, stringify!($level), module_path!())
};
(@extract_info [target = $target:literal] ; $code:expr) => {
($code, "error", $target)
};
(@extract_info ; $code:expr) => {
($code, "error", module_path!())
};
}