From b80190aa8b0874ef1cbd98d3793861715134918f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20=C3=96zerk?= Date: Wed, 30 Aug 2023 20:23:57 +0300 Subject: [PATCH 1/4] deserialize for btreeset --- bounded-collections/src/bounded_btree_set.rs | 65 +++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/bounded-collections/src/bounded_btree_set.rs b/bounded-collections/src/bounded_btree_set.rs index 3322cb72..fa0ff07b 100644 --- a/bounded-collections/src/bounded_btree_set.rs +++ b/bounded-collections/src/bounded_btree_set.rs @@ -21,6 +21,11 @@ use crate::{Get, TryCollect}; use alloc::collections::BTreeSet; use codec::{Compact, Decode, Encode, MaxEncodedLen}; use core::{borrow::Borrow, marker::PhantomData, ops::Deref}; +#[cfg(feature = "serde")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; /// A bounded set based on a B-Tree. /// @@ -29,9 +34,67 @@ use core::{borrow::Borrow, marker::PhantomData, ops::Deref}; /// /// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the /// set. All internal operations ensure this bound is respected. +#[cfg_attr(feature = "serde", derive(Serialize), serde(transparent))] #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct BoundedBTreeSet(BTreeSet, PhantomData); +pub struct BoundedBTreeSet(BTreeSet, #[cfg_attr(feature = "serde", serde(skip_serializing))] PhantomData); + +#[cfg(feature = "serde")] +impl<'de, T, S: Get> Deserialize<'de> for BoundedBTreeSet +where + T: Ord + Deserialize<'de>, + S: Clone, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Create a visitor to visit each element in the sequence + struct BTreeSetVisitor(std::marker::PhantomData<(T, S)>); + + impl<'de, T, S> Visitor<'de> for BTreeSetVisitor + where + T: Ord + Deserialize<'de>, + S: Get + Clone, + { + type Value = BTreeSet; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + Err(A::Error::custom("out of bounds")) + } else { + let mut values = BTreeSet::new(); + + while let Some(value) = seq.next_element()? { + values.insert(value); + if values.len() > max { + return Err(A::Error::custom("out of bounds")) + } + } + + Ok(values) + } + } + } + + let visitor: BTreeSetVisitor = BTreeSetVisitor(PhantomData); + deserializer + .deserialize_seq(visitor) + .map(|v| BoundedBTreeSet::::try_from(v).map_err(|_| Error::custom("out of bounds")))? + } +} impl Decode for BoundedBTreeSet where From ab485033fe6500d2c95ad7766205700e1c015a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20=C3=96zerk?= Date: Wed, 30 Aug 2023 20:35:15 +0300 Subject: [PATCH 2/4] tests added --- bounded-collections/src/bounded_btree_set.rs | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bounded-collections/src/bounded_btree_set.rs b/bounded-collections/src/bounded_btree_set.rs index fa0ff07b..df1d86c4 100644 --- a/bounded-collections/src/bounded_btree_set.rs +++ b/bounded-collections/src/bounded_btree_set.rs @@ -585,4 +585,37 @@ mod test { set: BoundedBTreeSet>, } } + + #[test] + fn test_serializer() { + let mut c = BoundedBTreeSet::>::new(); + c.try_insert(0).unwrap(); + c.try_insert(1).unwrap(); + c.try_insert(2).unwrap(); + + assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); + } + + #[test] + fn test_deserializer() { + let c: Result>, serde_json::error::Error> = serde_json::from_str(r#"[0,1,2]"#); + assert!(c.is_ok()); + let c = c.unwrap(); + + assert_eq!(c.len(), 3); + assert!(c.contains(&0)); + assert!(c.contains(&1)); + assert!(c.contains(&2)); + } + + #[test] + fn test_deserializer_failed() { + let c: Result>, serde_json::error::Error> = + serde_json::from_str(r#"[0,1,2,3,4,5]"#); + + match c { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), + _ => unreachable!("deserializer must raise error"), + } + } } From 0eee709e20c3ef206d62b650eb58d6016c8cbbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20=C3=96zerk?= Date: Wed, 30 Aug 2023 23:11:14 +0300 Subject: [PATCH 3/4] run tests under std for bounded-btree-set --- bounded-collections/src/bounded_btree_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bounded-collections/src/bounded_btree_set.rs b/bounded-collections/src/bounded_btree_set.rs index df1d86c4..a391fe85 100644 --- a/bounded-collections/src/bounded_btree_set.rs +++ b/bounded-collections/src/bounded_btree_set.rs @@ -396,7 +396,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod test { use super::*; use crate::ConstU32; From 99ee854a26e9f5a3ee6b0ba8c2085020341bbd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20=C3=96zerk?= Date: Thu, 31 Aug 2023 13:16:22 +0300 Subject: [PATCH 4/4] insert after bound check, new tests for bound check --- bounded-collections/src/bounded_btree_set.rs | 18 +++++++++++++++--- bounded-collections/src/bounded_vec.rs | 17 +++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/bounded-collections/src/bounded_btree_set.rs b/bounded-collections/src/bounded_btree_set.rs index a391fe85..c4eb255e 100644 --- a/bounded-collections/src/bounded_btree_set.rs +++ b/bounded-collections/src/bounded_btree_set.rs @@ -78,10 +78,10 @@ where let mut values = BTreeSet::new(); while let Some(value) = seq.next_element()? { - values.insert(value); - if values.len() > max { + if values.len() >= max { return Err(A::Error::custom("out of bounds")) } + values.insert(value); } Ok(values) @@ -608,10 +608,22 @@ mod test { assert!(c.contains(&2)); } + #[test] + fn test_deserializer_bound() { + let c: Result>, serde_json::error::Error> = serde_json::from_str(r#"[0,1,2]"#); + assert!(c.is_ok()); + let c = c.unwrap(); + + assert_eq!(c.len(), 3); + assert!(c.contains(&0)); + assert!(c.contains(&1)); + assert!(c.contains(&2)); + } + #[test] fn test_deserializer_failed() { let c: Result>, serde_json::error::Error> = - serde_json::from_str(r#"[0,1,2,3,4,5]"#); + serde_json::from_str(r#"[0,1,2,3,4]"#); match c { Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), diff --git a/bounded-collections/src/bounded_vec.rs b/bounded-collections/src/bounded_vec.rs index 2f87d3de..cd26afb1 100644 --- a/bounded-collections/src/bounded_vec.rs +++ b/bounded-collections/src/bounded_vec.rs @@ -87,10 +87,10 @@ where let mut values = Vec::with_capacity(size); while let Some(value) = seq.next_element()? { - values.push(value); - if values.len() > max { + if values.len() >= max { return Err(A::Error::custom("out of bounds")) } + values.push(value); } Ok(values) @@ -1187,10 +1187,19 @@ mod test { assert_eq!(c[2], 2); } + #[test] + fn test_deserializer_bound() { + let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); + + assert_eq!(c.len(), 3); + assert_eq!(c[0], 0); + assert_eq!(c[1], 1); + assert_eq!(c[2], 2); + } + #[test] fn test_deserializer_failed() { - let c: Result>, serde_json::error::Error> = - serde_json::from_str(r#"[0,1,2,3,4,5]"#); + let c: Result>, serde_json::error::Error> = serde_json::from_str(r#"[0,1,2,3,4]"#); match c { Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"),