Skip to content

Commit

Permalink
serde::{Serialize, Deserialize} for BoundedVec (paritytech#11314)
Browse files Browse the repository at this point in the history
* serde::{Serialize, Deserialize} for BoundedVec

* Apply suggestions from code review

* FMT

* 🤦

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
  • Loading branch information
3 people authored and ark0f committed Feb 27, 2023
1 parent 60314c4 commit 01de99b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frame/support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/
k256 = { version = "0.10.4", default-features = false, features = ["ecdsa"] }

[dev-dependencies]
serde_json = "1.0.79"
assert_matches = "1.3.0"
pretty_assertions = "1.2.1"
frame-system = { version = "4.0.0-dev", path = "../system" }
Expand Down
92 changes: 91 additions & 1 deletion frame/support/src/storage/bounded_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ use core::{
ops::{Deref, Index, IndexMut, RangeBounds},
slice::SliceIndex,
};
#[cfg(feature = "std")]
use serde::{
de::{Error, SeqAccess, Visitor},
Deserialize, Deserializer, Serialize,
};
use sp_std::{marker::PhantomData, prelude::*};

/// A bounded vector.
Expand All @@ -37,9 +42,67 @@ use sp_std::{marker::PhantomData, prelude::*};
///
/// As the name suggests, the length of the queue is always bounded. All internal operations ensure
/// this bound is respected.
#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))]
#[derive(Encode, scale_info::TypeInfo)]
#[scale_info(skip_type_params(S))]
pub struct BoundedVec<T, S>(Vec<T>, PhantomData<S>);
pub struct BoundedVec<T, S>(
Vec<T>,
#[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData<S>,
);

#[cfg(feature = "std")]
impl<'de, T, S: Get<u32>> Deserialize<'de> for BoundedVec<T, S>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VecVisitor<T, S: Get<u32>>(PhantomData<(T, S)>);

impl<'de, T, S: Get<u32>> Visitor<'de> for VecVisitor<T, S>
where
T: Deserialize<'de>,
{
type Value = Vec<T>;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a sequence")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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 = Vec::with_capacity(size);

while let Some(value) = seq.next_element()? {
values.push(value);
if values.len() > max {
return Err(A::Error::custom("out of bounds"))
}
}

Ok(values)
}
}
}

let visitor: VecVisitor<T, S> = VecVisitor(PhantomData);
deserializer
.deserialize_seq(visitor)
.map(|v| BoundedVec::<T, S>::try_from(v).map_err(|_| Error::custom("out of bounds")))?
}
}

/// A bounded slice.
///
Expand Down Expand Up @@ -908,4 +971,31 @@ pub mod test {
assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err());
assert_eq!(*b, vec![1, 2, 3]);
}

#[test]
fn test_serializer() {
let c: BoundedVec<u32, ConstU32<6>> = bounded_vec![0, 1, 2];
assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#);
}

#[test]
fn test_deserializer() {
let c: BoundedVec<u32, ConstU32<6>> = 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<BoundedVec<u32, ConstU32<4>>, 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"),
}
}
}

0 comments on commit 01de99b

Please sign in to comment.