minecraft_command_types/
snbt.rs

1use crate::has_macro::HasMacro;
2use crate::nbt_path::SNBTCompound;
3use ordered_float::NotNan;
4use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
5use serde::{Serialize, Serializer, de};
6use std::collections::BTreeMap;
7use std::fmt::Display;
8use std::fmt::Formatter;
9
10#[derive(Debug, Clone, Eq, PartialEq, Hash)]
11pub enum SNBT {
12    Byte(i8),
13    Short(i16),
14    Integer(i32),
15    Long(i64),
16    Float(NotNan<f32>),
17    Double(NotNan<f64>),
18    String(String),
19    List(Vec<SNBT>),
20    Compound(BTreeMap<String, SNBT>),
21    ByteArray(Vec<i8>),
22    IntegerArray(Vec<i32>),
23    LongArray(Vec<i64>),
24    Macro(String),
25}
26
27impl SNBT {
28    #[must_use]
29    pub fn list<T: Into<SNBT>>(values: Vec<T>) -> SNBT {
30        SNBT::List(values.into_iter().map(Into::into).collect())
31    }
32
33    #[must_use]
34    pub fn compound<T: Into<SNBT>>(values: BTreeMap<String, T>) -> SNBT {
35        SNBT::Compound(values.into_iter().map(|(k, v)| (k, v.into())).collect())
36    }
37
38    #[must_use]
39    pub fn get(&self, key: &String) -> Option<&SNBT> {
40        if let SNBT::Compound(compound) = self {
41            compound.get(key)
42        } else {
43            None
44        }
45    }
46
47    #[inline]
48    #[must_use]
49    fn has_macro_string(&self) -> bool {
50        if let SNBT::String(string) = self {
51            string.contains("$(")
52        } else {
53            false
54        }
55    }
56}
57
58impl HasMacro for SNBT {
59    fn has_macro(&self) -> bool {
60        match self {
61            SNBT::Macro(_) => true,
62            SNBT::List(list) => list.iter().any(|v| v.has_macro()),
63            SNBT::Compound(compound) => compound.values().any(|v| v.has_macro()),
64            _ => false,
65        }
66    }
67
68    fn has_macro_conflict(&self) -> bool {
69        match self {
70            SNBT::List(values) => values.has_macro() && values.iter().any(|v| v.has_macro_string()),
71            SNBT::Compound(compound) => {
72                compound.values().any(|v| v.has_macro())
73                    && compound.values().any(|v| v.has_macro_string())
74            }
75            _ => false,
76        }
77    }
78}
79
80pub fn fmt_snbt_compound(f: &mut Formatter<'_>, compound: &SNBTCompound) -> std::fmt::Result {
81    f.write_str("{")?;
82
83    for (i, (k, v)) in compound.iter().enumerate() {
84        if i > 0 {
85            f.write_str(", ")?;
86        }
87
88        write!(f, "\"{}\":{}", escape(k), v)?;
89    }
90
91    f.write_str("}")
92}
93
94#[inline]
95#[must_use]
96fn escape(input: &str) -> String {
97    input.replace('\\', "\\\\").replace('"', "\\\"")
98}
99
100impl Display for SNBT {
101    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102        match self {
103            SNBT::Byte(v) => write!(f, "{}b", v),
104            SNBT::Short(v) => write!(f, "{}s", v),
105            SNBT::Integer(v) => write!(f, "{}", v),
106            SNBT::Long(v) => write!(f, "{}l", v),
107            SNBT::Float(v) => write!(f, "{}f", v),
108            SNBT::Double(v) => write!(f, "{}d", v),
109            SNBT::String(s) => {
110                write!(f, "\"{}\"", escape(s))
111            }
112            SNBT::List(values) => {
113                f.write_str("]")?;
114
115                for (i, v) in values.iter().enumerate() {
116                    if i > 0 {
117                        f.write_str(", ")?;
118                    }
119
120                    v.fmt(f)?;
121                }
122
123                f.write_str("]")
124            }
125            SNBT::Compound(map) => fmt_snbt_compound(f, map),
126            SNBT::ByteArray(arr) => {
127                f.write_str("[B; ")?;
128
129                for (i, v) in arr.iter().enumerate() {
130                    if i > 0 {
131                        f.write_str(", ")?;
132                    }
133
134                    write!(f, "{}b", v)?;
135                }
136
137                f.write_str("]")
138            }
139            SNBT::IntegerArray(arr) => {
140                f.write_str("[I; ")?;
141
142                for (i, v) in arr.iter().enumerate() {
143                    if i > 0 {
144                        f.write_str(", ")?;
145                    }
146
147                    v.fmt(f)?;
148                }
149
150                f.write_str("]")
151            }
152            SNBT::LongArray(arr) => {
153                f.write_str("[L; ")?;
154
155                for (i, v) in arr.iter().enumerate() {
156                    if i > 0 {
157                        f.write_str(", ")?;
158                    }
159
160                    write!(f, "{}L", v)?;
161                }
162
163                f.write_str("]")
164            }
165            SNBT::Macro(name) => write!(f, "$({})", name),
166        }
167    }
168}
169
170impl Serialize for SNBT {
171    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172    where
173        S: Serializer,
174    {
175        match self {
176            SNBT::Byte(v) => serializer.serialize_i8(*v),
177            SNBT::Short(v) => serializer.serialize_i16(*v),
178            SNBT::Integer(v) => serializer.serialize_i32(*v),
179            SNBT::Long(v) => serializer.serialize_i64(*v),
180            SNBT::Float(v) => serializer.serialize_f32(**v),
181            SNBT::Double(v) => serializer.serialize_f64(**v),
182            SNBT::String(v) => serializer.serialize_str(v),
183            SNBT::List(v) => v.serialize(serializer),
184            SNBT::Compound(v) => v.serialize(serializer),
185            SNBT::ByteArray(v) => v.serialize(serializer),
186            SNBT::IntegerArray(v) => v.serialize(serializer),
187            SNBT::LongArray(v) => v.serialize(serializer),
188            SNBT::Macro(v) => format!("<macro {}>", v).serialize(serializer),
189        }
190    }
191}
192
193impl<'de> Deserialize<'de> for SNBT {
194    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195    where
196        D: Deserializer<'de>,
197    {
198        deserializer.deserialize_any(SNBTVisitor)
199    }
200}
201
202struct SNBTVisitor;
203
204impl<'de> Visitor<'de> for SNBTVisitor {
205    type Value = SNBT;
206
207    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
208        formatter.write_str("any valid SNBT value")
209    }
210
211    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
212        Ok(SNBT::Long(value))
213    }
214
215    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
216    where
217        E: de::Error,
218    {
219        if let Ok(v) = i64::try_from(value) {
220            Ok(SNBT::Long(v))
221        } else {
222            Err(E::custom(format!("u64 out of range for i64: {}", value)))
223        }
224    }
225
226    fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
227    where
228        E: de::Error,
229    {
230        NotNan::new(value)
231            .map(SNBT::Double)
232            .map_err(|_| E::custom("f64 value was NaN"))
233    }
234
235    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
236        Ok(SNBT::String(value.to_owned()))
237    }
238
239    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
240    where
241        A: SeqAccess<'de>,
242    {
243        let mut list = Vec::new();
244        while let Some(elem) = seq.next_element()? {
245            list.push(elem);
246        }
247        Ok(SNBT::List(list))
248    }
249
250    fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
251    where
252        M: MapAccess<'de>,
253    {
254        let mut compound = BTreeMap::new();
255        while let Some((key, value)) = map.next_entry()? {
256            compound.insert(key, value);
257        }
258        Ok(SNBT::Compound(compound))
259    }
260}
261
262impl From<i8> for SNBT {
263    fn from(i: i8) -> Self {
264        SNBT::Byte(i)
265    }
266}
267
268impl From<i16> for SNBT {
269    fn from(i: i16) -> Self {
270        SNBT::Short(i)
271    }
272}
273
274impl From<i32> for SNBT {
275    fn from(i: i32) -> Self {
276        SNBT::Integer(i)
277    }
278}
279
280impl From<i64> for SNBT {
281    fn from(i: i64) -> Self {
282        SNBT::Long(i)
283    }
284}
285
286impl From<NotNan<f32>> for SNBT {
287    fn from(f: NotNan<f32>) -> Self {
288        SNBT::Float(f)
289    }
290}
291
292impl From<NotNan<f64>> for SNBT {
293    fn from(f: NotNan<f64>) -> Self {
294        SNBT::Double(f)
295    }
296}
297
298impl From<String> for SNBT {
299    fn from(s: String) -> Self {
300        SNBT::String(s)
301    }
302}
303
304impl From<Vec<SNBT>> for SNBT {
305    fn from(v: Vec<SNBT>) -> Self {
306        SNBT::List(v)
307    }
308}
309
310impl From<BTreeMap<String, SNBT>> for SNBT {
311    fn from(m: BTreeMap<String, SNBT>) -> Self {
312        SNBT::Compound(m)
313    }
314}
315
316impl From<Vec<i8>> for SNBT {
317    fn from(v: Vec<i8>) -> Self {
318        SNBT::ByteArray(v)
319    }
320}
321
322impl From<Vec<i32>> for SNBT {
323    fn from(v: Vec<i32>) -> Self {
324        SNBT::IntegerArray(v)
325    }
326}
327
328impl From<Vec<i64>> for SNBT {
329    fn from(v: Vec<i64>) -> Self {
330        SNBT::LongArray(v)
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337    use ordered_float::NotNan;
338    use std::collections::BTreeMap;
339
340    fn nnf32(val: f32) -> NotNan<f32> {
341        NotNan::new(val).unwrap()
342    }
343
344    fn nnf64(val: f64) -> NotNan<f64> {
345        NotNan::new(val).unwrap()
346    }
347
348    #[test]
349    fn test_get_from_compound() {
350        let mut map = BTreeMap::new();
351        map.insert("key1".to_string(), SNBT::Integer(123));
352        map.insert("key2".to_string(), SNBT::String("hello".to_string()));
353        let compound = SNBT::Compound(map);
354
355        assert_eq!(compound.get(&"key1".to_string()), Some(&SNBT::Integer(123)));
356        assert_eq!(
357            compound.get(&"key2".to_string()),
358            Some(&SNBT::String("hello".to_string()))
359        );
360        assert_eq!(compound.get(&"non_existent_key".to_string()), None);
361    }
362
363    #[test]
364    fn test_get_from_non_compound() {
365        let list = SNBT::List(vec![SNBT::Integer(1)]);
366        let integer = SNBT::Integer(42);
367        let string = SNBT::String("test".to_string());
368
369        assert_eq!(list.get(&"any_key".to_string()), None);
370        assert_eq!(integer.get(&"any_key".to_string()), None);
371        assert_eq!(string.get(&"any_key".to_string()), None);
372    }
373
374    #[test]
375    fn test_from_implementations() {
376        assert_eq!(SNBT::from(10i8), SNBT::Byte(10));
377        assert_eq!(SNBT::from(1000i16), SNBT::Short(1000));
378        assert_eq!(SNBT::from(100000i32), SNBT::Integer(100000));
379        assert_eq!(SNBT::from(10000000000i64), SNBT::Long(10000000000));
380        assert_eq!(SNBT::from(nnf32(1.23)), SNBT::Float(nnf32(1.23)));
381        assert_eq!(SNBT::from(nnf64(4.56)), SNBT::Double(nnf64(4.56)));
382        assert_eq!(
383            SNBT::from("hello".to_string()),
384            SNBT::String("hello".to_string())
385        );
386        assert_eq!(
387            SNBT::from(vec![SNBT::Integer(1)]),
388            SNBT::List(vec![SNBT::Integer(1)])
389        );
390        let mut map = BTreeMap::new();
391        map.insert("a".to_string(), SNBT::Integer(1));
392        assert_eq!(SNBT::from(map.clone()), SNBT::Compound(map));
393        assert_eq!(SNBT::from(vec![1, 2, 3]), SNBT::IntegerArray(vec![1, 2, 3]));
394    }
395
396    mod serde_tests {
397        use super::*;
398        use serde_json;
399
400        fn assert_roundtrip(snbt: &SNBT, expected_json: &str) {
401            let json = serde_json::to_string(snbt).unwrap();
402            assert_eq!(json, expected_json);
403
404            let deserialized: SNBT = serde_json::from_str(&json).unwrap();
405            assert_eq!(*snbt, deserialized);
406        }
407
408        #[test]
409        fn test_serde_primitives() {
410            assert_roundtrip(&SNBT::Long(9223372036854775807), "9223372036854775807");
411            assert_roundtrip(&SNBT::Double(nnf64(-1.5e10)), "-15000000000.0");
412            assert_roundtrip(
413                &SNBT::String("Hello, World!".to_string()),
414                "\"Hello, World!\"",
415            );
416        }
417
418        #[test]
419        fn test_serde_list() {
420            let list = SNBT::List(vec![SNBT::Long(1), SNBT::String("two".to_string())]);
421            let json = "[1,\"two\"]";
422            assert_roundtrip(&list, json);
423        }
424
425        #[test]
426        fn test_serde_compound() {
427            let mut map = BTreeMap::new();
428            map.insert("name".to_string(), SNBT::String("Test".to_string()));
429            map.insert("value".to_string(), SNBT::Long(42));
430            let compound = SNBT::Compound(map);
431            let json = "{\"name\":\"Test\",\"value\":42}";
432            assert_roundtrip(&compound, json);
433        }
434
435        #[test]
436        fn test_serde_nested() {
437            let mut root = BTreeMap::new();
438            root.insert("id".to_string(), SNBT::Long(123456789));
439            root.insert(
440                "data".to_string(),
441                SNBT::List(vec![
442                    SNBT::Compound({
443                        let mut item1 = BTreeMap::new();
444                        item1.insert("type".to_string(), SNBT::String("A".to_string()));
445                        item1.insert("coords".to_string(), SNBT::list(vec![1i64, 2, 3]));
446                        item1
447                    }),
448                    SNBT::Compound({
449                        let mut item2 = BTreeMap::new();
450                        item2.insert("type".to_string(), SNBT::String("B".to_string()));
451                        item2
452                    }),
453                ]),
454            );
455            let snbt = SNBT::Compound(root);
456            let json = r#"{"data":[{"coords":[1,2,3],"type":"A"},{"type":"B"}],"id":123456789}"#;
457            assert_roundtrip(&snbt, json);
458        }
459
460        #[test]
461        fn test_deserialize_u64_in_range() {
462            let u64_val: u64 = 100;
463            let json = format!("{}", u64_val);
464            let snbt: SNBT = serde_json::from_str(&json).unwrap();
465            assert_eq!(snbt, SNBT::Long(100));
466        }
467
468        #[test]
469        fn test_deserialize_u64_out_of_range_fails() {
470            let u64_val: u64 = i64::MAX as u64 + 1;
471            let json = format!("{}", u64_val);
472            let result: Result<SNBT, _> = serde_json::from_str(&json);
473            assert!(result.is_err());
474            let err_msg = result.unwrap_err().to_string();
475            assert!(err_msg.contains("u64 out of range for i64"));
476        }
477
478        #[test]
479        fn test_deserialize_nan_double_fails() {
480            let result: Result<SNBT, _> = serde_json::from_str("NaN");
481            assert!(result.is_err());
482        }
483    }
484
485    #[cfg(test)]
486    mod has_macro_tests {
487        use super::*;
488        use std::collections::BTreeMap;
489
490        #[test]
491        fn test_has_macro_direct() {
492            let snbt = SNBT::Macro("test".to_string());
493            assert!(snbt.has_macro());
494        }
495
496        #[test]
497        fn test_has_macro_in_list() {
498            let snbt = SNBT::List(vec![
499                SNBT::Integer(1),
500                SNBT::Macro("test".to_string()),
501                SNBT::Integer(2),
502            ]);
503            assert!(snbt.has_macro());
504        }
505
506        #[test]
507        fn test_has_macro_in_compound() {
508            let mut map = BTreeMap::new();
509            map.insert("a".to_string(), SNBT::Integer(1));
510            map.insert("b".to_string(), SNBT::Macro("test".to_string()));
511            let snbt = SNBT::Compound(map);
512            assert!(snbt.has_macro());
513        }
514
515        #[test]
516        fn test_has_macro_nested() {
517            let mut inner_map = BTreeMap::new();
518            inner_map.insert("c".to_string(), SNBT::Macro("test".to_string()));
519            let mut map = BTreeMap::new();
520            map.insert("a".to_string(), SNBT::Integer(1));
521            map.insert("b".to_string(), SNBT::Compound(inner_map));
522            let snbt = SNBT::Compound(map);
523            assert!(snbt.has_macro());
524        }
525
526        #[test]
527        fn test_no_macro() {
528            let snbt = SNBT::List(vec![SNBT::Integer(1), SNBT::Integer(2)]);
529            assert!(!snbt.has_macro());
530        }
531
532        #[test]
533        fn test_no_macro_in_compound() {
534            let mut map = BTreeMap::new();
535            map.insert("a".to_string(), SNBT::Integer(1));
536            map.insert("b".to_string(), SNBT::String("test".to_string()));
537            let snbt = SNBT::Compound(map);
538            assert!(!snbt.has_macro());
539        }
540    }
541}