From 7d9209c2dd3e2c4235420dc84cf7c38cc93adfe0 Mon Sep 17 00:00:00 2001 From: norbiros Date: Sun, 1 Sep 2024 15:56:16 +0200 Subject: [PATCH] feat: Serde support --- .github/workflows/tests.yml | 4 +- Cargo.toml | 6 +- README.md | 41 +++- src/error.rs | 26 ++- src/lib.rs | 2 + src/nbt.rs | 2 +- src/serde/arrays.rs | 33 +++ src/serde/de.rs | 201 +++++++++++++++++++ src/serde/mod.rs | 3 + src/serde/ser.rs | 388 ++++++++++++++++++++++++++++++++++++ tests/serde.rs | 72 +++++++ 11 files changed, 766 insertions(+), 12 deletions(-) create mode 100644 src/serde/arrays.rs create mode 100644 src/serde/de.rs create mode 100644 src/serde/mod.rs create mode 100644 src/serde/ser.rs create mode 100644 tests/serde.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 24bbc11..ddbbaaf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,6 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run Clippy - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-features - name: Run tests - run: cargo test --verbose \ No newline at end of file + run: cargo test --all-features --verbose \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 4c5c32a..b310a1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,17 @@ readme = "README.md" license = "GPL-3.0-only" version = "0.1.2" edition = "2021" -exclude = ["tests"] +exclude = ["tests", ".github"] [features] -full = ["macro"] +full = ["macro", "serde"] default = ["macro"] macro = [] +serde = ["dep:serde"] [dependencies] bytes = "1.6.0" cesu8 = "1.1.0" derive_more = { version = "1.0.0-beta.6", features = ["into", "from"] } thiserror = "1.0.61" +serde = { version = "1.0.209", optional = true, features = ["derive"] } diff --git a/README.md b/README.md index de5682a..76535c7 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ Up-to-date Rust crate for easy and intuitive working with NBT data. ## Why not other libraries? -CrabNBT combines best features of existing NBT crates, to create perfect solution.
+CrabNBT combines best features of existing NBT crates, to create perfect, easy to use solution.
Big thanks to [simdnbt](https://github.com/azalea-rs/simdnbt) and [fastnbt](https://github.com/owengage/fastnbt) for ideas! ## Features -🚧 Support for serializing to/from Struct *(soon)*
+✅ Support for serializing to/from Struct (serde)
✅ [Java string](https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8) support
-✅ NBT! macro for easy creation
-✅ Good system of getting values from NBT
-✅ Serializing for single tags
-✅ Support of [Network NBT](https://wiki.vg/NBT#Network_NBT_(Java_Edition)) +✅ `nbt!` macro for easy creation
+✅ Easy to use system of retrieving values from NBT
+✅ Serialization support for individual tags
+✅ Support for [Network NBT](https://wiki.vg/NBT#Network_NBT_(Java_Edition)) ## Installing ```shell @@ -45,6 +45,9 @@ let nbt = Nbt::new( ]).into()) ]) ); + +let network_bytes = nbt.write_unnamed(); +let normal_bytes = nbt.write(); ``` ## Deserializing @@ -62,3 +65,29 @@ fn example(bytes: &mut Bytes) { .unwrap(); } ``` + +## Serde +*Requires `serde` feature.* + +```ignore +use crab_nbt::serde::arrays::IntArray; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, PartialEq, Debug)] +struct Test { + number: i32, + #[serde(with = "IntArray")] + int_array: Vec, +} + +fn cycle() { + let test = Test { + number: 5, + int_array: vec![7, 8] + }; + let bytes = to_bytes_unnamed(&test).unwrap(); + let recreated_struct: Test = from_bytes_unnamed(&mut expected).unwrap(); + + assert_eq!(test, recreated_struct); +} +``` \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index a7ef2ed..84202f1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,12 @@ +#[cfg(feature = "serde")] +use serde::{de, ser}; +#[cfg(feature = "serde")] +use std::fmt::Display; use thiserror::Error; -#[derive(Error, Copy, Clone, Debug)] +pub type Result = std::result::Result; + +#[derive(Error, Clone, Debug)] pub enum Error { #[error("The root tag of the NBT file is not a compound tag. Received tag id: {0}")] NoRootCompound(u8), @@ -8,4 +14,22 @@ pub enum Error { InvalidJavaString, #[error("Encountered an unknown NBT tag id {0}.")] UnknownTagId(u8), + #[error("Serde error: {0}")] + SerdeError(String), + #[error("NBT doesn't support this type {0}")] + UnsupportedType(String), +} + +#[cfg(feature = "serde")] +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::SerdeError(msg.to_string()) + } +} + +#[cfg(feature = "serde")] +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::SerdeError(msg.to_string()) + } } diff --git a/src/lib.rs b/src/lib.rs index 445f4b2..76bac0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ pub mod error; mod macros; mod nbt; +#[cfg(feature = "serde")] +pub mod serde; pub use crab_nbt::nbt::compound::NbtCompound; pub use crab_nbt::nbt::tag::NbtTag; diff --git a/src/nbt.rs b/src/nbt.rs index 9f0de71..b998536 100644 --- a/src/nbt.rs +++ b/src/nbt.rs @@ -7,7 +7,7 @@ use std::ops::Deref; pub mod compound; pub mod tag; -mod utils; +pub mod utils; /// Represents the main NBT structure. /// It contains the root compound tag of the NBT structure and its associated name diff --git a/src/serde/arrays.rs b/src/serde/arrays.rs new file mode 100644 index 0000000..bd7a414 --- /dev/null +++ b/src/serde/arrays.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Deserializer}; + +/// Crates structs, that allow for serializing fields as arrays (not lists) +/// ```ignore +/// #[serde(with = IntArray)] +/// ``` +macro_rules! impl_array { + ($name:ident, $variant:expr) => { + pub struct $name; + + impl $name { + pub fn serialize(input: T, serializer: S) -> Result + where + T: serde::Serialize, + S: serde::Serializer, + { + serializer.serialize_newtype_variant("nbt_array", 0, $variant, &input) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: Deserialize<'de>, + D: Deserializer<'de>, + { + T::deserialize(deserializer) + } + } + }; +} + +impl_array!(IntArray, "int"); +impl_array!(LongArray, "long"); +impl_array!(BytesArray, "byte"); diff --git a/src/serde/de.rs b/src/serde/de.rs new file mode 100644 index 0000000..bbaf2dd --- /dev/null +++ b/src/serde/de.rs @@ -0,0 +1,201 @@ +use crate::error::{Error, Result}; +use crate::nbt::utils::{ + BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, +}; +use crate::NbtTag; +use bytes::{Buf, BytesMut}; +use crab_nbt::nbt::utils::{get_nbt_string, END_ID}; +use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; +use serde::{forward_to_deserialize_any, Deserialize}; + +#[derive(Debug)] +pub struct Deserializer<'de> { + input: &'de mut BytesMut, + tag_to_deserialize: Option, + is_named: bool, +} + +impl<'de> Deserializer<'de> { + pub fn new(input: &'de mut BytesMut, is_named: bool) -> Self { + Deserializer { + input, + tag_to_deserialize: None, + is_named, + } + } +} + +/// Deserializes struct using Serde Deserializer from unnamed (network) NBT +pub fn from_bytes<'a, T>(s: &'a mut BytesMut) -> Result +where + T: Deserialize<'a>, +{ + let mut deserializer = Deserializer::new(s, true); + Ok(T::deserialize(&mut deserializer)?) +} + +/// Deserializes struct using Serde Deserializer from normal NBT +pub fn from_bytes_unnamed<'a, T>(s: &'a mut BytesMut) -> Result +where + T: Deserialize<'a>, +{ + let mut deserializer = Deserializer::new(s, false); + Ok(T::deserialize(&mut deserializer)?) +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { + type Error = Error; + + forward_to_deserialize_any!(i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 seq char str string bytes byte_buf tuple tuple_struct enum ignored_any unit unit_struct option newtype_struct); + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let tag_to_deserialize = self.tag_to_deserialize.unwrap(); + + let list_type = match tag_to_deserialize { + LIST_ID => Some(self.input.get_u8()), + INT_ARRAY_ID => Some(INT_ID), + LONG_ARRAY_ID => Some(LONG_ID), + BYTE_ARRAY_ID => Some(BYTE_ID), + _ => None, + }; + + if let Some(list_type) = list_type { + let remaining_values = self.input.get_u32(); + return visitor.visit_seq(ListAccess { + de: self, + list_type, + remaining_values, + }); + } + + let res: Result = Ok( + match NbtTag::deserialize_data(self.input, tag_to_deserialize)? { + NbtTag::End => { + unimplemented!("end") + } + NbtTag::Byte(value) => visitor.visit_i8(value)?, + NbtTag::Short(value) => visitor.visit_i16(value)?, + NbtTag::Int(value) => visitor.visit_i32(value)?, + NbtTag::Long(value) => visitor.visit_i64(value)?, + NbtTag::Float(value) => visitor.visit_f32(value)?, + NbtTag::Double(value) => visitor.visit_f64(value)?, + NbtTag::String(value) => visitor.visit_string(value)?, + _ => unreachable!(), + }, + ); + self.tag_to_deserialize = None; + res + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.tag_to_deserialize.unwrap() == BYTE_ID { + let value = self.input.get_u8(); + if value != 0 { + return visitor.visit_bool(true); + } + } + visitor.visit_bool(false) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.tag_to_deserialize.is_none() { + let next_byte = self.input.get_u8(); + if next_byte != COMPOUND_ID { + return Err(Error::NoRootCompound(next_byte)); + } + + if self.is_named { + // Consume struct name + NbtTag::deserialize(self.input)?; + } + } + + let value = visitor.visit_map(CompoundAccess { de: self })?; + Ok(value) + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let str = get_nbt_string(&mut self.input)?; + visitor.visit_string(str) + } + + fn is_human_readable(&self) -> bool { + false + } +} + +struct CompoundAccess<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, +} + +impl<'de, 'a> MapAccess<'de> for CompoundAccess<'a, 'de> { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result> + where + K: DeserializeSeed<'de>, + { + let tag = self.de.input.get_u8(); + self.de.tag_to_deserialize = Some(tag); + + if tag == END_ID { + return Ok(None); + } + + seed.deserialize(&mut *self.de).map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de>, + { + seed.deserialize(&mut *self.de) + } +} + +struct ListAccess<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + remaining_values: u32, + list_type: u8, +} + +impl<'a, 'de> SeqAccess<'de> for ListAccess<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.remaining_values == 0 { + return Ok(None); + } + + self.remaining_values -= 1; + self.de.tag_to_deserialize = Some(self.list_type); + seed.deserialize(&mut *self.de).map(Some) + } +} diff --git a/src/serde/mod.rs b/src/serde/mod.rs new file mode 100644 index 0000000..30fc65a --- /dev/null +++ b/src/serde/mod.rs @@ -0,0 +1,3 @@ +pub mod arrays; +pub mod de; +pub mod ser; diff --git a/src/serde/ser.rs b/src/serde/ser.rs new file mode 100644 index 0000000..16a150a --- /dev/null +++ b/src/serde/ser.rs @@ -0,0 +1,388 @@ +use crate::error::Error::UnsupportedType; +use crate::error::{Error, Result}; +use crate::nbt::utils::{ + BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, + LONG_ARRAY_ID, LONG_ID, SHORT_ID, STRING_ID, +}; +use crate::serde::ser::State::MapKey; +use crate::NbtTag; +use bytes::{BufMut, BytesMut}; +use crab_nbt::nbt::utils::END_ID; +use serde::ser::Impossible; +use serde::{ser, Serialize}; + +pub struct Serializer { + output: BytesMut, + state: State, +} + +// NBT has a different order of things, then most other formats +// So I use State, to keep what serializer has to do, and some information like field name +#[derive(Clone, Debug)] +enum State { + // In network NBT root name is not present + Root(Option), + Named(String), + // Used by maps, to check if key is String + MapKey, + FirstListElement { len: i32 }, + ListElement, + Array { name: String, array_type: String }, +} + +impl Serializer { + fn parse_state(&mut self, tag: u8) -> Result<()> { + Ok(match &mut self.state { + State::Named(name) | State::Array { name, .. } => { + self.output.put_u8(tag); + self.output + .put(NbtTag::String(name.clone()).serialize_data()); + } + State::FirstListElement { len } => { + self.output.put_u8(tag); + self.output.put_i32(*len); + } + MapKey => { + if tag != STRING_ID { + return Err(Error::SerdeError("Map key can only be string".to_string())); + } + } + State::ListElement => {} + _ => return Err(Error::SerdeError("Invalid Serializer state!".to_string())), + }) + } +} + +/// Serializes struct using Serde Serializer to unnamed (network) NBT +pub fn to_bytes_unnamed(value: &T) -> Result +where + T: Serialize, +{ + let mut serializer = Serializer { + output: BytesMut::new(), + state: State::Root(None), + }; + value.serialize(&mut serializer)?; + Ok(serializer.output) +} + +/// Serializes struct using Serde Serializer to normal NBT +pub fn to_bytes(value: &T, name: String) -> Result +where + T: Serialize, +{ + let mut serializer = Serializer { + output: BytesMut::new(), + state: State::Root(Some(name)), + }; + value.serialize(&mut serializer)?; + Ok(serializer.output) +} + +impl<'a> ser::Serializer for &'a mut Serializer { + type Ok = (); + type Error = Error; + + type SerializeSeq = Self; + type SerializeTuple = Impossible<(), Error>; + type SerializeTupleStruct = Impossible<(), Error>; + type SerializeTupleVariant = Impossible<(), Error>; + type SerializeMap = Self; + type SerializeStruct = Self; + type SerializeStructVariant = Impossible<(), Error>; + + // NBT doesn't have bool type, but it's most commonly represented as a byte + fn serialize_bool(self, v: bool) -> Result<()> { + self.serialize_i8(v as i8)?; + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result<()> { + self.parse_state(BYTE_ID)?; + self.output.put_i8(v); + Ok(()) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + self.parse_state(SHORT_ID)?; + self.output.put_i16(v); + Ok(()) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + self.parse_state(INT_ID)?; + self.output.put_i32(v); + Ok(()) + } + + fn serialize_i64(self, v: i64) -> Result<()> { + self.parse_state(LONG_ID)?; + self.output.put_i64(v); + Ok(()) + } + + fn serialize_u8(self, _v: u8) -> Result<()> { + Err(UnsupportedType("u8".to_string())) + } + + fn serialize_u16(self, _v: u16) -> Result<()> { + Err(UnsupportedType("u16".to_string())) + } + + fn serialize_u32(self, _v: u32) -> Result<()> { + Err(UnsupportedType("u32".to_string())) + } + + fn serialize_u64(self, _v: u64) -> Result<()> { + Err(UnsupportedType("u64".to_string())) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + self.parse_state(FLOAT_ID)?; + self.output.put_f32(v); + Ok(()) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + self.parse_state(DOUBLE_ID)?; + self.output.put_f64(v); + Ok(()) + } + + fn serialize_char(self, _v: char) -> Result<()> { + Err(UnsupportedType("char".to_string())) + } + + fn serialize_str(self, v: &str) -> Result<()> { + self.parse_state(STRING_ID)?; + self.output + .put(NbtTag::String(v.to_string()).serialize_data()); + Ok(()) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result<()> { + Err(UnsupportedType("bytes".to_string())) + } + + // Just skip serializing, if value is none + fn serialize_none(self) -> Result<()> { + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result<()> { + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + Err(UnsupportedType("unit struct".to_string())) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result<()> { + Err(UnsupportedType("unit variant".to_string())) + } + + fn serialize_newtype_struct(self, _name: &'static str, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(UnsupportedType("newtype struct".to_string())) + } + + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + if name != "nbt_array" { + return Err(Error::SerdeError( + "new_type variant supports only nbt_array".to_string(), + )); + } + + let name = match self.state { + State::Named(ref name) => name.clone(), + _ => return Err(Error::SerdeError("Invalid Serializer state!".to_string())), + }; + + self.state = State::Array { + name, + array_type: variant.to_string(), + }; + + value.serialize(self)?; + + Ok(()) + } + + fn serialize_seq(self, len: Option) -> Result { + if len.is_none() { + return Err(Error::SerdeError( + "Length of the sequence must be known first!".to_string(), + )); + } + + match &mut self.state { + State::Array { array_type, .. } => { + let id = match array_type.as_str() { + "byte" => BYTE_ARRAY_ID, + "int" => INT_ARRAY_ID, + "long" => LONG_ARRAY_ID, + _ => { + return Err(Error::SerdeError( + "Array supports only byte, int, long".to_string(), + )) + } + }; + self.parse_state(id)?; + self.output.put_i32(len.unwrap() as i32); + self.state = State::ListElement; + } + _ => { + self.parse_state(LIST_ID)?; + self.state = State::FirstListElement { + len: len.unwrap() as i32, + }; + } + } + + Ok(self) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(UnsupportedType("tuple".to_string())) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(UnsupportedType("tuple struct".to_string())) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(UnsupportedType("tuple variant".to_string())) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(self) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + self.output.put_u8(COMPOUND_ID); + + match &mut self.state { + State::Root(root_name) => { + if let Some(root_name) = root_name { + self.output + .put(NbtTag::String(root_name.clone()).serialize_data()); + } + } + State::Named(string) => { + self.output + .put(NbtTag::String(string.clone()).serialize_data()); + } + _ => { + unimplemented!() + } + } + + Ok(self) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(UnsupportedType("struct variant".to_string())) + } + + fn is_human_readable(&self) -> bool { + false + } +} + +impl<'a> ser::SerializeSeq for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(&mut **self)?; + self.state = State::ListElement; + Ok(()) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeStruct for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.state = State::Named(key.to_string()); + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + Ok(self.output.put_u8(END_ID)) + } +} + +impl<'a> ser::SerializeMap for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> std::result::Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.state = MapKey; + key.serialize(&mut **self) + } + + fn serialize_value(&mut self, value: &T) -> std::result::Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + Ok(self.output.put_u8(END_ID)) + } +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..4569f00 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,72 @@ +#![cfg(feature = "serde")] +use bytes::BytesMut; +use crab_nbt::serde::arrays::IntArray; +use crab_nbt::serde::de::from_bytes_unnamed; +use crab_nbt::serde::ser::to_bytes_unnamed; +use crab_nbt::{nbt, Nbt}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct Test { + str: String, + boolean: bool, + #[serde(with = "IntArray")] + array: Vec, + list: Vec, + sub: Inner, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct Inner { + int: i32, +} + +#[test] +fn test_serialize() { + let test = Test { + str: "hi ❤️".to_string(), + boolean: false, + array: vec![5, 6, 7], + list: vec![1, 2, 3], + sub: Inner { int: 5 }, + }; + let expected = nbt!("", { + "str": "hi ❤️", + "boolean": false, + "array": [I; 5, 6, 7], + "list": [1i16, 2i16, 3i16], + "sub": { + "int": 5 + } + }); + + let mut bytes = to_bytes_unnamed(&test).unwrap(); + let nbt = Nbt::read_unnamed(&mut bytes).unwrap(); + assert_eq!(nbt, expected); +} + +#[test] +fn test_deserialize() { + let mut test = BytesMut::from( + &nbt!("", { + "str": "hi ❤️", + "boolean": false, + "array": [I; 5, 6, 7], + "list": [1i16, 2i16, 3i16], + "sub": { + "int": 5 + } + }) + .write_unnamed()[..], + ); + let expected = Test { + str: "hi ❤️".to_string(), + boolean: false, + array: vec![5, 6, 7], + list: vec![1, 2, 3], + sub: Inner { int: 5 }, + }; + + let parsed: Test = from_bytes_unnamed(&mut test).unwrap(); + assert_eq!(expected, parsed); +}