#[macro_export]
macro_rules! define_header_enum {
(
$(tests_mod: $tests_mod:ident,)?
error_type: $Err:ident $(=> $err_msg:literal)?,
$(#[$enum_meta:meta])*
$vis:vis enum $Name:ident {
$(
$(#[$var_meta:meta])*
$variant:ident => $wire:literal
),+ $(,)?
}
) => {
$(
#[doc = concat!("Error for an unrecognized value; displays as `", $err_msg, ": <input>`.")]
#[derive(Debug, Clone, PartialEq, Eq)]
$vis struct $Err(pub String);
impl std::fmt::Display for $Err {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, concat!($err_msg, ": {}"), self.0)
}
}
impl std::error::Error for $Err {}
)?
$(#[$enum_meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[allow(missing_docs)]
$vis enum $Name {
$(
$(#[$var_meta])*
$variant,
)+
}
impl $Name {
#[allow(unused_doc_comments)]
pub const ALL: &'static [Self] = &[
$(
$(#[$var_meta])*
$Name::$variant,
)+
];
#[allow(unused_doc_comments)]
pub fn as_str(&self) -> &'static str {
match self {
$(
$(#[$var_meta])*
$Name::$variant => $wire,
)+
}
}
}
impl std::fmt::Display for $Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for $Name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::str::FromStr for $Name {
type Err = $Err;
#[allow(unused_doc_comments)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
$(
$(#[$var_meta])*
if s.eq_ignore_ascii_case($wire) {
return Ok($Name::$variant);
}
)+
Err($Err(s.to_string()))
}
}
$(
#[cfg(test)]
mod $tests_mod {
use super::{$Err, $Name};
#[test]
fn round_trip() {
for v in $Name::ALL {
assert_eq!(v.as_str().parse::<$Name>(), Ok(*v));
}
}
#[test]
fn case_insensitive() {
for v in $Name::ALL {
assert_eq!(v.as_str().to_lowercase().parse::<$Name>(), Ok(*v));
assert_eq!(v.as_str().to_uppercase().parse::<$Name>(), Ok(*v));
}
}
#[test]
fn display_matches_as_str() {
for v in $Name::ALL {
assert_eq!(v.to_string(), v.as_str());
}
}
#[test]
fn unknown_input_err() {
let input = "\u{0}no-such-value\u{0}";
assert_eq!(input.parse::<$Name>(), Err($Err(input.to_string())));
}
}
)?
};
}
macro_rules! impl_from_str_via_parse {
($Type:ty, $Err:ty) => {
impl std::str::FromStr for $Type {
type Err = $Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
};
}
#[cfg(test)]
mod tests {
define_header_enum! {
tests_mod: test_enum_generated,
error_type: ParseTestEnumError => "unknown test value",
pub(crate) enum TestEnum {
Foo => "Foo-Wire",
Bar => "Bar-Wire",
#[cfg(feature = "draft")]
Draft => "Draft-Wire",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParseOldEnumError(pub String);
impl std::fmt::Display for ParseOldEnumError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown old value: {}", self.0)
}
}
impl std::error::Error for ParseOldEnumError {}
define_header_enum! {
error_type: ParseOldEnumError,
pub(crate) enum OldEnum {
One => "Old-Wire",
}
}
#[test]
fn generated_error_display() {
let e = ParseTestEnumError("nope".to_string());
assert_eq!(e.to_string(), "unknown test value: nope");
}
#[test]
fn all_const_respects_cfg() {
#[cfg(not(feature = "draft"))]
assert_eq!(TestEnum::ALL, &[TestEnum::Foo, TestEnum::Bar]);
#[cfg(feature = "draft")]
assert_eq!(
TestEnum::ALL,
&[TestEnum::Foo, TestEnum::Bar, TestEnum::Draft]
);
}
#[test]
fn old_form_generates_all() {
assert_eq!(OldEnum::ALL, &[OldEnum::One]);
assert_eq!("old-wire".parse::<OldEnum>(), Ok(OldEnum::One));
}
}