use serde::de::Visitor; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; use std::marker::PhantomData; use std::ops::Deref; use std::str::FromStr; /// A type that tries to match either a star (*) or /// any other thing that implements `FromStr`. #[derive(Debug)] pub enum StarOr { Star, Other(T), } impl FromStr for StarOr { type Err = T::Err; fn from_str(s: &str) -> Result { if s.trim() == "*" { Ok(StarOr::Star) } else { T::from_str(s).map(StarOr::Other) } } } impl> Deref for StarOr { type Target = str; fn deref(&self) -> &Self::Target { match self { Self::Star => "*", Self::Other(t) => t.deref(), } } } impl> From> for String { fn from(s: StarOr) -> Self { match s { StarOr::Star => "*".to_string(), StarOr::Other(t) => t.into(), } } } impl PartialEq for StarOr { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Star, Self::Star) => true, (Self::Other(left), Self::Other(right)) if left.eq(right) => true, _ => false, } } } impl Eq for StarOr {} impl<'de, T, E> Deserialize<'de> for StarOr where T: FromStr, E: Display, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { /// Serde can't differentiate between `StarOr::Star` and `StarOr::Other` without a tag. /// Simply using `#[serde(untagged)]` + `#[serde(rename="*")]` will lead to attempting to /// deserialize everything as a `StarOr::Other`, including "*". /// [`#[serde(other)]`](https://serde.rs/variant-attrs.html#other) might have helped but is /// not supported on untagged enums. struct StarOrVisitor(PhantomData); impl<'de, T, FE> Visitor<'de> for StarOrVisitor where T: FromStr, FE: Display, { type Value = StarOr; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { formatter.write_str("a string") } fn visit_str(self, v: &str) -> Result where SE: serde::de::Error, { match v { "*" => Ok(StarOr::Star), v => { let other = FromStr::from_str(v).map_err(|e: T::Err| { SE::custom(format!("Invalid `other` value: {}", e)) })?; Ok(StarOr::Other(other)) } } } } deserializer.deserialize_str(StarOrVisitor(PhantomData)) } } impl Serialize for StarOr where T: Deref, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { StarOr::Star => serializer.serialize_str("*"), StarOr::Other(other) => serializer.serialize_str(other.deref()), } } } #[cfg(test)] mod tests { use super::*; use serde_json::{json, Value}; #[test] fn star_or_serde_roundtrip() { fn roundtrip(content: Value, expected: StarOr) { let deserialized: StarOr = serde_json::from_value(content.clone()).unwrap(); assert_eq!(deserialized, expected); assert_eq!(content, serde_json::to_value(deserialized).unwrap()); } roundtrip(json!("products"), StarOr::Other("products".to_string())); roundtrip(json!("*"), StarOr::Star); } }