1use std::{fmt, str::FromStr};
18
19use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
20
21macro_rules! steam_id_newtype {
22 ($(#[$meta:meta])* $vis:vis $name:ident($inner:ty)) => {
23 $(#[$meta])*
24 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
25 $vis struct $name(pub $inner);
26
27 impl $name {
28 #[inline]
30 pub const fn new(inner: $inner) -> Self {
31 Self(inner)
32 }
33
34 #[inline]
36 pub const fn get(self) -> $inner {
37 self.0
38 }
39 }
40
41 impl From<$inner> for $name {
42 #[inline]
43 fn from(value: $inner) -> Self {
44 Self(value)
45 }
46 }
47
48 impl From<$name> for $inner {
49 #[inline]
50 fn from(value: $name) -> Self {
51 value.0
52 }
53 }
54
55 impl fmt::Display for $name {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 write!(f, "{}", self.0)
58 }
59 }
60
61 impl FromStr for $name {
62 type Err = std::num::ParseIntError;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 s.parse::<$inner>().map(Self)
66 }
67 }
68
69 impl Serialize for $name {
70 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
71 self.0.serialize(serializer)
72 }
73 }
74
75 impl<'de> Deserialize<'de> for $name {
76 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
77 #[derive(Deserialize)]
80 #[serde(untagged)]
81 enum NumOrStr<T> {
82 Num(T),
83 Str(String),
84 }
85 let v = NumOrStr::<$inner>::deserialize(deserializer)?;
86 match v {
87 NumOrStr::Num(n) => Ok(Self(n)),
88 NumOrStr::Str(s) => s.parse::<$inner>().map(Self).map_err(de::Error::custom),
89 }
90 }
91 }
92 };
93}
94
95steam_id_newtype! {
96 pub AppId(u32)
98}
99
100steam_id_newtype! {
101 pub ContextId(u64)
103}
104
105steam_id_newtype! {
106 pub AssetId(u64)
108}
109
110steam_id_newtype! {
111 pub ClassId(u64)
113}
114
115steam_id_newtype! {
116 pub InstanceId(u64)
118}
119
120steam_id_newtype! {
121 pub TradeOfferId(u64)
123}
124
125steam_id_newtype! {
126 pub ItemNameId(u64)
128}
129
130steam_id_newtype! {
131 pub Amount(u32)
133}
134
135steam_id_newtype! {
136 pub PriceCents(u32)
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn parses_number_or_string() {
146 let n: AssetId = serde_json::from_str("42").unwrap();
147 let s: AssetId = serde_json::from_str("\"42\"").unwrap();
148 assert_eq!(n, AssetId(42));
149 assert_eq!(s, AssetId(42));
150 }
151
152 #[test]
153 fn serializes_as_number() {
154 let id = AssetId(42);
155 assert_eq!(serde_json::to_string(&id).unwrap(), "42");
156 }
157
158 #[test]
159 fn ergonomic_conversions() {
160 let id: AppId = 730u32.into();
161 assert_eq!(u32::from(id), 730);
162 assert_eq!(id.to_string(), "730");
163 assert_eq!("730".parse::<AppId>().unwrap(), AppId(730));
164 }
165}