1use core::fmt;
15
16use alloc::format;
17use serde::de::{self, Visitor};
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19
20use crate::resolver::resolve_name;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
25pub struct AssetId(pub u32);
26
27impl fmt::Display for AssetId {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 write!(f, "#{}", self.0)
30 }
31}
32
33impl Serialize for AssetId {
34 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
35 s.serialize_u32(self.0)
36 }
37}
38
39struct AssetIdVisitor;
40
41impl Visitor<'_> for AssetIdVisitor {
42 type Value = AssetId;
43
44 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str("an asset id integer or a name string")
46 }
47
48 fn visit_u64<E: de::Error>(self, v: u64) -> Result<AssetId, E> {
49 Ok(AssetId(v as u32))
50 }
51 fn visit_i64<E: de::Error>(self, v: i64) -> Result<AssetId, E> {
52 Ok(AssetId(v as u32))
53 }
54 fn visit_str<E: de::Error>(self, v: &str) -> Result<AssetId, E> {
55 resolve_name(v).map(AssetId).ok_or_else(|| {
56 E::custom(format!(
57 "no asset-name resolver installed to resolve reference {v:?}"
58 ))
59 })
60 }
61 fn visit_string<E: de::Error>(self, v: alloc::string::String) -> Result<AssetId, E> {
62 self.visit_str(&v)
63 }
64}
65
66impl<'de> Deserialize<'de> for AssetId {
67 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
68 if d.is_human_readable() {
69 d.deserialize_any(AssetIdVisitor)
70 } else {
71 Ok(AssetId(u32::deserialize(d)?))
72 }
73 }
74}
75
76pub fn de_opt_asset_ref<'de, D>(d: D) -> Result<Option<AssetId>, D::Error>
83where
84 D: Deserializer<'de>,
85{
86 if !d.is_human_readable() {
89 return Option::<AssetId>::deserialize(d);
90 }
91
92 struct OptVisitor;
93
94 impl Visitor<'_> for OptVisitor {
95 type Value = Option<AssetId>;
96
97 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.write_str("an asset reference name string, id integer, or null")
99 }
100
101 fn visit_unit<E: de::Error>(self) -> Result<Option<AssetId>, E> {
102 Ok(None)
103 }
104 fn visit_none<E: de::Error>(self) -> Result<Option<AssetId>, E> {
105 Ok(None)
106 }
107 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Option<AssetId>, E> {
108 Ok(Some(AssetId(v as u32)))
109 }
110 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Option<AssetId>, E> {
111 Ok(Some(AssetId(v as u32)))
112 }
113 fn visit_str<E: de::Error>(self, v: &str) -> Result<Option<AssetId>, E> {
114 if v.is_empty() {
115 Ok(None)
116 } else {
117 AssetIdVisitor.visit_str(v).map(Some)
118 }
119 }
120 fn visit_string<E: de::Error>(
121 self,
122 v: alloc::string::String,
123 ) -> Result<Option<AssetId>, E> {
124 self.visit_str(&v)
125 }
126 }
127
128 d.deserialize_any(OptVisitor)
129}
130
131#[cfg(test)]
132mod tests {
133 use alloc::string::ToString;
134
135 use super::*;
136
137 #[test]
138 fn round_trips_through_json_as_a_bare_integer() {
139 let bytes = serde_json::to_vec(&AssetId(7)).unwrap();
140 assert_eq!(bytes, b"7");
141 let back: AssetId = serde_json::from_slice(&bytes).unwrap();
142 assert_eq!(back, AssetId(7));
143 }
144
145 #[test]
146 fn deserializes_from_an_already_resolved_integer() {
147 let id: AssetId = serde_json::from_str("5").unwrap();
149 assert_eq!(id, AssetId(5));
150 }
151
152 #[test]
153 fn round_trips_through_postcard() {
154 let bytes = postcard::to_allocvec(&AssetId(1234)).unwrap();
156 let back: AssetId = postcard::from_bytes(&bytes).unwrap();
157 assert_eq!(back, AssetId(1234));
158 }
159
160 #[test]
161 fn a_truncated_baked_id_is_an_error_not_a_panic() {
162 assert!(postcard::from_bytes::<AssetId>(&[]).is_err());
166 }
167
168 #[test]
169 fn opt_ref_treats_empty_null_and_missing_as_none() {
170 #[derive(serde::Deserialize)]
171 struct Holder {
172 #[serde(default, deserialize_with = "de_opt_asset_ref")]
173 r: Option<AssetId>,
174 }
175 assert!(
176 serde_json::from_str::<Holder>("{\"r\":\"\"}")
177 .unwrap()
178 .r
179 .is_none()
180 );
181 assert!(
182 serde_json::from_str::<Holder>("{\"r\":null}")
183 .unwrap()
184 .r
185 .is_none()
186 );
187 assert!(serde_json::from_str::<Holder>("{}").unwrap().r.is_none());
188 assert_eq!(
189 serde_json::from_str::<Holder>("{\"r\":5}").unwrap().r,
190 Some(AssetId(5))
191 );
192 }
193
194 #[test]
195 fn opt_ref_round_trips_through_postcard() {
196 #[derive(serde::Serialize, serde::Deserialize)]
198 struct Holder {
199 #[serde(default, deserialize_with = "de_opt_asset_ref")]
200 r: Option<AssetId>,
201 #[serde(default, deserialize_with = "de_opt_asset_ref")]
202 none: Option<AssetId>,
203 }
204 let h = Holder {
205 r: Some(AssetId(7)),
206 none: None,
207 };
208 let bytes = postcard::to_allocvec(&h).unwrap();
209 let back: Holder = postcard::from_bytes(&bytes).unwrap();
210 assert_eq!(back.r, Some(AssetId(7)));
211 assert_eq!(back.none, None);
212 }
213
214 #[test]
215 fn display_formats_with_a_hash_prefix() {
216 assert_eq!(AssetId(42).to_string(), "#42");
217 }
218
219 #[test]
220 fn default_is_zero() {
221 assert_eq!(AssetId::default(), AssetId(0));
222 }
223
224 #[test]
225 fn deserializes_a_name_through_the_seam() {
226 crate::test_support::install_resolvers();
227 assert_eq!(
228 serde_json::from_str::<AssetId>("\"floor\"").unwrap(),
229 AssetId(5)
230 );
231 assert_eq!(
233 serde_json::from_value::<AssetId>(serde_json::json!("wall")).unwrap(),
234 AssetId(4)
235 );
236 }
237
238 #[test]
239 fn deserializes_a_signed_integer_narrowed_to_id_width() {
240 assert_eq!(
241 serde_json::from_str::<AssetId>("-1").unwrap(),
242 AssetId(u32::MAX)
243 );
244 }
245
246 #[test]
247 fn a_wrong_typed_id_names_what_it_accepts() {
248 let err = serde_json::from_str::<AssetId>("true")
249 .unwrap_err()
250 .to_string();
251 assert!(
252 err.contains("an asset id integer or a name string"),
253 "{err}"
254 );
255 }
256
257 #[derive(Debug, serde::Deserialize)]
258 struct Holder {
259 #[serde(default, deserialize_with = "de_opt_asset_ref")]
260 r: Option<AssetId>,
261 }
262
263 #[test]
264 fn opt_ref_accepts_names_signed_integers_and_a_reported_none() {
265 crate::test_support::install_resolvers();
266 assert_eq!(
267 serde_json::from_str::<Holder>("{\"r\":\"floor\"}")
268 .unwrap()
269 .r,
270 Some(AssetId(5))
271 );
272 assert_eq!(
273 serde_json::from_str::<Holder>("{\"r\":-1}").unwrap().r,
274 Some(AssetId(u32::MAX))
275 );
276 assert_eq!(
278 serde_json::from_value::<Holder>(serde_json::json!({"r": "wall"}))
279 .unwrap()
280 .r,
281 Some(AssetId(4))
282 );
283 assert_eq!(
284 serde_json::from_value::<Holder>(serde_json::json!({"r": ""}))
285 .unwrap()
286 .r,
287 None
288 );
289 assert_eq!(
291 de_opt_asset_ref(crate::test_support::NoneDeserializer).unwrap(),
292 None
293 );
294 }
295
296 #[test]
297 fn a_wrong_typed_opt_ref_names_what_it_accepts() {
298 let err = serde_json::from_str::<Holder>("{\"r\":true}")
299 .unwrap_err()
300 .to_string();
301 assert!(
302 err.contains("an asset reference name string, id integer, or null"),
303 "{err}"
304 );
305 }
306}