From 7ffee6f4203ccdeb216bfb1b7bcf9163de375de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 24 Mar 2022 15:35:04 +0100 Subject: [PATCH 1/2] Add byte array type --- Cargo.toml | 2 +- src/bytearray.rs | 225 +++++++++++++++++++++++++++++++++++++++++++++++ src/de.rs | 22 ++++- src/lib.rs | 5 ++ src/ser.rs | 20 ++++- 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 src/bytearray.rs diff --git a/Cargo.toml b/Cargo.toml index f6d897b..edbf59c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" keywords = ["serde", "serialization", "no_std", "bytes"] license = "MIT OR Apache-2.0" repository = "https://github.com/serde-rs/bytes" -rust-version = "1.31" +rust-version = "1.53" [features] default = ["std"] diff --git a/src/bytearray.rs b/src/bytearray.rs new file mode 100644 index 0000000..516207b --- /dev/null +++ b/src/bytearray.rs @@ -0,0 +1,225 @@ +use crate::Bytes; +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::Ordering; +use core::convert::TryInto; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; + +use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +/// Wrapper around `[u8; N]` to serialize and deserialize efficiently. +/// +/// ``` +/// use std::collections::HashMap; +/// use std::io; +/// +/// use serde_bytes::ByteArray; +/// +/// fn deserialize_bytearrays() -> bincode::Result<()> { +/// let example_data = [ +/// 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 116, +/// 119, 111, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 111, 110, 101 +/// ]; +/// +/// let map: HashMap> = bincode::deserialize(&example_data[..])?; +/// +/// println!("{:?}", map); +/// +/// Ok(()) +/// } +/// # +/// # fn main() { +/// # deserialize_bytearrays().unwrap(); +/// # } +/// ``` +#[derive(Clone, Eq, Ord)] +pub struct ByteArray { + bytes: [u8; N], +} + +impl ByteArray { + /// Transform an [array](https://doc.rust-lang.org/stable/std/primitive.array.html) to the equivalent `ByteArray` + pub fn new(bytes: [u8; N]) -> Self { + Self { bytes } + } + + /// Wrap existing bytes into a `ByteArray` + pub fn from>(bytes: T) -> Self { + Self { + bytes: bytes.into(), + } + } + + /// Unwraps the byte array underlying this `ByteArray` + pub fn into_array(self) -> [u8; N] { + self.bytes + } +} + +impl Debug for ByteArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.bytes, f) + } +} + +impl AsRef<[u8; N]> for ByteArray { + fn as_ref(&self) -> &[u8; N] { + &self.bytes + } +} +impl AsMut<[u8; N]> for ByteArray { + fn as_mut(&mut self) -> &mut [u8; N] { + &mut self.bytes + } +} + +impl Borrow<[u8; N]> for ByteArray { + fn borrow(&self) -> &[u8; N] { + &self.bytes + } +} +impl BorrowMut<[u8; N]> for ByteArray { + fn borrow_mut(&mut self) -> &mut [u8; N] { + &mut self.bytes + } +} + +impl Deref for ByteArray { + type Target = [u8; N]; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl DerefMut for ByteArray { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + +impl Borrow for ByteArray { + fn borrow(&self) -> &Bytes { + Bytes::new(&self.bytes) + } +} + +impl BorrowMut for ByteArray { + fn borrow_mut(&mut self) -> &mut Bytes { + unsafe { &mut *(&mut self.bytes as &mut [u8] as *mut [u8] as *mut Bytes) } + } +} + +impl PartialEq for ByteArray +where + Rhs: ?Sized + Borrow<[u8; N]>, +{ + fn eq(&self, other: &Rhs) -> bool { + self.as_ref().eq(other.borrow()) + } +} + +impl PartialOrd for ByteArray +where + Rhs: ?Sized + Borrow<[u8; N]>, +{ + fn partial_cmp(&self, other: &Rhs) -> Option { + self.as_ref().partial_cmp(other.borrow()) + } +} + +impl Hash for ByteArray { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl IntoIterator for ByteArray { + type Item = u8; + type IntoIter = <[u8; N] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIterator::into_iter(self.bytes) + } +} + +impl<'a, const N: usize> IntoIterator for &'a ByteArray { + type Item = &'a u8; + type IntoIter = <&'a [u8; N] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter() + } +} + +impl<'a, const N: usize> IntoIterator for &'a mut ByteArray { + type Item = &'a mut u8; + type IntoIter = <&'a mut [u8; N] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter_mut() + } +} + +impl Serialize for ByteArray { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.bytes) + } +} + +struct ByteArrayVisitor; + +impl<'de, const N: usize> Visitor<'de> for ByteArrayVisitor { + type Value = ByteArray; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a byte array of length {}", N) + } + + fn visit_seq(self, mut seq: V) -> Result, V::Error> + where + V: SeqAccess<'de>, + { + let mut bytes = [0; N]; + + for (idx, byte) in bytes.iter_mut().enumerate() { + *byte = seq + .next_element()? + .ok_or_else(|| V::Error::invalid_length(idx, &self))?; + } + + Ok(ByteArray::from(bytes)) + } + + fn visit_bytes(self, v: &[u8]) -> Result, E> + where + E: Error, + { + Ok(ByteArray { + bytes: v + .try_into() + .map_err(|_| E::invalid_length(v.len(), &self))?, + }) + } + + fn visit_str(self, v: &str) -> Result, E> + where + E: Error, + { + self.visit_bytes(v.as_bytes()) + } +} + +impl<'de, const N: usize> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(ByteArrayVisitor::) + } +} diff --git a/src/de.rs b/src/de.rs index e4a4732..0280e3e 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,4 +1,4 @@ -use crate::Bytes; +use crate::{ByteArray, Bytes}; use core::fmt; use core::marker::PhantomData; use serde::de::{Error, Visitor}; @@ -63,6 +63,26 @@ impl<'de: 'a, 'a> Deserialize<'de> for &'a Bytes { } } +impl<'de, const N: usize> Deserialize<'de> for [u8; N] { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let arr: ByteArray = serde::Deserialize::deserialize(deserializer)?; + Ok(*arr) + } +} + +impl<'de, const N: usize> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Via the serde::Deserialize impl for ByteArray + serde::Deserialize::deserialize(deserializer) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl<'de> Deserialize<'de> for ByteBuf { fn deserialize(deserializer: D) -> Result diff --git a/src/lib.rs b/src/lib.rs index fe6076f..f4ba138 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,9 @@ //! //! #[serde(with = "serde_bytes")] //! byte_buf: Vec, +//! +//! #[serde(with = "serde_bytes")] +//! byte_array: [u8; 314], //! } //! ``` @@ -36,6 +39,7 @@ clippy::needless_doctest_main )] +mod bytearray; mod bytes; mod de; mod ser; @@ -51,6 +55,7 @@ use serde::Deserializer; use serde::Serializer; +pub use crate::bytearray::ByteArray; pub use crate::bytes::Bytes; pub use crate::de::Deserialize; pub use crate::ser::Serialize; diff --git a/src/ser.rs b/src/ser.rs index 1cbed9f..e093649 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,4 +1,4 @@ -use crate::Bytes; +use crate::{ByteArray, Bytes}; use serde::Serializer; #[cfg(any(feature = "std", feature = "alloc"))] @@ -51,6 +51,24 @@ impl Serialize for Bytes { } } +impl Serialize for [u8; N] { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +impl Serialize for ByteArray { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&**self) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl Serialize for ByteBuf { fn serialize(&self, serializer: S) -> Result From 95d9f8317353d6a102128994668a8a16a577e2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 24 Mar 2022 16:27:25 +0100 Subject: [PATCH 2/2] Add tests for ByteArray --- src/lib.rs | 6 ++++++ tests/test_derive.rs | 30 ++++++++++++++++++++++++++++-- tests/test_partialeq.rs | 10 +++++++++- tests/test_serde.rs | 18 +++++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4ba138..9dabfdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,9 @@ pub use crate::bytebuf::ByteBuf; /// /// #[serde(with = "serde_bytes")] /// byte_buf: Vec, +/// +/// #[serde(with = "serde_bytes")] +/// byte_array: [u8; 314], /// } /// ``` pub fn serialize(bytes: &T, serializer: S) -> Result @@ -106,6 +109,9 @@ where /// struct Packet { /// #[serde(with = "serde_bytes")] /// payload: Vec, +/// +/// #[serde(with = "serde_bytes")] +/// byte_array: [u8; 314], /// } /// ``` #[cfg(any(feature = "std", feature = "alloc"))] diff --git a/tests/test_derive.rs b/tests/test_derive.rs index c7e97f1..dd818e4 100644 --- a/tests/test_derive.rs +++ b/tests/test_derive.rs @@ -1,6 +1,6 @@ #![allow(clippy::derive_partial_eq_without_eq, clippy::ref_option_ref)] -use serde_bytes::{ByteBuf, Bytes}; +use serde_bytes::{ByteArray, ByteBuf, Bytes}; use serde_derive::{Deserialize, Serialize}; use serde_test::{assert_tokens, Token}; use std::borrow::Cow; @@ -10,12 +10,18 @@ struct Test<'a> { #[serde(with = "serde_bytes")] slice: &'a [u8], + #[serde(with = "serde_bytes")] + array: [u8; 314], + #[serde(with = "serde_bytes")] vec: Vec, #[serde(with = "serde_bytes")] bytes: &'a Bytes, + #[serde(with = "serde_bytes")] + byte_array: ByteArray<314>, + #[serde(with = "serde_bytes")] byte_buf: ByteBuf, @@ -37,6 +43,12 @@ struct Test<'a> { #[serde(with = "serde_bytes")] opt_vec: Option>, + #[serde(with = "serde_bytes")] + opt_array: Option<[u8; 314]>, + + #[serde(with = "serde_bytes")] + opt_bytearray: Option>, + #[serde(with = "serde_bytes")] opt_cow_slice: Option>, } @@ -51,8 +63,10 @@ struct Dst { fn test() { let test = Test { slice: b"...", + array: [0; 314], vec: b"...".to_vec(), bytes: Bytes::new(b"..."), + byte_array: ByteArray::new([0; 314]), byte_buf: ByteBuf::from(b"...".as_ref()), cow_slice: Cow::Borrowed(b"..."), cow_bytes: Cow::Borrowed(Bytes::new(b"...")), @@ -60,6 +74,8 @@ fn test() { boxed_bytes: ByteBuf::from(b"...".as_ref()).into_boxed_bytes(), opt_slice: Some(b"..."), opt_vec: Some(b"...".to_vec()), + opt_array: Some([0; 314]), + opt_bytearray: Some(ByteArray::new([0; 314])), opt_cow_slice: Some(Cow::Borrowed(b"...")), }; @@ -68,14 +84,18 @@ fn test() { &[ Token::Struct { name: "Test", - len: 11, + len: 15, }, Token::Str("slice"), Token::BorrowedBytes(b"..."), + Token::Str("array"), + Token::Bytes(&[0; 314]), Token::Str("vec"), Token::Bytes(b"..."), Token::Str("bytes"), Token::BorrowedBytes(b"..."), + Token::Str("byte_array"), + Token::Bytes(&[0; 314]), Token::Str("byte_buf"), Token::Bytes(b"..."), Token::Str("cow_slice"), @@ -92,6 +112,12 @@ fn test() { Token::Str("opt_vec"), Token::Some, Token::Bytes(b"..."), + Token::Str("opt_array"), + Token::Some, + Token::Bytes(&[0; 314]), + Token::Str("opt_bytearray"), + Token::Some, + Token::Bytes(&[0; 314]), Token::Str("opt_cow_slice"), Token::Some, Token::BorrowedBytes(b"..."), diff --git a/tests/test_partialeq.rs b/tests/test_partialeq.rs index a57d194..a0423cf 100644 --- a/tests/test_partialeq.rs +++ b/tests/test_partialeq.rs @@ -1,6 +1,6 @@ #![allow(clippy::needless_pass_by_value)] -use serde_bytes::{ByteBuf, Bytes}; +use serde_bytes::{ByteArray, ByteBuf, Bytes}; fn _bytes_eq_slice(bytes: &Bytes, slice: &[u8]) -> bool { bytes == slice @@ -13,3 +13,11 @@ fn _bytebuf_eq_vec(bytebuf: ByteBuf, vec: Vec) -> bool { fn _bytes_eq_bytestring(bytes: &Bytes) -> bool { bytes == b"..." } + +fn _bytearray_eq_bytestring(bytes: &ByteArray) -> bool { + bytes == &[0u8; N] +} + +fn _bytearray_eq_bytearray(bytes: &ByteArray, other: &ByteArray) -> bool { + bytes == other +} diff --git a/tests/test_serde.rs b/tests/test_serde.rs index d48e3fd..6b864b9 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -1,4 +1,4 @@ -use serde_bytes::{ByteBuf, Bytes}; +use serde_bytes::{ByteArray, ByteBuf, Bytes}; use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token}; #[test] @@ -57,3 +57,19 @@ fn test_byte_buf() { ], ); } + +#[test] +fn test_bytearray() { + let empty = ByteArray::new([]); + assert_tokens(&empty, &[Token::BorrowedBytes(b"")]); + assert_ser_tokens(&empty, &[Token::Bytes(b"")]); + assert_ser_tokens(&empty, &[Token::ByteBuf(b"")]); + assert_de_tokens(&empty, &[Token::BorrowedStr("")]); + + let buf = [65, 66, 67]; + let bytes = ByteArray::new(buf); + assert_tokens(&bytes, &[Token::BorrowedBytes(b"ABC")]); + assert_ser_tokens(&bytes, &[Token::Bytes(b"ABC")]); + assert_ser_tokens(&bytes, &[Token::ByteBuf(b"ABC")]); + assert_de_tokens(&bytes, &[Token::BorrowedStr("ABC")]); +}