screeps/local/object_id/
raw.rs

1use std::{fmt, fmt::Write, str::FromStr};
2
3use arrayvec::ArrayString;
4use js_sys::JsString;
5use serde::{
6    de::{Error, Visitor},
7    Deserialize, Serialize,
8};
9
10use super::errors::RawObjectIdParseError;
11
12const MAX_PACKED_VAL: u128 = (1 << (32 * 3)) - 1;
13
14/// Represents an Object ID using a packed 12-byte representation
15///
16/// Each object id in screeps is represented by a Mongo GUID, which,
17/// while not guaranteed, is unlikely to change. This takes advantage of that by
18/// storing a packed representation.
19///
20/// # Ordering
21///
22/// To facilitate use as a key in a [`BTreeMap`] or other similar data
23/// structures, `ObjectId` implements [`PartialOrd`] and [`Ord`].
24///
25/// `RawObjectId`'s are ordered by the corresponding order of their underlying
26/// byte values. See [`ObjectId`] documentation for more information.
27///
28/// [`BTreeMap`]: std::collections::BTreeMap
29/// [`Ord`]: std::cmp::Ord
30/// [`PartialOrd`]: std::cmp::PartialOrd
31/// [`ObjectId`]: super::ObjectId
32#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct RawObjectId {
34    packed: u128,
35}
36
37impl fmt::Debug for RawObjectId {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("RawObjectId")
40            .field("packed", &self.packed)
41            .field("real", &self.to_string())
42            .finish()
43    }
44}
45
46impl fmt::Display for RawObjectId {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(
49            f,
50            "{:0width$x}",
51            self.packed >> 32,
52            width = (self.packed as u32) as usize
53        )
54    }
55}
56
57impl Serialize for RawObjectId {
58    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59    where
60        S: serde::Serializer,
61    {
62        if serializer.is_human_readable() {
63            serializer.collect_str(&self.to_array_string())
64        } else {
65            serializer.serialize_bytes(&self.packed.to_be_bytes())
66        }
67    }
68}
69
70struct RawObjectIdVisitor;
71
72impl Visitor<'_> for RawObjectIdVisitor {
73    type Value = RawObjectId;
74
75    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
76        formatter.write_str("a string or bytes representing an object id")
77    }
78
79    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
80    where
81        E: Error,
82    {
83        RawObjectId::from_str(v).map_err(|e| E::custom(format!("Could not parse object id: {e}")))
84    }
85
86    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
87    where
88        E: Error,
89    {
90        v.try_into()
91            .map(u128::from_be_bytes)
92            .map(RawObjectId::from_packed)
93            .map_err(|e| E::custom(format!("Could not parse object id: {e}")))
94    }
95}
96
97impl<'de> Deserialize<'de> for RawObjectId {
98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99    where
100        D: serde::Deserializer<'de>,
101    {
102        if deserializer.is_human_readable() {
103            deserializer.deserialize_str(RawObjectIdVisitor)
104        } else {
105            deserializer.deserialize_bytes(RawObjectIdVisitor)
106        }
107    }
108}
109
110impl FromStr for RawObjectId {
111    type Err = RawObjectIdParseError;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        // get the actual integer value of the id, which we'll store in the most
115        // significant 96 bits of the u128
116        let u128_id = u128::from_str_radix(s, 16)?;
117        // and the length, which we know can't be greater than 24 without going over
118        // MAX_PACKED_VAL
119        let pad_length = s.len() as u128;
120
121        if u128_id > MAX_PACKED_VAL || pad_length > 24 {
122            return Err(RawObjectIdParseError::value_too_large(u128_id));
123        }
124
125        Ok(Self::from((u128_id << 32) | pad_length))
126    }
127}
128
129impl TryFrom<JsString> for RawObjectId {
130    type Error = RawObjectIdParseError;
131
132    fn try_from(js_id: JsString) -> Result<Self, Self::Error> {
133        let id: String = js_id.into();
134        RawObjectId::from_str(&id)
135    }
136}
137
138impl RawObjectId {
139    /// Creates an object ID from its packed representation.
140    ///
141    /// The input to this function is the bytes representing the up-to-24 hex
142    /// digits in the object id.
143    pub const fn from_packed(packed: u128) -> Self {
144        RawObjectId { packed }
145    }
146
147    /// Formats this object ID as a string on the stack.
148    ///
149    /// This is equivalent to [`ToString::to_string`], but involves no
150    /// allocation.
151    pub fn to_array_string(&self) -> ArrayString<24> {
152        let mut res = ArrayString::new();
153        write!(res, "{self}").expect("expected formatting into a fixed-sized buffer to succeed");
154        res
155    }
156}
157
158impl From<RawObjectId> for ArrayString<24> {
159    fn from(id: RawObjectId) -> Self {
160        id.to_array_string()
161    }
162}
163
164impl From<RawObjectId> for String {
165    fn from(id: RawObjectId) -> Self {
166        id.to_string()
167    }
168}
169
170impl From<RawObjectId> for u128 {
171    fn from(id: RawObjectId) -> Self {
172        id.packed
173    }
174}
175
176impl From<u128> for RawObjectId {
177    fn from(packed: u128) -> Self {
178        Self::from_packed(packed)
179    }
180}
181
182#[cfg(test)]
183mod test {
184    use super::RawObjectId;
185
186    const TEST_IDS: &[&str] = &[
187        "0",
188        "1",
189        // max allowed ID
190        "ffffffffffffffffffffffff",
191        // 24 char, leading 0 (#278)
192        "06aebab343040c9baaa22322",
193        "000000000000000000000001",
194        "100000000000000000000000",
195        // potential bad u128 from float?
196        "5bbcab1d9099fc012e632dbc",
197        "5bbcab1d9099fc012e632dbd",
198        "10000000000000000",
199        "1000000000000000",
200        "bc03381d32f6790",
201        // 15 char, leading 0 (#244)
202        "0df4aea318bd552",
203        "000000000000f00",
204        "100000000",
205        "10000000",
206    ];
207
208    #[test]
209    fn rust_display_rust_fromstr_roundtrip() {
210        for id in TEST_IDS {
211            let parsed: RawObjectId = id.parse().unwrap();
212            assert_eq!(&*parsed.to_string(), *id);
213        }
214    }
215
216    #[test]
217    fn rust_to_array_string_rust_fromstr_roundtrip() {
218        for id in TEST_IDS {
219            let parsed: RawObjectId = id.parse().unwrap();
220            assert_eq!(&*parsed.to_array_string(), *id);
221        }
222    }
223
224    #[test]
225    fn rust_to_u128_from_u128_roundtrip() {
226        for id in TEST_IDS {
227            let parsed: RawObjectId = id.parse().unwrap();
228            let int = u128::from(parsed);
229            let reparsed: RawObjectId = int.into();
230            assert_eq!(parsed, reparsed);
231            assert_eq!(reparsed.to_string(), *id);
232        }
233    }
234
235    #[test]
236    fn rust_to_serde_json_from_serde_json_roundtrip() {
237        for id in TEST_IDS {
238            let parsed: RawObjectId = id.parse().unwrap();
239            let serialized = serde_json::to_string(&parsed).unwrap();
240            let reparsed: RawObjectId = serde_json::from_str(&serialized).unwrap();
241            assert_eq!(parsed, reparsed);
242            assert_eq!(reparsed.to_string(), *id);
243        }
244    }
245
246    #[test]
247    fn rust_vec_to_serde_json_from_serde_json_roundtrip() {
248        let mut ids_parsed = vec![];
249        for id in TEST_IDS {
250            let parsed: RawObjectId = id.parse().unwrap();
251            ids_parsed.push(parsed);
252        }
253        let serialized = serde_json::to_string(&ids_parsed).unwrap();
254        let ids_reparsed: Vec<RawObjectId> = serde_json::from_str(&serialized).unwrap();
255        assert_eq!(ids_parsed, ids_reparsed);
256    }
257
258    #[test]
259    fn rust_to_serde_bincode_from_serde_bincode_roundtrip() {
260        for id in TEST_IDS {
261            let parsed: RawObjectId = id.parse().unwrap();
262            let serialized = bincode::serialize(&parsed).unwrap();
263            let reparsed: RawObjectId = bincode::deserialize(&serialized).unwrap();
264            assert_eq!(parsed, reparsed);
265            assert_eq!(reparsed.to_string(), *id);
266        }
267    }
268
269    #[test]
270    fn rust_vec_to_serde_bincode_from_serde_bincode_roundtrip() {
271        let mut ids_parsed = vec![];
272        for id in TEST_IDS {
273            let parsed: RawObjectId = id.parse().unwrap();
274            ids_parsed.push(parsed);
275        }
276        let serialized = bincode::serialize(&ids_parsed).unwrap();
277        let ids_reparsed: Vec<RawObjectId> = bincode::deserialize(&serialized).unwrap();
278        assert_eq!(ids_parsed, ids_reparsed);
279    }
280
281    // sorted first by id value (lowest first), then by the length of the string
282    // (shortest first)
283    #[rustfmt::skip]
284    const SORTED_IDS: &[&str] = &[
285        "0",
286        "00",
287        "000",
288        "0000",
289        "1",
290        "01",
291        "001",
292        "0001",
293        "2",
294        "a",
295        "f",
296        "00f",
297        "10",
298        "010",
299        "0010",
300        "100",
301        "0100",
302        "f00",
303        "0f00",
304        "1000",
305    ];
306
307    #[test]
308    fn sort_order() {
309        let mut ids_parsed = vec![];
310        for id in SORTED_IDS {
311            let parsed: RawObjectId = id.parse().unwrap();
312            ids_parsed.push(parsed);
313        }
314        let mut ids_sort: Vec<_> = ids_parsed.clone();
315        ids_sort.sort();
316        assert_eq!(ids_sort, ids_parsed);
317    }
318
319    const INVALID_IDS: &[&str] = &[
320        // empty string
321        "",
322        // negative number
323        "-1",
324        "-0",
325        // longer than 24 characters
326        "1000000000000000000000000",
327        // valid number but padded beyond what we can store in 96 bits
328        "000000000000000000000000f",
329        // u128::MAX
330        "340282366920938463463374607431768211455",
331        // even longer
332        "000000000000000000000000000000000000000999000340282366920938463463374607431768211455",
333        // bad characters
334        "g",
335        "💣",
336        "\n",
337    ];
338
339    #[test]
340    fn invalid_values_do_not_parse() {
341        for id in INVALID_IDS {
342            let res: Result<RawObjectId, _> = id.parse();
343            assert!(res.is_err());
344        }
345    }
346}