Skip to content

Commit

Permalink
Improve optional support for borsh serialization
Browse files Browse the repository at this point in the history
- Do not allocate when deserializing ArrayString
- Serialize length as u32, not as u64, to be consistent with
  serialization of [T] and str
- Add tests
  • Loading branch information
honzasp authored and bluss committed Mar 7, 2024
1 parent 4337b1b commit ae6cfd3
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -18,7 +18,7 @@ jobs:
matrix:
include:
- rust: 1.51.0 # MSRV
features: serde
features: serde, borsh
experimental: false
- rust: stable
features:
Expand All @@ -28,7 +28,7 @@ jobs:
features: serde
experimental: false
- rust: nightly
features: serde, zeroize
features: serde, borsh, zeroize
experimental: false

steps:
Expand Down
21 changes: 15 additions & 6 deletions src/array_string.rs
Expand Up @@ -640,13 +640,22 @@ impl<const CAP: usize> borsh::BorshSerialize for ArrayString<CAP> {
/// Requires crate feature `"borsh"`
impl<const CAP: usize> borsh::BorshDeserialize for ArrayString<CAP> {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let s = <String as borsh::BorshDeserialize>::deserialize_reader(reader)?;
ArrayString::from(&s).map_err(|_| {
borsh::io::Error::new(
let len = <u32 as borsh::BorshDeserialize>::deserialize_reader(reader)? as usize;
if len > CAP {
return Err(borsh::io::Error::new(
borsh::io::ErrorKind::InvalidData,
format!("expected a string no more than {} bytes long", CAP),
)
})
format!("Expected a string no more than {} bytes long", CAP),
))
}

let mut buf = [0u8; CAP];
let buf = &mut buf[..len];
reader.read_exact(buf)?;

let s = str::from_utf8(&buf).map_err(|err| {
borsh::io::Error::new(borsh::io::ErrorKind::InvalidData, err.to_string())
})?;
Ok(Self::from(s).unwrap())
}
}

Expand Down
11 changes: 3 additions & 8 deletions src/arrayvec.rs
Expand Up @@ -1309,12 +1309,7 @@ where
T: borsh::BorshSerialize,
{
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
let vs = self.as_slice();
<usize as borsh::BorshSerialize>::serialize(&vs.len(), writer)?;
for elem in vs {
<T as borsh::BorshSerialize>::serialize(elem, writer)?;
}
Ok(())
<[T] as borsh::BorshSerialize>::serialize(self.as_slice(), writer)
}
}

Expand All @@ -1326,13 +1321,13 @@ where
{
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let mut values = Self::new();
let len = <usize as borsh::BorshDeserialize>::deserialize_reader(reader)?;
let len = <u32 as borsh::BorshDeserialize>::deserialize_reader(reader)?;
for _ in 0..len {
let elem = <T as borsh::BorshDeserialize>::deserialize_reader(reader)?;
if let Err(_) = values.try_push(elem) {
return Err(borsh::io::Error::new(
borsh::io::ErrorKind::InvalidData,
format!("expected an array with no more than {} items", CAP),
format!("Expected an array with no more than {} items", CAP),
));
}
}
Expand Down
73 changes: 73 additions & 0 deletions tests/borsh.rs
@@ -0,0 +1,73 @@
#![cfg(feature = "borsh")]
use std::fmt;
extern crate arrayvec;
extern crate borsh;

fn assert_ser<T: borsh::BorshSerialize>(v: &T, expected_bytes: &[u8]) {
let mut actual_bytes = Vec::new();
v.serialize(&mut actual_bytes).unwrap();
assert_eq!(actual_bytes, expected_bytes);
}

fn assert_roundtrip<T: borsh::BorshSerialize + borsh::BorshDeserialize + PartialEq + fmt::Debug>(v: &T) {
let mut bytes = Vec::new();
v.serialize(&mut bytes).unwrap();
let v_de = T::try_from_slice(&bytes).unwrap();
assert_eq!(*v, v_de);
}

mod array_vec {
use arrayvec::ArrayVec;
use super::{assert_ser, assert_roundtrip};

#[test]
fn test_empty() {
let vec = ArrayVec::<u32, 0>::new();
assert_ser(&vec, b"\0\0\0\0");
assert_roundtrip(&vec);
}

#[test]
fn test_full() {
let mut vec = ArrayVec::<u32, 3>::new();
vec.push(0xdeadbeef);
vec.push(0x123);
vec.push(0x456);
assert_ser(&vec, b"\x03\0\0\0\xef\xbe\xad\xde\x23\x01\0\0\x56\x04\0\0");
assert_roundtrip(&vec);
}

#[test]
fn test_with_free_capacity() {
let mut vec = ArrayVec::<u32, 3>::new();
vec.push(0xdeadbeef);
assert_ser(&vec, b"\x01\0\0\0\xef\xbe\xad\xde");
assert_roundtrip(&vec);
}
}

mod array_string {
use arrayvec::ArrayString;
use super::{assert_ser, assert_roundtrip};

#[test]
fn test_empty() {
let string = ArrayString::<0>::new();
assert_ser(&string, b"\0\0\0\0");
assert_roundtrip(&string);
}

#[test]
fn test_full() {
let string = ArrayString::from_byte_string(b"hello world").unwrap();
assert_ser(&string, b"\x0b\0\0\0hello world");
assert_roundtrip(&string);
}

#[test]
fn test_with_free_capacity() {
let string = ArrayString::<16>::from("hello world").unwrap();
assert_ser(&string, b"\x0b\0\0\0hello world");
assert_roundtrip(&string);
}
}

0 comments on commit ae6cfd3

Please sign in to comment.