use serde::{de::Visitor, Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Id {
Number(u64),
String(String),
None,
}
impl From<u64> for Id {
fn from(value: u64) -> Self {
Self::Number(value)
}
}
impl From<String> for Id {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Number(n) => write!(f, "{n}"),
Self::String(s) => f.write_str(s),
Self::None => f.write_str("null"),
}
}
}
impl Serialize for Id {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Number(n) => serializer.serialize_u64(*n),
Self::String(s) => serializer.serialize_str(s),
Self::None => serializer.serialize_none(),
}
}
}
impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct IdVisitor;
impl Visitor<'_> for IdVisitor {
type Value = Id;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "a string, a number, or null")
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.to_owned().into())
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Id::None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Id::None)
}
}
deserializer.deserialize_any(IdVisitor)
}
}
impl PartialOrd for Id {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Id {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Self::Number(a), Self::Number(b)) => a.cmp(b),
(Self::Number(_), _) => std::cmp::Ordering::Less,
(Self::String(_), Self::Number(_)) => std::cmp::Ordering::Greater,
(Self::String(a), Self::String(b)) => a.cmp(b),
(Self::String(_), Self::None) => std::cmp::Ordering::Less,
(Self::None, Self::None) => std::cmp::Ordering::Equal,
(Self::None, _) => std::cmp::Ordering::Greater,
}
}
}
impl Id {
pub const fn is_number(&self) -> bool {
matches!(self, Self::Number(_))
}
pub const fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub const fn as_number(&self) -> Option<u64> {
match self {
Self::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestCase {
id: Id,
}
#[test]
fn it_serializes_and_deserializes() {
let cases = [
(TestCase { id: Id::Number(1) }, r#"{"id":1}"#),
(TestCase { id: Id::String("foo".to_string()) }, r#"{"id":"foo"}"#),
(TestCase { id: Id::None }, r#"{"id":null}"#),
];
for (case, expected) in cases {
let serialized = serde_json::to_string(&case).unwrap();
assert_eq!(serialized, expected);
let deserialized: TestCase = serde_json::from_str(expected).unwrap();
assert_eq!(deserialized, case);
}
}
}