Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved support for borsh serialization #259

Merged
merged 2 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ categories = ["data-structures", "no-std"]

[build-dependencies]

[dependencies.borsh]
version = "1.2.0"
optional = true
default-features = false

[dependencies.serde]
version = "1.0"
optional = true
Expand Down Expand Up @@ -49,7 +54,7 @@ debug = true
debug = true

[package.metadata.docs.rs]
features = ["serde", "zeroize"]
features = ["borsh", "serde", "zeroize"]

[package.metadata.release]
no-dev-version = true
Expand Down
31 changes: 31 additions & 0 deletions src/array_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,37 @@ impl<'de, const CAP: usize> Deserialize<'de> for ArrayString<CAP>
}
}

#[cfg(feature = "borsh")]
/// Requires crate feature `"borsh"`
impl<const CAP: usize> borsh::BorshSerialize for ArrayString<CAP> {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
<str as borsh::BorshSerialize>::serialize(&*self, writer)
}
}

#[cfg(feature = "borsh")]
/// 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 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),
))
}

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())
}
}

impl<'a, const CAP: usize> TryFrom<&'a str> for ArrayString<CAP>
{
type Error = CapacityError<&'a str>;
Expand Down
34 changes: 34 additions & 0 deletions src/arrayvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,3 +1298,37 @@ impl<'de, T: Deserialize<'de>, const CAP: usize> Deserialize<'de> for ArrayVec<T
deserializer.deserialize_seq(ArrayVecVisitor::<T, CAP>(PhantomData))
}
}

#[cfg(feature = "borsh")]
/// Requires crate feature `"borsh"`
impl<T, const CAP: usize> borsh::BorshSerialize for ArrayVec<T, CAP>
where
T: borsh::BorshSerialize,
{
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
<[T] as borsh::BorshSerialize>::serialize(self.as_slice(), writer)
}
}

#[cfg(feature = "borsh")]
/// Requires crate feature `"borsh"`
impl<T, const CAP: usize> borsh::BorshDeserialize for ArrayVec<T, CAP>
where
T: borsh::BorshDeserialize,
{
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let mut values = Self::new();
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),
));
}
}

Ok(values)
}
}
73 changes: 73 additions & 0 deletions tests/borsh.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}