pub trait Labels: Sized + Copy + Eq + 'static {
const ALL: &'static [(Self, &'static str, Option<&'static str>)];
fn label(self) -> &'static str {
Self::ALL
.iter()
.find(|(variant, _, _)| *variant == self)
.map(|(_, label, _)| *label)
.expect("Labels::ALL must list every variant")
}
fn from_label(label: &str) -> Option<Self> {
Self::ALL
.iter()
.find(|(_, candidate, _)| *candidate == label)
.map(|(variant, _, _)| *variant)
}
}
#[macro_export]
macro_rules! labels {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident = $label:literal $(: $description:literal)?
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
$vis enum $name {
$(
$(#[$variant_meta])*
$variant,
)+
}
impl $crate::Labels for $name {
const ALL: &'static [(Self, &'static str, ::core::option::Option<&'static str>)] = &[
$(
($name::$variant, $label, $crate::__label_description!($($description)?)),
)+
];
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __label_description {
() => {
::core::option::Option::None
};
($description:literal) => {
::core::option::Option::Some($description)
};
}
#[cfg(test)]
mod tests {
use super::*;
crate::labels! {
enum Tone {
Calm = "calm",
Angry = "angry": "Strong language",
}
}
#[test]
fn labels_round_trip() {
assert_eq!(Tone::Calm.label(), "calm");
assert_eq!(Tone::from_label("angry"), Some(Tone::Angry));
assert_eq!(Tone::from_label("Calm"), None);
assert_eq!(Tone::ALL[1].2, Some("Strong language"));
assert_eq!(Tone::ALL[0].2, None);
}
}