use std::{fmt, str::FromStr};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
macro_rules! steam_id_newtype {
($(#[$meta:meta])* $vis:vis $name:ident($inner:ty)) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
$vis struct $name(pub $inner);
impl $name {
#[inline]
pub const fn new(inner: $inner) -> Self {
Self(inner)
}
#[inline]
pub const fn get(self) -> $inner {
self.0
}
}
impl From<$inner> for $name {
#[inline]
fn from(value: $inner) -> Self {
Self(value)
}
}
impl From<$name> for $inner {
#[inline]
fn from(value: $name) -> Self {
value.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for $name {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<$inner>().map(Self)
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(untagged)]
enum NumOrStr<T> {
Num(T),
Str(String),
}
let v = NumOrStr::<$inner>::deserialize(deserializer)?;
match v {
NumOrStr::Num(n) => Ok(Self(n)),
NumOrStr::Str(s) => s.parse::<$inner>().map(Self).map_err(de::Error::custom),
}
}
}
};
}
steam_id_newtype! {
pub AppId(u32)
}
steam_id_newtype! {
pub ContextId(u64)
}
steam_id_newtype! {
pub AssetId(u64)
}
steam_id_newtype! {
pub ClassId(u64)
}
steam_id_newtype! {
pub InstanceId(u64)
}
steam_id_newtype! {
pub TradeOfferId(u64)
}
steam_id_newtype! {
pub ItemNameId(u64)
}
steam_id_newtype! {
pub Amount(u32)
}
steam_id_newtype! {
pub PriceCents(u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_number_or_string() {
let n: AssetId = serde_json::from_str("42").unwrap();
let s: AssetId = serde_json::from_str("\"42\"").unwrap();
assert_eq!(n, AssetId(42));
assert_eq!(s, AssetId(42));
}
#[test]
fn serializes_as_number() {
let id = AssetId(42);
assert_eq!(serde_json::to_string(&id).unwrap(), "42");
}
#[test]
fn ergonomic_conversions() {
let id: AppId = 730u32.into();
assert_eq!(u32::from(id), 730);
assert_eq!(id.to_string(), "730");
assert_eq!("730".parse::<AppId>().unwrap(), AppId(730));
}
}