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_le_bytes<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
130    from_le_bytes_with_config(bytes, &NbtReadConfig::default())
131}
132
133pub fn from_net_bytes<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
134    from_net_bytes_with_config(bytes, &NbtReadConfig::default())
135}
136
137pub fn from_be_bytes_with_config<T: DeserializeOwned>(
138    bytes: &[u8],
139    config: &NbtReadConfig,
140) -> Result<T> {
141    let mut cursor = Cursor::new(bytes);
142    let root = read_tag_with_config::<BigEndian, _>(&mut cursor, config)?;
143    ensure_consumed(bytes.len(), cursor.position() as usize)?;
144    from_root_tag(&root)
145}
146
147pub fn from_le_bytes_with_config<T: DeserializeOwned>(
148    bytes: &[u8],
149    config: &NbtReadConfig,
150) -> Result<T> {
151    let mut cursor = Cursor::new(bytes);
152    let root = read_tag_with_config::<LittleEndian, _>(&mut cursor, config)?;
153    ensure_consumed(bytes.len(), cursor.position() as usize)?;
154    from_root_tag(&root)
155}
156
157pub fn from_net_bytes_with_config<T: DeserializeOwned>(
158    bytes: &[u8],
159    config: &NbtReadConfig,
160) -> Result<T> {
161    let mut cursor = Cursor::new(bytes);
162    let root = read_tag_with_config::<NetworkLittleEndian, _>(&mut cursor, config)?;
163    ensure_consumed(bytes.len(), cursor.position() as usize)?;
164    from_root_tag(&root)
165}
166
167fn ensure_consumed(total: usize, consumed: usize) -> Result<()> {
168    if consumed == total {
169        return Ok(());
170    }
171    Err(Error::TrailingPayloadBytes {
172        unread: total - consumed,
173    })
174}
175
176fn serde_error<E: std::fmt::Display>(error: E) -> Error {
177    Error::Serde {
178        message: error.to_string(),
179    }
180}
181
182fn serde_value_to_tag(value: SerdeValue) -> Result<Tag> {
183    match value {
184        SerdeValue::Bool(value) => Ok(Tag::Byte(if value { 1 } else { 0 })),
185        SerdeValue::I8(value) => Ok(Tag::Byte(value)),
186        SerdeValue::I16(value) => Ok(Tag::Short(value)),
187        SerdeValue::I32(value) => Ok(Tag::Int(value)),
188        SerdeValue::I64(value) => Ok(Tag::Long(value)),
189        SerdeValue::U8(value) => Ok(Tag::Short(value as i16)),
190        SerdeValue::U16(value) => i16::try_from(value)
191            .map(Tag::Short)
192            .or_else(|_| Ok(Tag::Int(i32::from(value)))),
193        SerdeValue::U32(value) => {
194            if let Ok(int) = i32::try_from(value) {
195                Ok(Tag::Int(int))
196            } else {
197                Ok(Tag::Long(i64::from(value)))
198            }
199        }
200        SerdeValue::U64(value) => {
201            let long = i64::try_from(value).map_err(|_| serde_error("u64 out of i64 range"))?;
202            Ok(Tag::Long(long))
203        }
204        SerdeValue::F32(value) => Ok(Tag::Float(value)),
205        SerdeValue::F64(value) => Ok(Tag::Double(value)),
206        SerdeValue::Char(value) => Ok(Tag::String(value.to_string())),
207        SerdeValue::String(value) => Ok(Tag::String(value)),
208        SerdeValue::Bytes(bytes) => Ok(Tag::ByteArray(bytes)),
209        SerdeValue::Seq(values) => serde_seq_to_tag(values),
210        SerdeValue::Map(values) => serde_map_to_compound(values).map(Tag::Compound),
211        SerdeValue::Option(None) => Err(serde_error("Option::None is not representable in NBT")),
212        SerdeValue::Option(Some(inner)) => serde_value_to_tag(*inner),
213        SerdeValue::Unit => Err(serde_error("unit values are not representable in NBT")),
214        SerdeValue::Newtype(inner) => serde_value_to_tag(*inner),
215    }
216}
217
218fn serde_seq_to_tag(values: Vec<SerdeValue>) -> Result<Tag> {
219    if values.is_empty() {
220        return Ok(Tag::List(ListTag::empty(TagType::End)));
221    }
222
223    if let Some(bytes) = try_u8_seq_to_byte_array(&values) {
224        return Ok(Tag::ByteArray(bytes));
225    }
226    if let Some(ints) = try_i32_seq_to_int_array(&values) {
227        return Ok(Tag::IntArray(ints));
228    }
229    if let Some(longs) = try_i64_seq_to_long_array(&values) {
230        return Ok(Tag::LongArray(longs));
231    }
232
233    let mut tags = Vec::with_capacity(values.len());
234    for value in values {
235        tags.push(serde_value_to_tag(value)?);
236    }
237    let element_type = tags.first().map(Tag::tag_type).unwrap_or(TagType::End);
238    Ok(Tag::List(ListTag::new(element_type, tags)?))
239}
240
241fn try_u8_seq_to_byte_array(values: &[SerdeValue]) -> Option<Vec<u8>> {
242    let mut out = Vec::with_capacity(values.len());
243    for value in values {
244        match value {
245            SerdeValue::U8(byte) => out.push(*byte),
246            _ => return None,
247        }
248    }
249    Some(out)
250}
251
252fn try_i32_seq_to_int_array(values: &[SerdeValue]) -> Option<Vec<i32>> {
253    let mut out = Vec::with_capacity(values.len());
254    for value in values {
255        match value {
256            SerdeValue::I32(int) => out.push(*int),
257            SerdeValue::U32(int) => out.push(i32::try_from(*int).ok()?),
258            _ => return None,
259        }
260    }
261    Some(out)
262}
263
264fn try_i64_seq_to_long_array(values: &[SerdeValue]) -> Option<Vec<i64>> {
265    let mut out = Vec::with_capacity(values.len());
266    for value in values {
267        match value {
268            SerdeValue::I64(long) => out.push(*long),
269            SerdeValue::U64(long) => out.push(i64::try_from(*long).ok()?),
270            _ => return None,
271        }
272    }
273    Some(out)
274}
275
276fn serde_map_to_compound(values: BTreeMap<SerdeValue, SerdeValue>) -> Result<CompoundTag> {
277    let mut out = CompoundTag::new();
278    for (key, value) in values {
279        let key = serde_key_to_string(key)?;
280        let value = serde_value_to_tag(value)?;
281        out.insert(key, value);
282    }
283    Ok(out)
284}
285
286fn serde_key_to_string(value: SerdeValue) -> Result<String> {
287    match value {
288        SerdeValue::String(value) => Ok(value),
289        SerdeValue::Char(value) => Ok(value.to_string()),
290        SerdeValue::Bool(value) => Ok(value.to_string()),
291        SerdeValue::I8(value) => Ok(value.to_string()),
292        SerdeValue::I16(value) => Ok(value.to_string()),
293        SerdeValue::I32(value) => Ok(value.to_string()),
294        SerdeValue::I64(value) => Ok(value.to_string()),
295        SerdeValue::U8(value) => Ok(value.to_string()),
296        SerdeValue::U16(value) => Ok(value.to_string()),
297        SerdeValue::U32(value) => Ok(value.to_string()),
298        SerdeValue::U64(value) => Ok(value.to_string()),
299        _ => Err(serde_error("map key must be string-like for NBT compound")),
300    }
301}
302
303fn tag_to_serde_value(tag: &Tag) -> Result<SerdeValue> {
304    match tag {
305        Tag::End => Err(serde_error(
306            "TAG_End is not representable as a typed serde value",
307        )),
308        Tag::Byte(value) => Ok(SerdeValue::I8(*value)),
309        Tag::Short(value) => Ok(SerdeValue::I16(*value)),
310        Tag::Int(value) => Ok(SerdeValue::I32(*value)),
311        Tag::Long(value) => Ok(SerdeValue::I64(*value)),
312        Tag::Float(value) => Ok(SerdeValue::F32(*value)),
313        Tag::Double(value) => Ok(SerdeValue::F64(*value)),
314        Tag::ByteArray(values) => Ok(SerdeValue::Seq(
315            values.iter().copied().map(SerdeValue::U8).collect(),
316        )),
317        Tag::String(value) => Ok(SerdeValue::String(value.clone())),
318        Tag::List(list) => {
319            let mut values = Vec::with_capacity(list.elements.len());
320            for element in &list.elements {
321                values.push(tag_to_serde_value(element)?);
322            }
323            Ok(SerdeValue::Seq(values))
324        }
325        Tag::Compound(values) => {
326            let mut map = BTreeMap::new();
327            for (key, value) in values {
328                map.insert(SerdeValue::String(key.clone()), tag_to_serde_value(value)?);
329            }
330            Ok(SerdeValue::Map(map))
331        }
332        Tag::IntArray(values) => Ok(SerdeValue::Seq(
333            values.iter().copied().map(SerdeValue::I32).collect(),
334        )),
335        Tag::LongArray(values) => Ok(SerdeValue::Seq(
336            values.iter().copied().map(SerdeValue::I64).collect(),
337        )),
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use indexmap::IndexMap;
344    use serde::{Deserialize, Serialize};
345
346    use super::*;
347
348    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349    struct DemoData {
350        name: String,
351        health: i32,
352        pitch: f32,
353        bytes: Vec<u8>,
354        scores: Vec<i32>,
355    }
356
357    fn sample() -> DemoData {
358        DemoData {
359            name: "Steve".to_string(),
360            health: 20,
361            pitch: 11.5,
362            bytes: vec![1, 2, 3, 250],
363            scores: vec![7, 11, 42],
364        }
365    }
366
367    #[test]
368    fn tag_roundtrip_typed() {
369        let input = sample();
370        let tag = to_tag(&input).unwrap();
371        let output: DemoData = from_tag(&tag).unwrap();
372        assert_eq!(output, input);
373    }
374
375    #[test]
376    fn be_bytes_roundtrip_typed() {
377        let input = sample();
378        let bytes = to_be_bytes(&input).unwrap();
379        let output: DemoData = from_be_bytes(&bytes).unwrap();
380        assert_eq!(output, input);
381    }
382
383    #[test]
384    fn le_bytes_roundtrip_typed() {
385        let input = sample();
386        let bytes = to_le_bytes_named("demo", &input).unwrap();
387        let output: DemoData = from_le_bytes(&bytes).unwrap();
388        assert_eq!(output, input);
389    }
390
391    #[test]
392    fn net_bytes_roundtrip_typed() {
393        let input = sample();
394        let bytes = to_net_bytes(&input).unwrap();
395        let output: DemoData = from_net_bytes(&bytes).unwrap();
396        assert_eq!(output, input);
397    }
398
399    #[test]
400    fn none_option_is_rejected() {
401        #[derive(Serialize)]
402        struct OptionalField {
403            maybe: Option<i32>,
404        }
405
406        let err = to_tag(&OptionalField { maybe: None }).unwrap_err();
407        assert!(matches!(err.innermost(), Error::Serde { .. }));
408    }
409
410    #[test]
411    fn some_option_is_serialized_as_inner_tag() {
412        #[derive(Serialize, Deserialize, Debug, PartialEq)]
413        struct OptionalField {
414            maybe: Option<i32>,
415        }
416
417        let tag = to_tag(&OptionalField { maybe: Some(42) }).unwrap();
418        let compound = match tag {
419            Tag::Compound(value) => value,
420            other => panic!("expected compound, got {other:?}"),
421        };
422        assert_eq!(compound.get("maybe"), Some(&Tag::Int(42)));
423
424        let decoded: OptionalField = from_tag(&Tag::Compound(compound)).unwrap();
425        assert_eq!(decoded, OptionalField { maybe: Some(42) });
426    }
427
428    #[test]
429    fn vec_u8_non_empty_encodes_as_byte_array() {
430        let tag = to_tag(&vec![1u8, 2, 3, 250]).unwrap();
431        assert_eq!(tag, Tag::ByteArray(vec![1, 2, 3, 250]));
432    }
433
434    #[test]
435    fn empty_vec_u8_without_wrapper_is_encoded_as_empty_list() {
436        let tag = to_tag(&Vec::<u8>::new()).unwrap();
437        assert_eq!(tag, Tag::List(ListTag::empty(TagType::End)));
438        let contract = std::hint::black_box(SERDE_BEHAVIOR_CONTRACT);
439        assert!(contract.empty_vec_u8_requires_wrapper);
440    }
441
442    #[test]
443    fn nbt_byte_array_wrapper_forces_empty_byte_array_semantics() {
444        #[derive(Serialize, Deserialize, Debug, PartialEq)]
445        struct WrappedBytes {
446            bytes: NbtByteArray,
447        }
448
449        let input = WrappedBytes {
450            bytes: NbtByteArray(Vec::new()),
451        };
452        let tag = to_tag(&input).unwrap();
453        let compound = match tag {
454            Tag::Compound(value) => value,
455            other => panic!("expected compound, got {other:?}"),
456        };
457        assert_eq!(compound.get("bytes"), Some(&Tag::ByteArray(Vec::new())));
458
459        let output: WrappedBytes = from_tag(&Tag::Compound(compound)).unwrap();
460        assert_eq!(output, input);
461    }
462
463    #[test]
464    fn byte_array_helper_roundtrip() {
465        let tag = to_byte_array_tag(vec![9u8, 8, 7]);
466        let bytes = from_byte_array_tag(&tag).unwrap();
467        assert_eq!(bytes, vec![9, 8, 7]);
468
469        let wrong = Tag::Int(1);
470        let err = from_byte_array_tag(&wrong).unwrap_err();
471        assert!(matches!(
472            err,
473            Error::UnexpectedType {
474                context: "byte_array_tag_decode",
475                expected_id,
476                actual_id
477            } if expected_id == TagType::ByteArray.id() && actual_id == TagType::Int.id()
478        ));
479    }
480
481    #[test]
482    fn contract_flags_are_expected() {
483        let contract = std::hint::black_box(SERDE_BEHAVIOR_CONTRACT);
484        assert!(contract.option_some_as_inner_tag);
485        assert!(contract.option_none_rejected);
486        assert!(contract.vec_u8_non_empty_as_byte_array);
487        assert!(contract.empty_vec_u8_requires_wrapper);
488    }
489
490    #[test]
491    fn byte_array_tag_decodes_to_vec_u8() {
492        #[derive(Deserialize, Debug, PartialEq)]
493        struct ByteVecHolder {
494            bytes: Vec<u8>,
495        }
496
497        let mut compound = IndexMap::new();
498        compound.insert("bytes".to_string(), Tag::ByteArray(vec![4, 5, 6]));
499        let decoded: ByteVecHolder = from_tag(&Tag::Compound(compound)).unwrap();
500        assert_eq!(
501            decoded,
502            ByteVecHolder {
503                bytes: vec![4, 5, 6]
504            }
505        );
506    }
507}