Skip to main content

nbt_rust/
serde_api.rs

1use std::collections::BTreeMap;
2use std::io::Cursor;
3
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_value::Value as SerdeValue;
7
8use crate::config::NbtReadConfig;
9use crate::encoding::{BigEndian, LittleEndian, NetworkLittleEndian};
10use crate::error::{Error, Result};
11use crate::root::{read_tag_with_config, write_tag, RootTag};
12use crate::tag::{CompoundTag, ListTag, Tag, TagType};
13
14/// Typed serde conversion contract for `Option` and byte vectors.
15///
16/// - `Option::Some(T)` is serialized as `T`'s NBT tag payload.
17/// - `Option::None` is rejected (NBT has no native null marker).
18/// - Non-empty `Vec<u8>` is detected and encoded as `Tag::ByteArray`.
19/// - Empty `Vec<u8>` is ambiguous under `serde_value` (type erasure); to force
20///   byte-array semantics for empty payloads, use [`NbtByteArray`].
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct SerdeBehaviorContract {
23    pub option_some_as_inner_tag: bool,
24    pub option_none_rejected: bool,
25    pub vec_u8_non_empty_as_byte_array: bool,
26    pub empty_vec_u8_requires_wrapper: bool,
27}
28
29pub const SERDE_BEHAVIOR_CONTRACT: SerdeBehaviorContract = SerdeBehaviorContract {
30    option_some_as_inner_tag: true,
31    option_none_rejected: true,
32    vec_u8_non_empty_as_byte_array: true,
33    empty_vec_u8_requires_wrapper: true,
34};
35
36/// Wrapper that guarantees `Tag::ByteArray` semantics (including empty payloads)
37/// when used with typed serde conversion APIs.
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39pub struct NbtByteArray(pub Vec<u8>);
40
41impl Serialize for NbtByteArray {
42    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
43    where
44        S: Serializer,
45    {
46        serializer.serialize_bytes(&self.0)
47    }
48}
49
50impl<'de> Deserialize<'de> for NbtByteArray {
51    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
52    where
53        D: Deserializer<'de>,
54    {
55        Ok(Self(Vec::<u8>::deserialize(deserializer)?))
56    }
57}
58
59pub fn to_byte_array_tag(bytes: impl Into<Vec<u8>>) -> Tag {
60    Tag::ByteArray(bytes.into())
61}
62
63pub fn from_byte_array_tag(tag: &Tag) -> Result<Vec<u8>> {
64    match tag {
65        Tag::ByteArray(value) => Ok(value.clone()),
66        other => Err(Error::UnexpectedType {
67            context: "byte_array_tag_decode",
68            expected_id: TagType::ByteArray.id(),
69            actual_id: other.tag_type().id(),
70        }),
71    }
72}
73
74pub fn to_tag<T: Serialize>(value: &T) -> Result<Tag> {
75    let raw = serde_value::to_value(value).map_err(serde_error)?;
76    serde_value_to_tag(raw)
77}
78
79pub fn from_tag<T: DeserializeOwned>(tag: &Tag) -> Result<T> {
80    let raw = tag_to_serde_value(tag)?;
81    raw.deserialize_into().map_err(serde_error)
82}
83
84pub fn to_root_tag<T: Serialize>(name: impl Into<String>, value: &T) -> Result<RootTag> {
85    Ok(RootTag::new(name, to_tag(value)?))
86}
87
88pub fn from_root_tag<T: DeserializeOwned>(root: &RootTag) -> Result<T> {
89    from_tag(&root.payload)
90}
91
92pub fn to_be_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>> {
93    to_be_bytes_named("", value)
94}
95
96pub fn to_le_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>> {
97    to_le_bytes_named("", value)
98}
99
100pub fn to_net_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>> {
101    to_net_bytes_named("", value)
102}
103
104pub fn to_be_bytes_named<T: Serialize>(name: impl Into<String>, value: &T) -> Result<Vec<u8>> {
105    let root = to_root_tag(name, value)?;
106    let mut out = Vec::new();
107    write_tag::<BigEndian, _>(&mut out, &root)?;
108    Ok(out)
109}
110
111pub fn to_le_bytes_named<T: Serialize>(name: impl Into<String>, value: &T) -> Result<Vec<u8>> {
112    let root = to_root_tag(name, value)?;
113    let mut out = Vec::new();
114    write_tag::<LittleEndian, _>(&mut out, &root)?;
115    Ok(out)
116}
117
118pub fn to_net_bytes_named<T: Serialize>(name: impl Into<String>, value: &T) -> Result<Vec<u8>> {
119    let root = to_root_tag(name, value)?;
120    let mut out = Vec::new();
121    write_tag::<NetworkLittleEndian, _>(&mut out, &root)?;
122    Ok(out)
123}
124
125pub fn from_be_bytes<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
126    from_be_bytes_with_config(bytes, &NbtReadConfig::default())
127}
128
129pub fn from_be_bytes_named<T: DeserializeOwned>(bytes: &[u8]) -> Result<(String, T)> {
130    from_be_bytes_named_with_config(bytes, &NbtReadConfig::default())
131}
132
133pub fn from_le_bytes<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
134    from_le_bytes_with_config(bytes, &NbtReadConfig::default())
135}
136
137pub fn from_net_bytes<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
138    from_net_bytes_with_config(bytes, &NbtReadConfig::default())
139}
140
141pub fn from_be_bytes_with_config<T: DeserializeOwned>(
142    bytes: &[u8],
143    config: &NbtReadConfig,
144) -> Result<T> {
145    let mut cursor = Cursor::new(bytes);
146    let root = read_tag_with_config::<BigEndian, _>(&mut cursor, config)?;
147    ensure_consumed(bytes.len(), cursor.position() as usize)?;
148    from_root_tag(&root)
149}
150
151pub fn from_be_bytes_named_with_config<T: DeserializeOwned>(
152    bytes: &[u8],
153    config: &NbtReadConfig,
154) -> Result<(String, T)> {
155    let mut cursor = Cursor::new(bytes);
156    let root = read_tag_with_config::<BigEndian, _>(&mut cursor, config)?;
157    ensure_consumed(bytes.len(), cursor.position() as usize)?;
158    let value = from_root_tag(&root)?;
159    Ok((root.name, value))
160}
161
162pub fn from_le_bytes_with_config<T: DeserializeOwned>(
163    bytes: &[u8],
164    config: &NbtReadConfig,
165) -> Result<T> {
166    let mut cursor = Cursor::new(bytes);
167    let root = read_tag_with_config::<LittleEndian, _>(&mut cursor, config)?;
168    ensure_consumed(bytes.len(), cursor.position() as usize)?;
169    from_root_tag(&root)
170}
171
172pub fn from_net_bytes_with_config<T: DeserializeOwned>(
173    bytes: &[u8],
174    config: &NbtReadConfig,
175) -> Result<T> {
176    let mut cursor = Cursor::new(bytes);
177    let root = read_tag_with_config::<NetworkLittleEndian, _>(&mut cursor, config)?;
178    ensure_consumed(bytes.len(), cursor.position() as usize)?;
179    from_root_tag(&root)
180}
181
182fn ensure_consumed(total: usize, consumed: usize) -> Result<()> {
183    if consumed == total {
184        return Ok(());
185    }
186    Err(Error::TrailingPayloadBytes {
187        unread: total - consumed,
188    })
189}
190
191fn serde_error<E: std::fmt::Display>(error: E) -> Error {
192    Error::Serde {
193        message: error.to_string(),
194    }
195}
196
197fn serde_value_to_tag(value: SerdeValue) -> Result<Tag> {
198    match value {
199        SerdeValue::Bool(value) => Ok(Tag::Byte(if value { 1 } else { 0 })),
200        SerdeValue::I8(value) => Ok(Tag::Byte(value)),
201        SerdeValue::I16(value) => Ok(Tag::Short(value)),
202        SerdeValue::I32(value) => Ok(Tag::Int(value)),
203        SerdeValue::I64(value) => Ok(Tag::Long(value)),
204        SerdeValue::U8(value) => Ok(Tag::Short(value as i16)),
205        SerdeValue::U16(value) => i16::try_from(value)
206            .map(Tag::Short)
207            .or_else(|_| Ok(Tag::Int(i32::from(value)))),
208        SerdeValue::U32(value) => {
209            if let Ok(int) = i32::try_from(value) {
210                Ok(Tag::Int(int))
211            } else {
212                Ok(Tag::Long(i64::from(value)))
213            }
214        }
215        SerdeValue::U64(value) => {
216            let long = i64::try_from(value).map_err(|_| serde_error("u64 out of i64 range"))?;
217            Ok(Tag::Long(long))
218        }
219        SerdeValue::F32(value) => Ok(Tag::Float(value)),
220        SerdeValue::F64(value) => Ok(Tag::Double(value)),
221        SerdeValue::Char(value) => Ok(Tag::String(value.to_string())),
222        SerdeValue::String(value) => Ok(Tag::String(value)),
223        SerdeValue::Bytes(bytes) => Ok(Tag::ByteArray(bytes)),
224        SerdeValue::Seq(values) => serde_seq_to_tag(values),
225        SerdeValue::Map(values) => serde_map_to_compound(values).map(Tag::Compound),
226        SerdeValue::Option(None) => Err(serde_error("Option::None is not representable in NBT")),
227        SerdeValue::Option(Some(inner)) => serde_value_to_tag(*inner),
228        SerdeValue::Unit => Err(serde_error("unit values are not representable in NBT")),
229        SerdeValue::Newtype(inner) => serde_value_to_tag(*inner),
230    }
231}
232
233fn serde_seq_to_tag(values: Vec<SerdeValue>) -> Result<Tag> {
234    if values.is_empty() {
235        return Ok(Tag::List(ListTag::empty(TagType::End)));
236    }
237
238    if let Some(bytes) = try_u8_seq_to_byte_array(&values) {
239        return Ok(Tag::ByteArray(bytes));
240    }
241    if let Some(ints) = try_i32_seq_to_int_array(&values) {
242        return Ok(Tag::IntArray(ints));
243    }
244    if let Some(longs) = try_i64_seq_to_long_array(&values) {
245        return Ok(Tag::LongArray(longs));
246    }
247
248    let mut tags = Vec::with_capacity(values.len());
249    for value in values {
250        tags.push(serde_value_to_tag(value)?);
251    }
252    let element_type = tags.first().map(Tag::tag_type).unwrap_or(TagType::End);
253    Ok(Tag::List(ListTag::new(element_type, tags)?))
254}
255
256fn try_u8_seq_to_byte_array(values: &[SerdeValue]) -> Option<Vec<u8>> {
257    let mut out = Vec::with_capacity(values.len());
258    for value in values {
259        match value {
260            SerdeValue::U8(byte) => out.push(*byte),
261            _ => return None,
262        }
263    }
264    Some(out)
265}
266
267fn try_i32_seq_to_int_array(values: &[SerdeValue]) -> Option<Vec<i32>> {
268    let mut out = Vec::with_capacity(values.len());
269    for value in values {
270        match value {
271            SerdeValue::I32(int) => out.push(*int),
272            SerdeValue::U32(int) => out.push(i32::try_from(*int).ok()?),
273            _ => return None,
274        }
275    }
276    Some(out)
277}
278
279fn try_i64_seq_to_long_array(values: &[SerdeValue]) -> Option<Vec<i64>> {
280    let mut out = Vec::with_capacity(values.len());
281    for value in values {
282        match value {
283            SerdeValue::I64(long) => out.push(*long),
284            SerdeValue::U64(long) => out.push(i64::try_from(*long).ok()?),
285            _ => return None,
286        }
287    }
288    Some(out)
289}
290
291fn serde_map_to_compound(values: BTreeMap<SerdeValue, SerdeValue>) -> Result<CompoundTag> {
292    let mut out = CompoundTag::new();
293    for (key, value) in values {
294        let key = serde_key_to_string(key)?;
295        let value = serde_value_to_tag(value)?;
296        out.insert(key, value);
297    }
298    Ok(out)
299}
300
301fn serde_key_to_string(value: SerdeValue) -> Result<String> {
302    match value {
303        SerdeValue::String(value) => Ok(value),
304        SerdeValue::Char(value) => Ok(value.to_string()),
305        SerdeValue::Bool(value) => Ok(value.to_string()),
306        SerdeValue::I8(value) => Ok(value.to_string()),
307        SerdeValue::I16(value) => Ok(value.to_string()),
308        SerdeValue::I32(value) => Ok(value.to_string()),
309        SerdeValue::I64(value) => Ok(value.to_string()),
310        SerdeValue::U8(value) => Ok(value.to_string()),
311        SerdeValue::U16(value) => Ok(value.to_string()),
312        SerdeValue::U32(value) => Ok(value.to_string()),
313        SerdeValue::U64(value) => Ok(value.to_string()),
314        _ => Err(serde_error("map key must be string-like for NBT compound")),
315    }
316}
317
318fn tag_to_serde_value(tag: &Tag) -> Result<SerdeValue> {
319    match tag {
320        Tag::End => Err(serde_error(
321            "TAG_End is not representable as a typed serde value",
322        )),
323        Tag::Byte(value) => Ok(SerdeValue::I8(*value)),
324        Tag::Short(value) => Ok(SerdeValue::I16(*value)),
325        Tag::Int(value) => Ok(SerdeValue::I32(*value)),
326        Tag::Long(value) => Ok(SerdeValue::I64(*value)),
327        Tag::Float(value) => Ok(SerdeValue::F32(*value)),
328        Tag::Double(value) => Ok(SerdeValue::F64(*value)),
329        Tag::ByteArray(values) => Ok(SerdeValue::Seq(
330            values.iter().copied().map(SerdeValue::U8).collect(),
331        )),
332        Tag::String(value) => Ok(SerdeValue::String(value.clone())),
333        Tag::List(list) => {
334            let mut values = Vec::with_capacity(list.elements.len());
335            for element in &list.elements {
336                values.push(tag_to_serde_value(element)?);
337            }
338            Ok(SerdeValue::Seq(values))
339        }
340        Tag::Compound(values) => {
341            let mut map = BTreeMap::new();
342            for (key, value) in values {
343                map.insert(SerdeValue::String(key.clone()), tag_to_serde_value(value)?);
344            }
345            Ok(SerdeValue::Map(map))
346        }
347        Tag::IntArray(values) => Ok(SerdeValue::Seq(
348            values.iter().copied().map(SerdeValue::I32).collect(),
349        )),
350        Tag::LongArray(values) => Ok(SerdeValue::Seq(
351            values.iter().copied().map(SerdeValue::I64).collect(),
352        )),
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use indexmap::IndexMap;
359    use serde::{Deserialize, Serialize};
360
361    use super::*;
362
363    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
364    struct DemoData {
365        name: String,
366        health: i32,
367        pitch: f32,
368        bytes: Vec<u8>,
369        scores: Vec<i32>,
370    }
371
372    fn sample() -> DemoData {
373        DemoData {
374            name: "Steve".to_string(),
375            health: 20,
376            pitch: 11.5,
377            bytes: vec![1, 2, 3, 250],
378            scores: vec![7, 11, 42],
379        }
380    }
381
382    #[test]
383    fn tag_roundtrip_typed() {
384        let input = sample();
385        let tag = to_tag(&input).unwrap();
386        let output: DemoData = from_tag(&tag).unwrap();
387        assert_eq!(output, input);
388    }
389
390    #[test]
391    fn be_bytes_roundtrip_typed() {
392        let input = sample();
393        let bytes = to_be_bytes(&input).unwrap();
394        let output: DemoData = from_be_bytes(&bytes).unwrap();
395        assert_eq!(output, input);
396    }
397
398    #[test]
399    fn be_bytes_named_roundtrip_typed() {
400        let input = sample();
401        let bytes = to_be_bytes_named("PlayerData", &input).unwrap();
402        let (root_name, output): (String, DemoData) = from_be_bytes_named(&bytes).unwrap();
403        assert_eq!(root_name, "PlayerData");
404        assert_eq!(output, input);
405    }
406
407    #[test]
408    fn le_bytes_roundtrip_typed() {
409        let input = sample();
410        let bytes = to_le_bytes_named("demo", &input).unwrap();
411        let output: DemoData = from_le_bytes(&bytes).unwrap();
412        assert_eq!(output, input);
413    }
414
415    #[test]
416    fn net_bytes_roundtrip_typed() {
417        let input = sample();
418        let bytes = to_net_bytes(&input).unwrap();
419        let output: DemoData = from_net_bytes(&bytes).unwrap();
420        assert_eq!(output, input);
421    }
422
423    #[test]
424    fn none_option_is_rejected() {
425        #[derive(Serialize)]
426        struct OptionalField {
427            maybe: Option<i32>,
428        }
429
430        let err = to_tag(&OptionalField { maybe: None }).unwrap_err();
431        assert!(matches!(err.innermost(), Error::Serde { .. }));
432    }
433
434    #[test]
435    fn some_option_is_serialized_as_inner_tag() {
436        #[derive(Serialize, Deserialize, Debug, PartialEq)]
437        struct OptionalField {
438            maybe: Option<i32>,
439        }
440
441        let tag = to_tag(&OptionalField { maybe: Some(42) }).unwrap();
442        let compound = match tag {
443            Tag::Compound(value) => value,
444            other => panic!("expected compound, got {other:?}"),
445        };
446        assert_eq!(compound.get("maybe"), Some(&Tag::Int(42)));
447
448        let decoded: OptionalField = from_tag(&Tag::Compound(compound)).unwrap();
449        assert_eq!(decoded, OptionalField { maybe: Some(42) });
450    }
451
452    #[test]
453    fn vec_u8_non_empty_encodes_as_byte_array() {
454        let tag = to_tag(&vec![1u8, 2, 3, 250]).unwrap();
455        assert_eq!(tag, Tag::ByteArray(vec![1, 2, 3, 250]));
456    }
457
458    #[test]
459    fn empty_vec_u8_without_wrapper_is_encoded_as_empty_list() {
460        let tag = to_tag(&Vec::<u8>::new()).unwrap();
461        assert_eq!(tag, Tag::List(ListTag::empty(TagType::End)));
462        let contract = std::hint::black_box(SERDE_BEHAVIOR_CONTRACT);
463        assert!(contract.empty_vec_u8_requires_wrapper);
464    }
465
466    #[test]
467    fn nbt_byte_array_wrapper_forces_empty_byte_array_semantics() {
468        #[derive(Serialize, Deserialize, Debug, PartialEq)]
469        struct WrappedBytes {
470            bytes: NbtByteArray,
471        }
472
473        let input = WrappedBytes {
474            bytes: NbtByteArray(Vec::new()),
475        };
476        let tag = to_tag(&input).unwrap();
477        let compound = match tag {
478            Tag::Compound(value) => value,
479            other => panic!("expected compound, got {other:?}"),
480        };
481        assert_eq!(compound.get("bytes"), Some(&Tag::ByteArray(Vec::new())));
482
483        let output: WrappedBytes = from_tag(&Tag::Compound(compound)).unwrap();
484        assert_eq!(output, input);
485    }
486
487    #[test]
488    fn byte_array_helper_roundtrip() {
489        let tag = to_byte_array_tag(vec![9u8, 8, 7]);
490        let bytes = from_byte_array_tag(&tag).unwrap();
491        assert_eq!(bytes, vec![9, 8, 7]);
492
493        let wrong = Tag::Int(1);
494        let err = from_byte_array_tag(&wrong).unwrap_err();
495        assert!(matches!(
496            err,
497            Error::UnexpectedType {
498                context: "byte_array_tag_decode",
499                expected_id,
500                actual_id
501            } if expected_id == TagType::ByteArray.id() && actual_id == TagType::Int.id()
502        ));
503    }
504
505    #[test]
506    fn contract_flags_are_expected() {
507        let contract = std::hint::black_box(SERDE_BEHAVIOR_CONTRACT);
508        assert!(contract.option_some_as_inner_tag);
509        assert!(contract.option_none_rejected);
510        assert!(contract.vec_u8_non_empty_as_byte_array);
511        assert!(contract.empty_vec_u8_requires_wrapper);
512    }
513
514    #[test]
515    fn byte_array_tag_decodes_to_vec_u8() {
516        #[derive(Deserialize, Debug, PartialEq)]
517        struct ByteVecHolder {
518            bytes: Vec<u8>,
519        }
520
521        let mut compound = IndexMap::new();
522        compound.insert("bytes".to_string(), Tag::ByteArray(vec![4, 5, 6]));
523        let decoded: ByteVecHolder = from_tag(&Tag::Compound(compound)).unwrap();
524        assert_eq!(
525            decoded,
526            ByteVecHolder {
527                bytes: vec![4, 5, 6]
528            }
529        );
530    }
531}