From ae38b913022251230c6b5fbe3df3706e300449ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 11:32:30 +0000 Subject: [PATCH 1/7] Add `borsh` dep to Cargo manifest --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 95216563..13a54dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ equivalent = { version = "1.0", default-features = false } arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } +borsh = { version = "1.2", optional = true, default-features = false } rayon = { version = "1.5.3", optional = true } # Internal feature, only used when building as part of rustc, @@ -54,7 +55,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["arbitrary", "quickcheck", "serde", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"] rustdoc-args = ["--cfg", "docsrs"] [workspace] From 0804a16e24d2c8e0ed5e466df195fc358bb91b48 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 11:33:19 +0000 Subject: [PATCH 2/7] Implement `borsh` serialization routines --- src/borsh.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++++ 2 files changed, 102 insertions(+) create mode 100644 src/borsh.rs diff --git a/src/borsh.rs b/src/borsh.rs new file mode 100644 index 00000000..9338f435 --- /dev/null +++ b/src/borsh.rs @@ -0,0 +1,96 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "borsh")))] + +use alloc::vec::Vec; +use core::hash::BuildHasher; +use core::hash::Hash; +use core::iter::ExactSizeIterator; +use core::mem::size_of; + +use borsh::error::ERROR_ZST_FORBIDDEN; +use borsh::io::{Error, ErrorKind, Read, Result, Write}; +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::map::IndexMap; +use crate::set::IndexSet; + +impl BorshSerialize for IndexMap +where + K: BorshSerialize, + V: BorshSerialize, + H: BuildHasher, +{ + #[inline] + fn serialize(&self, writer: &mut W) -> Result<()> { + check_zst::()?; + + let iterator = self.iter(); + + u32::try_from(iterator.len()) + .map_err(|_| ErrorKind::InvalidData)? + .serialize(writer)?; + + for (key, value) in iterator { + key.serialize(writer)?; + value.serialize(writer)?; + } + + Ok(()) + } +} + +impl BorshDeserialize for IndexMap +where + K: BorshDeserialize + Eq + Hash, + V: BorshDeserialize, + H: BuildHasher + Default, +{ + #[inline] + fn deserialize_reader(reader: &mut R) -> Result { + check_zst::()?; + let vec = >::deserialize_reader(reader)?; + Ok(vec.into_iter().collect::>()) + } +} + +impl BorshSerialize for IndexSet +where + T: BorshSerialize, + H: BuildHasher, +{ + #[inline] + fn serialize(&self, writer: &mut W) -> Result<()> { + check_zst::()?; + + let iterator = self.iter(); + + u32::try_from(iterator.len()) + .map_err(|_| ErrorKind::InvalidData)? + .serialize(writer)?; + + for item in iterator { + item.serialize(writer)?; + } + + Ok(()) + } +} + +impl BorshDeserialize for IndexSet +where + T: BorshDeserialize + Eq + Hash, + H: BuildHasher + Default, +{ + #[inline] + fn deserialize_reader(reader: &mut R) -> Result { + check_zst::()?; + let vec = >::deserialize_reader(reader)?; + Ok(vec.into_iter().collect::>()) + } +} + +fn check_zst() -> Result<()> { + if size_of::() == 0 { + return Err(Error::new(ErrorKind::InvalidData, ERROR_ZST_FORBIDDEN)); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index b88c1bce..d6d3ede9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,8 @@ //! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for //! (de)serializing [`IndexMap`] as an ordered sequence are available in the //! [`map::serde_seq`] module. +//! * `borsh`: Adds implementations for [`BorshSerialize`] and [`BorshDeserialize`] +//! to [`IndexMap`] and [`IndexSet`]. //! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait //! to [`IndexMap`] and [`IndexSet`]. //! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait @@ -46,6 +48,8 @@ //! [`no_std`]: #no-standard-library-targets //! [`Serialize`]: `::serde::Serialize` //! [`Deserialize`]: `::serde::Deserialize` +//! [`BorshSerialize`]: `::borsh::BorshSerialize` +//! [`BorshDeserialize`]: `::borsh::BorshDeserialize` //! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary` //! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary` //! @@ -110,6 +114,8 @@ use alloc::vec::{self, Vec}; mod arbitrary; #[macro_use] mod macros; +#[cfg(feature = "borsh")] +mod borsh; mod mutable_keys; #[cfg(feature = "serde")] mod serde; From c610e14ea059dc8e99359edf5fa4a1f53274a73b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 14:32:32 +0000 Subject: [PATCH 3/7] Add `borsh` serialization roundtrip tests --- src/borsh.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/borsh.rs b/src/borsh.rs index 9338f435..24899acb 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -94,3 +94,32 @@ fn check_zst() -> Result<()> { } Ok(()) } + +#[cfg(test)] +mod borsh_tests { + use super::*; + + #[test] + fn map_borsh_roundtrip() { + let original_map: IndexMap = { + let mut map = IndexMap::new(); + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + map + }; + let serialized_map = borsh::to_vec(&original_map).unwrap(); + let deserialized_map: IndexMap = + BorshDeserialize::try_from_slice(&serialized_map).unwrap(); + assert_eq!(original_map, deserialized_map); + } + + #[test] + fn set_borsh_roundtrip() { + let original_map: IndexSet = [1, 2, 3, 4, 5, 6].into_iter().collect(); + let serialized_map = borsh::to_vec(&original_map).unwrap(); + let deserialized_map: IndexSet = + BorshDeserialize::try_from_slice(&serialized_map).unwrap(); + assert_eq!(original_map, deserialized_map); + } +} From 6ad3e42dc34a0f902090aa69e0aa0ce82508a4db Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Feb 2024 10:14:09 +0000 Subject: [PATCH 4/7] Include `borsh` in CI workflow --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7631a57b..5b5448c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,8 @@ jobs: features: rustc-rayon - rust: stable features: serde + - rust: stable + features: borsh - rust: stable features: std - rust: beta From b8b1f52599d7fee3458bad36110d8a495ddee443 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:03:14 -0800 Subject: [PATCH 5/7] ci: reduce features on MSRV --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b5448c9..312f4533 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,8 +121,10 @@ jobs: with: tool: cargo-hack - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - - name: Build - run: cargo build --verbose --all-features + - name: Build (nightly) + run: cargo +nightly build --verbose --all-features + - name: Build (MSRV) + run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon # One job that "summarizes" the success state of this pipeline. This can then be added to branch # protection, rather than having to add each job separately. From 32793f1211fdf88b475e909259470011edfba152 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:04:28 -0800 Subject: [PATCH 6/7] Don't require BuildHasher in BorshSerialize --- src/borsh.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/borsh.rs b/src/borsh.rs index 24899acb..2b3e06fe 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -17,7 +17,6 @@ impl BorshSerialize for IndexMap where K: BorshSerialize, V: BorshSerialize, - H: BuildHasher, { #[inline] fn serialize(&self, writer: &mut W) -> Result<()> { @@ -55,7 +54,6 @@ where impl BorshSerialize for IndexSet where T: BorshSerialize, - H: BuildHasher, { #[inline] fn serialize(&self, writer: &mut W) -> Result<()> { From b81a4d25e1703dfd574aee4551460ba571e1c870 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:06:23 -0800 Subject: [PATCH 7/7] Use S for the BuildHasher parameter --- src/borsh.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/borsh.rs b/src/borsh.rs index 2b3e06fe..7b4afdc4 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -13,7 +13,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use crate::map::IndexMap; use crate::set::IndexSet; -impl BorshSerialize for IndexMap +impl BorshSerialize for IndexMap where K: BorshSerialize, V: BorshSerialize, @@ -37,21 +37,21 @@ where } } -impl BorshDeserialize for IndexMap +impl BorshDeserialize for IndexMap where K: BorshDeserialize + Eq + Hash, V: BorshDeserialize, - H: BuildHasher + Default, + S: BuildHasher + Default, { #[inline] fn deserialize_reader(reader: &mut R) -> Result { check_zst::()?; let vec = >::deserialize_reader(reader)?; - Ok(vec.into_iter().collect::>()) + Ok(vec.into_iter().collect::>()) } } -impl BorshSerialize for IndexSet +impl BorshSerialize for IndexSet where T: BorshSerialize, { @@ -73,16 +73,16 @@ where } } -impl BorshDeserialize for IndexSet +impl BorshDeserialize for IndexSet where T: BorshDeserialize + Eq + Hash, - H: BuildHasher + Default, + S: BuildHasher + Default, { #[inline] fn deserialize_reader(reader: &mut R) -> Result { check_zst::()?; let vec = >::deserialize_reader(reader)?; - Ok(vec.into_iter().collect::>()) + Ok(vec.into_iter().collect::>()) } }