From d70652bc55656488cf8b578c3061a05398e9b4ff Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:07:32 +1000 Subject: [PATCH 1/9] Add heapless::Vec wrapper --- Cargo.toml | 2 + src/heapless_bytebuf.rs | 282 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 + 3 files changed, 290 insertions(+) create mode 100644 src/heapless_bytebuf.rs diff --git a/Cargo.toml b/Cargo.toml index c1b7673..9942dfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,11 @@ rust-version = "1.68" default = ["std"] std = ["serde/std"] alloc = ["serde/alloc"] +heapless = ["dep:heapless", "heapless/serde"] [dependencies] serde = { version = "1.0.166", default-features = false } +heapless = { version = "0.8", default-features = false, optional = true } [dev-dependencies] bincode = { version = "2", features = ["serde"] } diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs new file mode 100644 index 0000000..c3434cf --- /dev/null +++ b/src/heapless_bytebuf.rs @@ -0,0 +1,282 @@ +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::Ordering; +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; +use core::convert::TryFrom; + +use heapless::Vec; + +use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +use crate::Bytes; + +/// Wrapper around `heapless::Vec` to serialize and deserialize efficiently. +/// +/// ``` +/// use std::collections::HashMap; +/// use std::io; +/// +/// use serde_bytes::HeaplessByteBuf; +/// +/// fn deserialize_bytebufs() -> Result<(), bincode::error::DecodeError> { +/// let example_data = [2, 2, 3, 116, 119, 111, 1, 3, 111, 110, 101]; +/// +/// let map: HashMap>; +/// (map, _) = bincode::serde::decode_from_slice( +/// &example_data, +/// bincode::config::standard(), +/// )?; +/// +/// println!("{:?}", map); +/// +/// Ok(()) +/// } +/// # +/// # fn main() { +/// # deserialize_bytebufs().unwrap(); +/// # } +/// ``` +#[derive(Clone, Default, Eq, Ord)] +pub struct HeaplessByteBuf { + bytes: Vec, +} + +impl HeaplessByteBuf { + /// Construct a `HeaplessByteBuf`. + pub fn new() -> Self { + HeaplessByteBuf { + bytes: Vec::new(), + } + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + pub fn from>>(bytes: T) -> Self { + HeaplessByteBuf { + bytes: bytes.into(), + } + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + pub fn try_from_slice(bytes: &[u8]) -> Result> { + let bytes = Vec::from_slice(bytes) + .map_err(|_| HeaplessByteBufFullError:: )?; + + Ok(HeaplessByteBuf { + bytes, + }) + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + #[cfg(feature = "alloc")] + pub fn try_from>>(vec: T) -> Result> { + let std_vec = vec.into(); + let bytes = Vec::from_slice(&std_vec) + .map_err(|_| HeaplessByteBufFullError:: )?; + + Ok(HeaplessByteBuf { + bytes, + }) + } + + /// Unwrap the vector of byte underlying this `HeaplessByteBuf`. + pub fn into_inner(self) -> Vec { + self.bytes + } + + #[doc(hidden)] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> as IntoIterator>::IntoIter { + self.bytes.into_iter() + } +} + +#[derive(Debug)] +pub struct HeaplessByteBufFullError; + +impl Display for HeaplessByteBufFullError { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + write!(f, "heapless::Vec is too small") + } +} + +impl core::error::Error for HeaplessByteBufFullError {} + +impl Debug for HeaplessByteBuf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.bytes, f) + } +} + +impl AsRef<[u8]> for HeaplessByteBuf { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl AsMut<[u8]> for HeaplessByteBuf { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +impl Deref for HeaplessByteBuf { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl DerefMut for HeaplessByteBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + +impl Borrow for HeaplessByteBuf { + fn borrow(&self) -> &Bytes { + Bytes::new(&self.bytes) + } +} + +impl BorrowMut for HeaplessByteBuf { + fn borrow_mut(&mut self) -> &mut Bytes { + unsafe { &mut *(&mut self.bytes as &mut [u8] as *mut [u8] as *mut Bytes) } + } +} + + +#[cfg(feature = "alloc")] +impl TryFrom> for HeaplessByteBuf { + type Error = HeaplessByteBufFullError; + + fn try_from(bytes: alloc::vec::Vec) -> Result { + HeaplessByteBuf::::try_from(bytes) + } +} + +impl PartialEq for HeaplessByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn eq(&self, other: &Rhs) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl PartialOrd for HeaplessByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn partial_cmp(&self, other: &Rhs) -> Option { + self.as_ref().partial_cmp(other.as_ref()) + } +} + +impl Hash for HeaplessByteBuf { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl IntoIterator for HeaplessByteBuf { + type Item = u8; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.into_iter() + } +} + +impl<'a, const N: usize> IntoIterator for &'a HeaplessByteBuf { + type Item = &'a u8; + type IntoIter = <&'a [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter() + } +} + +impl<'a, const N: usize> IntoIterator for &'a mut HeaplessByteBuf { + type Item = &'a mut u8; + type IntoIter = <&'a mut [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter_mut() + } +} + +impl Serialize for HeaplessByteBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.bytes) + } +} + +struct HeaplessByteBufVisitor; + +impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { + type Value = HeaplessByteBuf; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("byte array") + } + + fn visit_seq(self, mut visitor: V) -> Result, V::Error> + where + V: SeqAccess<'de>, + { + let mut bytes = Vec::::new(); + + while let Some(b) = visitor.next_element()? { + let result = bytes.push(b); + + // Push can only fail if the sequence is too large for the Vec + if result.is_err() { + let expected: &str = &format!("{N}"); + return Err(V::Error::invalid_length(N + 1, &expected)); + } + } + + Ok(HeaplessByteBuf::from(bytes)) + } + + fn visit_bytes(self, v: &[u8]) -> Result, E> + where + E: Error, + { + HeaplessByteBuf::try_from_slice(v) + .map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(v.len(), &expected) + }) + } + + #[cfg(feature = "alloc")] + fn visit_byte_buf(self, v: alloc::vec::Vec) -> Result, E> + where + E: Error, + { + let len = v.len(); + HeaplessByteBuf::try_from(v) + .map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(len, &expected) + }) + } + + // TODO: &str +} + +impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_byte_buf(HeaplessByteBufVisitor::) + } +} diff --git a/src/lib.rs b/src/lib.rs index b6f6655..5bd03ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,9 @@ mod ser; #[cfg(any(feature = "std", feature = "alloc"))] mod bytebuf; +#[cfg(feature = "heapless")] +mod heapless_bytebuf; + #[cfg(feature = "alloc")] extern crate alloc; @@ -64,6 +67,9 @@ pub use crate::ser::Serialize; #[cfg(any(feature = "std", feature = "alloc"))] pub use crate::bytebuf::ByteBuf; +#[cfg(feature = "heapless")] +pub use crate::heapless_bytebuf::HeaplessByteBuf; + /// Serde `serialize_with` function to serialize bytes efficiently. /// /// This function can be used with either of the following Serde attributes: From 7f480ea464a715e0f29a93a29cf00a09d650727f Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:15:58 +1000 Subject: [PATCH 2/9] Add Serialize/Deserialize impls for heapless::Vec --- src/de.rs | 24 ++++++++++++++++++++++++ src/heapless_bytebuf.rs | 1 - src/ser.rs | 23 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/de.rs b/src/de.rs index 27c35e6..e92710b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,6 +7,9 @@ use serde::Deserializer; #[cfg(any(feature = "std", feature = "alloc"))] use crate::ByteBuf; +#[cfg(feature = "heapless")] +use crate::HeaplessByteBuf; + #[cfg(any(feature = "std", feature = "alloc"))] use core::cmp; @@ -53,6 +56,16 @@ impl<'de> Deserialize<'de> for Vec { } } +#[cfg(feature = "heapless")] +impl<'de, const N: usize> Deserialize<'de> for heapless::Vec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(HeaplessByteBuf::into_inner) + } +} + impl<'de: 'a, 'a> Deserialize<'de> for &'a Bytes { fn deserialize(deserializer: D) -> Result where @@ -114,6 +127,17 @@ impl<'de> Deserialize<'de> for ByteBuf { } } +#[cfg(feature = "heapless")] +impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Via the serde::Deserialize impl for ByteBuf. + serde::Deserialize::deserialize(deserializer) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl<'de: 'a, 'a> Deserialize<'de> for Cow<'a, [u8]> { fn deserialize(deserializer: D) -> Result diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs index c3434cf..da2e134 100644 --- a/src/heapless_bytebuf.rs +++ b/src/heapless_bytebuf.rs @@ -147,7 +147,6 @@ impl BorrowMut for HeaplessByteBuf { } } - #[cfg(feature = "alloc")] impl TryFrom> for HeaplessByteBuf { type Error = HeaplessByteBufFullError; diff --git a/src/ser.rs b/src/ser.rs index 37889a8..8dce8df 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -4,6 +4,9 @@ use serde::Serializer; #[cfg(any(feature = "std", feature = "alloc"))] use crate::ByteBuf; +#[cfg(feature = "heapless")] +use crate::HeaplessByteBuf; + #[cfg(feature = "alloc")] use alloc::borrow::Cow; #[cfg(all(feature = "std", not(feature = "alloc")))] @@ -69,6 +72,16 @@ impl Serialize for ByteArray { } } +#[cfg(feature = "heapless")] +impl Serialize for heapless::Vec { + 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 @@ -79,6 +92,16 @@ impl Serialize for ByteBuf { } } +#[cfg(feature = "heapless")] +impl Serialize for HeaplessByteBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl<'a> Serialize for Cow<'a, [u8]> { fn serialize(&self, serializer: S) -> Result From fc1601b1ed9011743011f3450f62c626e506aa74 Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:16:15 +1000 Subject: [PATCH 3/9] Format --- src/heapless_bytebuf.rs | 42 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs index da2e134..650c6b0 100644 --- a/src/heapless_bytebuf.rs +++ b/src/heapless_bytebuf.rs @@ -1,9 +1,9 @@ use core::borrow::{Borrow, BorrowMut}; use core::cmp::Ordering; +use core::convert::TryFrom; use core::fmt::{self, Debug, Display, Formatter}; use core::hash::{Hash, Hasher}; use core::ops::{Deref, DerefMut}; -use core::convert::TryFrom; use heapless::Vec; @@ -46,9 +46,7 @@ pub struct HeaplessByteBuf { impl HeaplessByteBuf { /// Construct a `HeaplessByteBuf`. pub fn new() -> Self { - HeaplessByteBuf { - bytes: Vec::new(), - } + HeaplessByteBuf { bytes: Vec::new() } } /// Wrap existing bytes in a `HeaplessByteBuf`. @@ -60,24 +58,20 @@ impl HeaplessByteBuf { /// Wrap existing bytes in a `HeaplessByteBuf`. pub fn try_from_slice(bytes: &[u8]) -> Result> { - let bytes = Vec::from_slice(bytes) - .map_err(|_| HeaplessByteBufFullError:: )?; + let bytes = Vec::from_slice(bytes).map_err(|_| HeaplessByteBufFullError::)?; - Ok(HeaplessByteBuf { - bytes, - }) + Ok(HeaplessByteBuf { bytes }) } /// Wrap existing bytes in a `HeaplessByteBuf`. #[cfg(feature = "alloc")] - pub fn try_from>>(vec: T) -> Result> { + pub fn try_from>>( + vec: T, + ) -> Result> { let std_vec = vec.into(); - let bytes = Vec::from_slice(&std_vec) - .map_err(|_| HeaplessByteBufFullError:: )?; + let bytes = Vec::from_slice(&std_vec).map_err(|_| HeaplessByteBufFullError::)?; - Ok(HeaplessByteBuf { - bytes, - }) + Ok(HeaplessByteBuf { bytes }) } /// Unwrap the vector of byte underlying this `HeaplessByteBuf`. @@ -248,11 +242,10 @@ impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { where E: Error, { - HeaplessByteBuf::try_from_slice(v) - .map_err(|_| { - let expected: &str = &format!("{N}"); - Error::invalid_length(v.len(), &expected) - }) + HeaplessByteBuf::try_from_slice(v).map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(v.len(), &expected) + }) } #[cfg(feature = "alloc")] @@ -261,11 +254,10 @@ impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { E: Error, { let len = v.len(); - HeaplessByteBuf::try_from(v) - .map_err(|_| { - let expected: &str = &format!("{N}"); - Error::invalid_length(len, &expected) - }) + HeaplessByteBuf::try_from(v).map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(len, &expected) + }) } // TODO: &str From 5a4bc67ffd4636a2b8ce6e04d6b2d2366d89f567 Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:28:18 +1000 Subject: [PATCH 4/9] Update docs w/ examples --- src/heapless_bytebuf.rs | 2 -- src/lib.rs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs index 650c6b0..b73b799 100644 --- a/src/heapless_bytebuf.rs +++ b/src/heapless_bytebuf.rs @@ -259,8 +259,6 @@ impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { Error::invalid_length(len, &expected) }) } - - // TODO: &str } impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { diff --git a/src/lib.rs b/src/lib.rs index 5bd03ab..0fe2e47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,12 @@ //! efficient handling of `&[u8]` and `Vec` in structs without needing a //! wrapper type. //! +//! This approach also works with `heapless::Vec`, which is useful +//! where the size of data has a maxmimum bound but isn't an exact size. Use +//! the `heapless` feature to enable support. You can also opt into special +//! handling in this case by wrapping the `heapless::Vec` with +//! `serde_bytes::HeaplessByteBuf`. +//! //! ``` //! # use serde_derive::{Deserialize, Serialize}; //! use serde::{Deserialize, Serialize}; @@ -27,6 +33,10 @@ //! //! #[serde(with = "serde_bytes")] //! byte_array: [u8; 314], +//! +//! #[cfg(feature = "heapless")] +//! #[serde(with = "serde_bytes")] +//! heapless_byte_buf: heapless::Vec, //! } //! ``` @@ -91,6 +101,10 @@ pub use crate::heapless_bytebuf::HeaplessByteBuf; /// /// #[serde(with = "serde_bytes")] /// byte_array: [u8; 314], +/// +/// #[cfg(feature = "heapless")] +/// #[serde(with = "serde_bytes")] +/// heapless_byte_buf: heapless::Vec, /// } /// ``` pub fn serialize(bytes: &T, serializer: S) -> Result @@ -119,6 +133,10 @@ where /// /// #[serde(with = "serde_bytes")] /// byte_array: [u8; 314], +/// +/// #[cfg(feature = "heapless")] +/// #[serde(with = "serde_bytes")] +/// heapless_payload: heapless::Vec, /// } /// ``` pub fn deserialize<'de, T, D>(deserializer: D) -> Result From 9430b095ffd2cd4e12b728fc182ed974fbfdbbab Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:30:03 +1000 Subject: [PATCH 5/9] Update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e0a0e9f..d482507 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ in `serde_bytes::ByteBuf`. Additionally this crate supports the Serde `with` attribute to enable efficient handling of `&[u8]` and `Vec` in structs without needing a wrapper type. +This approach also works with `heapless::Vec`, which is useful +where the size of data has a maxmimum bound but isn't an exact size. Use +the `heapless` feature to enable support. You can also opt into special +handling in this case by wrapping the `heapless::Vec` with +`serde_bytes::HeaplessByteBuf`. + ## Example ```rust From b990e0ced7eaf111f2cf49a82230ae42f36f33b1 Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:42:49 +1000 Subject: [PATCH 6/9] Add tests for heapless Vec & HeaplessByteBuf --- tests/test_derive.rs | 28 ++++++++++++++++++++++++++++ tests/test_partialeq.rs | 8 ++++++++ tests/test_serde.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/tests/test_derive.rs b/tests/test_derive.rs index 019a7cb..3f8b854 100644 --- a/tests/test_derive.rs +++ b/tests/test_derive.rs @@ -5,6 +5,9 @@ use serde_derive::{Deserialize, Serialize}; use serde_test::{assert_tokens, Token}; use std::borrow::Cow; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Test<'a> { #[serde(with = "serde_bytes")] @@ -57,6 +60,14 @@ struct Test<'a> { #[serde(with = "serde_bytes")] opt_cow_slice: Option>, + + #[cfg(feature = "heapless")] + #[serde(with = "serde_bytes")] + heapless_byte_buf: HeaplessByteBuf<314>, + + #[cfg(feature = "heapless")] + #[serde(with = "serde_bytes")] + heapless_vec: heapless::Vec, } #[derive(Serialize)] @@ -86,6 +97,12 @@ fn test() { opt_array: Some([0; 314]), opt_bytearray: Some(ByteArray::new([0; 314])), opt_cow_slice: Some(Cow::Borrowed(b"...")), + #[cfg(feature = "heapless")] + heapless_byte_buf: HeaplessByteBuf::try_from(b"...".as_ref()) + .expect("Size of tested `HeaplessByteBuf`to be >= size of test data"), + #[cfg(feature = "heapless")] + heapless_vec: heapless::Vec::from_slice(b"...".as_ref()) + .expect("Size of tested `heapless::Vec` to be >= size of test data"), }; assert_tokens( @@ -93,7 +110,10 @@ fn test() { &[ Token::Struct { name: "Test", + #[cfg(not(feature = "heapless"))] len: 17, + #[cfg(feature = "heapless")] + len: 19, }, Token::Str("slice"), Token::BorrowedBytes(b"..."), @@ -134,6 +154,14 @@ fn test() { Token::Str("opt_cow_slice"), Token::Some, Token::BorrowedBytes(b"..."), + #[cfg(feature = "heapless")] + Token::Str("heapless_byte_buf"), + #[cfg(feature = "heapless")] + Token::BorrowedBytes(b"..."), + #[cfg(feature = "heapless")] + Token::Str("heapless_vec"), + #[cfg(feature = "heapless")] + Token::BorrowedBytes(b"..."), Token::StructEnd, ], ); diff --git a/tests/test_partialeq.rs b/tests/test_partialeq.rs index a0423cf..be40cec 100644 --- a/tests/test_partialeq.rs +++ b/tests/test_partialeq.rs @@ -2,6 +2,9 @@ use serde_bytes::{ByteArray, ByteBuf, Bytes}; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + fn _bytes_eq_slice(bytes: &Bytes, slice: &[u8]) -> bool { bytes == slice } @@ -10,6 +13,11 @@ fn _bytebuf_eq_vec(bytebuf: ByteBuf, vec: Vec) -> bool { bytebuf == vec } +#[cfg(feature = "heapless")] +fn _heapless_bytebuf_eq_vec(bytebuf: HeaplessByteBuf, vec: Vec) -> bool { + bytebuf == vec +} + fn _bytes_eq_bytestring(bytes: &Bytes) -> bool { bytes == b"..." } diff --git a/tests/test_serde.rs b/tests/test_serde.rs index 6b864b9..d15043a 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -1,6 +1,9 @@ use serde_bytes::{ByteArray, ByteBuf, Bytes}; use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token}; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + #[test] fn test_bytes() { let empty = Bytes::new(&[]); @@ -73,3 +76,40 @@ fn test_bytearray() { assert_ser_tokens(&bytes, &[Token::ByteBuf(b"ABC")]); assert_de_tokens(&bytes, &[Token::BorrowedStr("ABC")]); } + +#[test] +#[cfg(feature = "heapless")] +fn test_heapless_byte_buf() { + let empty = HeaplessByteBuf::<3>::new(); + assert_tokens(&empty, &[Token::BorrowedBytes(b"")]); + assert_tokens(&empty, &[Token::Bytes(b"")]); + assert_tokens(&empty, &[Token::ByteBuf(b"")]); + assert_de_tokens(&empty, &[Token::Seq { len: None }, Token::SeqEnd]); + assert_de_tokens(&empty, &[Token::Seq { len: Some(0) }, Token::SeqEnd]); + + let buf = HeaplessByteBuf::<3>::try_from(vec![65, 66, 67]) + .expect("Size of tested `HeaplessByteBuf`to be >= size of test data"); + assert_tokens(&buf, &[Token::BorrowedBytes(b"ABC")]); + assert_tokens(&buf, &[Token::Bytes(b"ABC")]); + assert_tokens(&buf, &[Token::ByteBuf(b"ABC")]); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: None }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: Some(3) }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); +} From d420e7bc55211f649e283c5b44dec494dbae9925 Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 18:45:42 +1000 Subject: [PATCH 7/9] Fix feature flags (std + alloc) --- src/heapless_bytebuf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs index b73b799..1f04e9c 100644 --- a/src/heapless_bytebuf.rs +++ b/src/heapless_bytebuf.rs @@ -64,7 +64,7 @@ impl HeaplessByteBuf { } /// Wrap existing bytes in a `HeaplessByteBuf`. - #[cfg(feature = "alloc")] + #[cfg(any(feature = "std", feature = "alloc"))] pub fn try_from>>( vec: T, ) -> Result> { @@ -141,7 +141,7 @@ impl BorrowMut for HeaplessByteBuf { } } -#[cfg(feature = "alloc")] +#[cfg(any(feature = "std", feature = "alloc"))] impl TryFrom> for HeaplessByteBuf { type Error = HeaplessByteBufFullError; @@ -248,7 +248,7 @@ impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { }) } - #[cfg(feature = "alloc")] + #[cfg(any(feature = "std", feature = "alloc"))] fn visit_byte_buf(self, v: alloc::vec::Vec) -> Result, E> where E: Error, From e2050a6659aefe5dc8888f5070a68e255ea7b744 Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 19:00:58 +1000 Subject: [PATCH 8/9] Improve length error message & use instead of when deserializing --- src/heapless_bytebuf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs index 1f04e9c..e7c9400 100644 --- a/src/heapless_bytebuf.rs +++ b/src/heapless_bytebuf.rs @@ -91,7 +91,7 @@ pub struct HeaplessByteBufFullError; impl Display for HeaplessByteBufFullError { fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - write!(f, "heapless::Vec is too small") + write!(f, "heapless::Vec<{N}> is too small") } } @@ -266,6 +266,6 @@ impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { where D: Deserializer<'de>, { - deserializer.deserialize_byte_buf(HeaplessByteBufVisitor::) + deserializer.deserialize_bytes(HeaplessByteBufVisitor::) } } From f5a0e90c66a7babb5f1d042e9fc6d5a5bd2ab09d Mon Sep 17 00:00:00 2001 From: Cameron Duff Date: Fri, 1 Aug 2025 19:06:23 +1000 Subject: [PATCH 9/9] Remove 'heapless/serde' feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9942dfb..40bbff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ rust-version = "1.68" default = ["std"] std = ["serde/std"] alloc = ["serde/alloc"] -heapless = ["dep:heapless", "heapless/serde"] +heapless = ["dep:heapless"] [dependencies] serde = { version = "1.0.166", default-features = false }