#[macro_export]
macro_rules! impl_string_enum {
($ty:ident, $expecting:literal, $( $s:literal => $variant:ident ),+ $(,)?) => {
impl ::serde::Serialize for $ty {
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(match self {
$( $ty::$variant => $s, )+
$ty::Other(v) => v.as_str(),
})
}
}
impl<'de> ::serde::Deserialize<'de> for $ty {
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct Visitor;
impl ::serde::de::Visitor<'_> for Visitor {
type Value = $ty;
fn expecting(
&self,
f: &mut ::std::fmt::Formatter<'_>,
) -> ::std::fmt::Result {
write!(f, $expecting)
}
fn visit_str<E: ::serde::de::Error>(self, v: &str) -> Result<$ty, E> {
Ok(match v {
$( $s => $ty::$variant, )+
_ => $ty::Other(v.to_owned()),
})
}
}
d.deserialize_str(Visitor)
}
}
impl ::std::fmt::Display for $ty {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(match self {
$( $ty::$variant => $s, )+
$ty::Other(v) => v.as_str(),
})
}
}
};
}
#[cfg(test)]
mod tests {
#[derive(Debug, PartialEq)]
enum TestKind {
Foo,
Bar,
Other(String),
}
impl_string_enum!(
TestKind,
"a test kind string",
"foo" => Foo,
"bar" => Bar,
);
#[test]
fn known_variant_serializes_to_wire_string() {
assert_eq!(serde_json::to_string(&TestKind::Foo).unwrap(), r#""foo""#);
assert_eq!(serde_json::to_string(&TestKind::Bar).unwrap(), r#""bar""#);
}
#[test]
fn known_wire_string_deserializes_to_variant() {
let foo: TestKind = serde_json::from_str(r#""foo""#).unwrap();
let bar: TestKind = serde_json::from_str(r#""bar""#).unwrap();
assert_eq!(foo, TestKind::Foo);
assert_eq!(bar, TestKind::Bar);
}
#[test]
fn unknown_wire_string_round_trips_via_other() {
let original = r#""vendor-extension-value""#;
let parsed: TestKind = serde_json::from_str(original).unwrap();
assert_eq!(parsed, TestKind::Other("vendor-extension-value".to_owned()));
let reserialized = serde_json::to_string(&parsed).unwrap();
assert_eq!(reserialized, original);
}
#[test]
fn display_matches_serialize_form() {
assert_eq!(format!("{}", TestKind::Foo), "foo");
assert_eq!(format!("{}", TestKind::Bar), "bar");
assert_eq!(
format!("{}", TestKind::Other("custom".to_owned())),
"custom",
);
}
#[test]
fn empty_string_round_trips_as_other() {
let parsed: TestKind = serde_json::from_str(r#""""#).unwrap();
assert_eq!(parsed, TestKind::Other(String::new()));
assert_eq!(serde_json::to_string(&parsed).unwrap(), r#""""#);
}
#[test]
fn deserialize_rejects_non_string_json() {
assert!(serde_json::from_str::<TestKind>("42").is_err());
assert!(serde_json::from_str::<TestKind>("true").is_err());
assert!(serde_json::from_str::<TestKind>("null").is_err());
assert!(serde_json::from_str::<TestKind>("{}").is_err());
}
}