#[macro_export]
macro_rules! impl_string_enum {
($ty:ident, $expecting:literal, $( $s:literal => $variant:ident ),+ $(,)?) => {
impl $ty {
pub fn as_str(&self) -> &str {
match self {
$( $ty::$variant => $s, )+
$ty::Other(v) => v.as_str(),
}
}
}
impl ::serde::Serialize for $ty {
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.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($ty::from(v))
}
}
d.deserialize_str(Visitor)
}
}
impl ::std::convert::From<&str> for $ty {
fn from(v: &str) -> Self {
match v {
$( $s => $ty::$variant, )+
_ => $ty::Other(v.to_owned()),
}
}
}
impl ::std::convert::From<::std::string::String> for $ty {
fn from(v: ::std::string::String) -> Self {
match v.as_str() {
$( $s => $ty::$variant, )+
_ => $ty::Other(v),
}
}
}
impl ::std::fmt::Display for $ty {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(self.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 forged_other_is_unequal_but_round_trips_to_variant() {
let forged = TestKind::Other("foo".to_owned());
assert_ne!(forged, TestKind::Foo);
let wire = serde_json::to_string(&forged).unwrap();
assert_eq!(wire, r#""foo""#);
let back: TestKind = serde_json::from_str(&wire).unwrap();
assert_eq!(back, TestKind::Foo);
assert_eq!(TestKind::from("foo"), TestKind::Foo);
}
#[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 as_str_yields_wire_string() {
assert_eq!(TestKind::Foo.as_str(), "foo");
assert_eq!(TestKind::Bar.as_str(), "bar");
assert_eq!(TestKind::Other("custom".to_owned()).as_str(), "custom");
}
#[test]
fn from_str_matches_deserialize() {
assert_eq!(TestKind::from("foo"), TestKind::Foo);
assert_eq!(TestKind::from(String::from("bar")), TestKind::Bar);
assert_eq!(
TestKind::from("vendor-extension-value"),
TestKind::Other("vendor-extension-value".to_owned())
);
assert_eq!(
TestKind::from(String::from("vendor-extension-value")),
serde_json::from_str::<TestKind>(r#""vendor-extension-value""#).unwrap()
);
}
#[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());
}
}