diff --git a/src/lib.rs b/src/lib.rs index 4c8c278..da45d70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ pub use value::Value; pub use de::{from_gzip_reader, from_reader, from_zlib_reader}; #[cfg(feature = "serde")] #[doc(inline)] +pub use ser::{i32_array, i64_array, i8_array}; +#[cfg(feature = "serde")] +#[doc(inline)] pub use ser::{to_gzip_writer, to_writer, to_zlib_writer}; mod blob; diff --git a/src/macros.rs b/src/macros.rs index c470525..1e31e53 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -172,3 +172,39 @@ macro_rules! unrepresentable { $(return_expr_for_serialized_types_helper!{Err(Error::UnrepresentableType(stringify!($type))), $type})* }; } + +/// Serde `serialize_with` implementation for array serialization. +/// +/// This macro provides the function body for `i8_array`, `i32_array` and `i64_array` +/// in [`self::ser`], providing NBT `ByteArray`, `IntArray` and `LongArray` +/// serialization with serde. +macro_rules! array_serializer { + ($func_name:literal, $arr: ident, $serializer: ident) => {{ + use serde::ser::SerializeTupleStruct; + use std::borrow::Borrow; + + let error = concat!( + $func_name, + " serializer may only be used with known-length collections" + ); + let magic = concat!("__hematite_nbt_", $func_name, "__"); + + let mut iter = $arr.into_iter(); + let (length, max_length) = iter.size_hint(); + + if max_length.is_none() || length != max_length.unwrap() { + return Err(SerError::custom(error)); + } + + let mut seq = $serializer.serialize_tuple_struct(magic, length)?; + for _i in 0..length { + seq.serialize_field(iter.next().ok_or(SerError::custom(error))?.borrow())?; + } + + if iter.next().is_some() { + Err(SerError::custom(error)) + } else { + seq.end() + } + }}; +} diff --git a/src/ser.rs b/src/ser.rs index 1e3192f..9a4d514 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -10,6 +10,7 @@ use serde::ser; use raw; use error::{Error, Result}; +use serde::ser::Error as SerError; /// Encode `value` in Named Binary Tag format to the given `io::Write` /// destination, with an optional header. @@ -109,11 +110,15 @@ where } } - fn for_seq(outer: &'a mut Encoder<'b, W>, length: i32) -> Result { - // For an empty list, write TAG_End as the tag type. - if length == 0 { - raw::write_bare_byte(&mut outer.writer, 0x00)?; - raw::write_bare_int(&mut outer.writer, 0)?; + fn for_seq(outer: &'a mut Encoder<'b, W>, length: i32, array: bool) -> Result { + if length == 0 || array { + // Write sigil for empty list or typed array, because SerializeSeq::serialize_element is never called + if !array { + // For an empty list, write TAG_End as the tag type. + raw::write_bare_byte(&mut outer.writer, 0x00)?; + } + // Write list/array length + raw::write_bare_int(&mut outer.writer, length)?; } Ok(Compound { outer, @@ -150,6 +155,25 @@ where } } +impl<'a, 'b, W> ser::SerializeTupleStruct for Compound<'a, 'b, W> +where + W: io::Write, +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: serde::Serialize, + { + value.serialize(&mut InnerEncoder::from_outer(self.outer)) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + impl<'a, 'b, W> ser::SerializeStruct for Compound<'a, 'b, W> where W: io::Write, @@ -268,14 +292,14 @@ where type Error = Error; type SerializeSeq = Compound<'a, 'b, W>; type SerializeTuple = ser::Impossible<(), Error>; - type SerializeTupleStruct = ser::Impossible<(), Error>; + type SerializeTupleStruct = Compound<'a, 'b, W>; type SerializeTupleVariant = ser::Impossible<(), Error>; type SerializeMap = Compound<'a, 'b, W>; type SerializeStruct = Compound<'a, 'b, W>; type SerializeStructVariant = ser::Impossible<(), Error>; unrepresentable!( - u8 u16 u32 u64 char unit unit_variant newtype_variant tuple tuple_struct + u8 u16 u32 u64 char unit unit_variant newtype_variant tuple tuple_variant struct_variant ); @@ -353,7 +377,7 @@ where #[inline] fn serialize_seq(self, len: Option) -> Result { if let Some(l) = len { - Compound::for_seq(self.outer, l as i32) + Compound::for_seq(self.outer, l as i32, false) } else { Err(Error::UnrepresentableType("unsized list")) } @@ -368,6 +392,19 @@ where fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { Ok(Compound::from_outer(self.outer)) } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + match name { + "__hematite_nbt_i8_array__" + | "__hematite_nbt_i32_array__" + | "__hematite_nbt_i64_array__" => Compound::for_seq(self.outer, len as i32, true), + _ => Err(Error::UnrepresentableType(stringify!(tuple_struct))), + } + } } /// A serializer for valid map keys, i.e. strings. @@ -452,14 +489,14 @@ where type Error = Error; type SerializeSeq = NoOp; type SerializeTuple = ser::Impossible<(), Error>; - type SerializeTupleStruct = ser::Impossible<(), Error>; + type SerializeTupleStruct = NoOp; type SerializeTupleVariant = ser::Impossible<(), Error>; type SerializeMap = NoOp; type SerializeStruct = NoOp; type SerializeStructVariant = ser::Impossible<(), Error>; unrepresentable!( - u8 u16 u32 u64 char unit unit_variant newtype_variant tuple tuple_struct + u8 u16 u32 u64 char unit unit_variant newtype_variant tuple tuple_variant struct_variant ); @@ -555,6 +592,21 @@ where self.write_header(0x0a)?; Ok(NoOp) } + + fn serialize_tuple_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + match name { + "__hematite_nbt_i8_array__" => self.write_header(0x07)?, + "__hematite_nbt_i32_array__" => self.write_header(0x0b)?, + "__hematite_nbt_i64_array__" => self.write_header(0x0c)?, + _ => return Err(Error::UnrepresentableType("tuple struct")), + } + + Ok(NoOp) + } } /// This empty serializer provides a way to serialize only headers/tags for @@ -577,6 +629,22 @@ impl ser::SerializeSeq for NoOp { } } +impl ser::SerializeTupleStruct for NoOp { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _value: &T) -> Result<()> + where + T: serde::Serialize, + { + Ok(()) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + impl ser::SerializeStruct for NoOp { type Ok = (); type Error = Error; @@ -615,3 +683,123 @@ impl ser::SerializeMap for NoOp { Ok(()) } } + +/// This function provides serde serialization support for NBT type `ByteArray`. +/// +/// It should be used in conjunction with serde's field annotation `serialize_with`. +/// In the following example `byte_data` will be serialized as a `ByteArray` +/// instead of a `List` of `Byte`s: +/// +/// ``` +/// extern crate serde; +/// use nbt::to_writer; +/// use serde::Serialize; +/// +/// let mut serialized = Vec::new(); +/// +/// // Declare your struct +/// #[derive(Serialize)] +/// struct Sheep { +/// #[serde(serialize_with="nbt::i8_array")] +/// byte_data: Vec, +/// } +/// +/// // Serialize to NBT! +/// to_writer( +/// &mut serialized, +/// &Sheep { +/// byte_data: vec![0x62, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x20, 0x73, 0x68, 0x65, 0x65, 0x70], +/// }, +/// None +/// ).unwrap(); +/// +/// print!("Serialized: {:?}", serialized); +/// ``` +pub fn i8_array<'a, T, S>(array: &'a T, serializer: S) -> std::result::Result +where + &'a T: IntoIterator, + <&'a T as IntoIterator>::Item: std::borrow::Borrow, + S: serde::ser::Serializer, +{ + array_serializer!("i8_array", array, serializer) +} + +/// This function provides serde serialization support for NBT type `IntArray`. +/// +/// It should be used in conjunction with serde's field annotation `serialize_with`. +/// In the following example `int_data` will be serialized as an `IntArray` +/// instead of a `List` of `Int`s: +/// +/// ``` +/// extern crate serde; +/// use nbt::to_writer; +/// use serde::Serialize; +/// +/// let mut serialized = Vec::new(); +/// +/// // Declare your struct +/// #[derive(Serialize)] +/// struct Cow { +/// #[serde(serialize_with="nbt::i32_array")] +/// int_data: Vec, +/// } +/// +/// // Serialize to NBT! +/// to_writer( +/// &mut serialized, +/// &Cow { +/// int_data: vec![1, 8, 64, 512, 4096, 32768, 262144], +/// }, +/// None +/// ).unwrap(); +/// +/// print!("Serialized: {:?}", serialized); +/// ``` +pub fn i32_array<'a, T, S>(array: &'a T, serializer: S) -> std::result::Result +where + &'a T: IntoIterator, + <&'a T as IntoIterator>::Item: std::borrow::Borrow, + S: serde::ser::Serializer, +{ + array_serializer!("i32_array", array, serializer) +} + +/// This function provides serde serialization support for NBT type `LongArray`. +/// +/// It should be used in conjunction with serde's field annotation `serialize_with`. +/// In the following example `int_data` will be serialized as a `LongArray` +/// instead of a `List` of `Int`s: +/// +/// ``` +/// extern crate serde; +/// use nbt::to_writer; +/// use serde::Serialize; +/// +/// let mut serialized = Vec::new(); +/// +/// // Declare your struct +/// #[derive(Serialize)] +/// struct Enderman { +/// #[serde(serialize_with="nbt::i64_array")] +/// long_data: Vec, +/// } +/// +/// // Serialize to NBT! +/// to_writer( +/// &mut serialized, +/// &Enderman { +/// long_data: vec![0x1848ccd2157df10e, 0x64c5efff28280e9a], +/// }, +/// None +/// ).unwrap(); +/// +/// print!("Serialized: {:?}", serialized); +/// ``` +pub fn i64_array<'a, T, S>(array: &'a T, serializer: S) -> std::result::Result +where + &'a T: IntoIterator, + <&'a T as IntoIterator>::Item: std::borrow::Borrow, + S: serde::ser::Serializer, +{ + array_serializer!("i64_array", array, serializer) +} diff --git a/tests/serde_basics.rs b/tests/serde_basics.rs index f884eeb..7f02f5b 100644 --- a/tests/serde_basics.rs +++ b/tests/serde_basics.rs @@ -6,7 +6,7 @@ extern crate nbt; use std::collections::HashMap; -use nbt::de::from_reader; +use serde::Serializer; /// Helper function that asserts data of type T can be serialized into and /// deserialized from `bytes`. `name` is an optional header for the top-level @@ -174,11 +174,23 @@ fn roundtrip_nested_list() { #[derive(Debug, PartialEq, Serialize, Deserialize)] struct NestedArrayNbt { + #[serde(serialize_with = "nested_i32_array")] data: Vec>, } +fn nested_i32_array(outer_arr: &Vec>, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Debug, Serialize)] + struct Wrapper(#[serde(serialize_with = "nbt::i32_array")] Vec); + + // clone should be optimized to a no-op + serializer.collect_seq(outer_arr.iter().map(|vec| Wrapper(vec.clone()))) +} + #[test] -fn deserialize_nested_array() { +fn roundtrip_nested_array() { let nbt = NestedArrayNbt { data: vec![vec![1, 2], vec![3, 4]], }; @@ -203,13 +215,18 @@ fn deserialize_nested_array() { 0x00 ]; - let read: NestedArrayNbt = from_reader(&bytes[..]).unwrap(); - assert_eq!(read, nbt) + assert_roundtrip_eq(nbt, &bytes, None); +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct ByteArrayNbt { + #[serde(serialize_with = "nbt::i8_array")] + data: Vec, } #[test] -fn deserialize_byte_array() { - let nbt = BasicListNbt { +fn roundtrip_byte_array() { + let nbt = ByteArrayNbt { data: vec![1, 2, 3], }; @@ -225,18 +242,18 @@ fn deserialize_byte_array() { 0x00 ]; - let read: BasicListNbt = from_reader(&bytes[..]).unwrap(); - assert_eq!(read, nbt) + assert_roundtrip_eq(nbt, &bytes, None); } #[derive(Debug, PartialEq, Serialize, Deserialize)] -struct IntListNbt { +struct IntArrayNbt { + #[serde(serialize_with = "nbt::i32_array")] data: Vec, } #[test] -fn deserialize_empty_array() { - let nbt = IntListNbt { data: vec![] }; +fn roundtrip_empty_array() { + let nbt = IntArrayNbt { data: vec![] }; #[rustfmt::skip] let bytes = vec![ @@ -249,13 +266,12 @@ fn deserialize_empty_array() { 0x00 ]; - let read: IntListNbt = from_reader(&bytes[..]).unwrap(); - assert_eq!(read, nbt) + assert_roundtrip_eq(nbt, &bytes, None); } #[test] -fn deserialize_int_array() { - let nbt = IntListNbt { +fn roundtrip_int_array() { + let nbt = IntArrayNbt { data: vec![1, 2, 3], }; @@ -274,18 +290,18 @@ fn deserialize_int_array() { 0x00 ]; - let read: IntListNbt = from_reader(&bytes[..]).unwrap(); - assert_eq!(read, nbt) + assert_roundtrip_eq(nbt, &bytes, None); } #[derive(Debug, PartialEq, Serialize, Deserialize)] -struct LongListNbt { +struct LongArrayNbt { + #[serde(serialize_with = "nbt::ser::i64_array")] data: Vec, } #[test] -fn deserialize_long_array() { - let nbt = LongListNbt { +fn roundtrip_long_array() { + let nbt = LongArrayNbt { data: vec![1, 2, 3], }; @@ -304,8 +320,7 @@ fn deserialize_long_array() { 0x00 ]; - let read: LongListNbt = from_reader(&bytes[..]).unwrap(); - assert_eq!(read, nbt) + assert_roundtrip_eq(nbt, &bytes, None); } #[derive(Debug, PartialEq, Serialize, Deserialize)]