Skip to content

Commit c934ace

Browse files
authored
Merge pull request #313 from heliaxdev/heliax/borsh-support
Add `borsh` serialization support
2 parents 4df99d8 + b81a4d2 commit c934ace

File tree

4 files changed

+137
-3
lines changed

4 files changed

+137
-3
lines changed

.github/workflows/ci.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
features: rustc-rayon
3030
- rust: stable
3131
features: serde
32+
- rust: stable
33+
features: borsh
3234
- rust: stable
3335
features: std
3436
- rust: beta
@@ -119,8 +121,10 @@ jobs:
119121
with:
120122
tool: cargo-hack
121123
- run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions
122-
- name: Build
123-
run: cargo build --verbose --all-features
124+
- name: Build (nightly)
125+
run: cargo +nightly build --verbose --all-features
126+
- name: Build (MSRV)
127+
run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon
124128

125129
# One job that "summarizes" the success state of this pipeline. This can then be added to branch
126130
# protection, rather than having to add each job separately.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ equivalent = { version = "1.0", default-features = false }
1919
arbitrary = { version = "1.0", optional = true, default-features = false }
2020
quickcheck = { version = "1.0", optional = true, default-features = false }
2121
serde = { version = "1.0", optional = true, default-features = false }
22+
borsh = { version = "1.2", optional = true, default-features = false }
2223
rayon = { version = "1.5.3", optional = true }
2324

2425
# Internal feature, only used when building as part of rustc,
@@ -54,7 +55,7 @@ no-dev-version = true
5455
tag-name = "{{version}}"
5556

5657
[package.metadata.docs.rs]
57-
features = ["arbitrary", "quickcheck", "serde", "rayon"]
58+
features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"]
5859
rustdoc-args = ["--cfg", "docsrs"]
5960

6061
[workspace]

src/borsh.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#![cfg_attr(docsrs, doc(cfg(feature = "borsh")))]
2+
3+
use alloc::vec::Vec;
4+
use core::hash::BuildHasher;
5+
use core::hash::Hash;
6+
use core::iter::ExactSizeIterator;
7+
use core::mem::size_of;
8+
9+
use borsh::error::ERROR_ZST_FORBIDDEN;
10+
use borsh::io::{Error, ErrorKind, Read, Result, Write};
11+
use borsh::{BorshDeserialize, BorshSerialize};
12+
13+
use crate::map::IndexMap;
14+
use crate::set::IndexSet;
15+
16+
impl<K, V, S> BorshSerialize for IndexMap<K, V, S>
17+
where
18+
K: BorshSerialize,
19+
V: BorshSerialize,
20+
{
21+
#[inline]
22+
fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
23+
check_zst::<K>()?;
24+
25+
let iterator = self.iter();
26+
27+
u32::try_from(iterator.len())
28+
.map_err(|_| ErrorKind::InvalidData)?
29+
.serialize(writer)?;
30+
31+
for (key, value) in iterator {
32+
key.serialize(writer)?;
33+
value.serialize(writer)?;
34+
}
35+
36+
Ok(())
37+
}
38+
}
39+
40+
impl<K, V, S> BorshDeserialize for IndexMap<K, V, S>
41+
where
42+
K: BorshDeserialize + Eq + Hash,
43+
V: BorshDeserialize,
44+
S: BuildHasher + Default,
45+
{
46+
#[inline]
47+
fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
48+
check_zst::<K>()?;
49+
let vec = <Vec<(K, V)>>::deserialize_reader(reader)?;
50+
Ok(vec.into_iter().collect::<IndexMap<K, V, S>>())
51+
}
52+
}
53+
54+
impl<T, S> BorshSerialize for IndexSet<T, S>
55+
where
56+
T: BorshSerialize,
57+
{
58+
#[inline]
59+
fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
60+
check_zst::<T>()?;
61+
62+
let iterator = self.iter();
63+
64+
u32::try_from(iterator.len())
65+
.map_err(|_| ErrorKind::InvalidData)?
66+
.serialize(writer)?;
67+
68+
for item in iterator {
69+
item.serialize(writer)?;
70+
}
71+
72+
Ok(())
73+
}
74+
}
75+
76+
impl<T, S> BorshDeserialize for IndexSet<T, S>
77+
where
78+
T: BorshDeserialize + Eq + Hash,
79+
S: BuildHasher + Default,
80+
{
81+
#[inline]
82+
fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
83+
check_zst::<T>()?;
84+
let vec = <Vec<T>>::deserialize_reader(reader)?;
85+
Ok(vec.into_iter().collect::<IndexSet<T, S>>())
86+
}
87+
}
88+
89+
fn check_zst<T>() -> Result<()> {
90+
if size_of::<T>() == 0 {
91+
return Err(Error::new(ErrorKind::InvalidData, ERROR_ZST_FORBIDDEN));
92+
}
93+
Ok(())
94+
}
95+
96+
#[cfg(test)]
97+
mod borsh_tests {
98+
use super::*;
99+
100+
#[test]
101+
fn map_borsh_roundtrip() {
102+
let original_map: IndexMap<i32, i32> = {
103+
let mut map = IndexMap::new();
104+
map.insert(1, 2);
105+
map.insert(3, 4);
106+
map.insert(5, 6);
107+
map
108+
};
109+
let serialized_map = borsh::to_vec(&original_map).unwrap();
110+
let deserialized_map: IndexMap<i32, i32> =
111+
BorshDeserialize::try_from_slice(&serialized_map).unwrap();
112+
assert_eq!(original_map, deserialized_map);
113+
}
114+
115+
#[test]
116+
fn set_borsh_roundtrip() {
117+
let original_map: IndexSet<i32> = [1, 2, 3, 4, 5, 6].into_iter().collect();
118+
let serialized_map = borsh::to_vec(&original_map).unwrap();
119+
let deserialized_map: IndexSet<i32> =
120+
BorshDeserialize::try_from_slice(&serialized_map).unwrap();
121+
assert_eq!(original_map, deserialized_map);
122+
}
123+
}

src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
//! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for
3636
//! (de)serializing [`IndexMap`] as an ordered sequence are available in the
3737
//! [`map::serde_seq`] module.
38+
//! * `borsh`: Adds implementations for [`BorshSerialize`] and [`BorshDeserialize`]
39+
//! to [`IndexMap`] and [`IndexSet`].
3840
//! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait
3941
//! to [`IndexMap`] and [`IndexSet`].
4042
//! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait
@@ -46,6 +48,8 @@
4648
//! [`no_std`]: #no-standard-library-targets
4749
//! [`Serialize`]: `::serde::Serialize`
4850
//! [`Deserialize`]: `::serde::Deserialize`
51+
//! [`BorshSerialize`]: `::borsh::BorshSerialize`
52+
//! [`BorshDeserialize`]: `::borsh::BorshDeserialize`
4953
//! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary`
5054
//! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary`
5155
//!
@@ -110,6 +114,8 @@ use alloc::vec::{self, Vec};
110114
mod arbitrary;
111115
#[macro_use]
112116
mod macros;
117+
#[cfg(feature = "borsh")]
118+
mod borsh;
113119
mod mutable_keys;
114120
#[cfg(feature = "serde")]
115121
mod serde;

0 commit comments

Comments
 (0)