From 8e05b49caf467296d4c150d9c359acce40bb4550 Mon Sep 17 00:00:00 2001 From: kraktus Date: Fri, 29 Sep 2023 11:04:59 +0200 Subject: [PATCH] Implement `SerializeAs` and `DeserializeAs` for `ArrayVec` `serde_with` is a widely used crate (24M downloads) to allow custom (de)serialisation. This commit allow to use Arrayvec as a container in the `serde_with::serde_as` macro. ```rust #[serde_as] #[derive(Serialize, Deserialize)] pub struct Native { #[serde_as(as = "ArrayVec")] pub sans: ArrayVec, } ``` I've it included under the `serde` feature, but it can be on its own feature. If this is deemed to niche to include, an alternative would be to include it directly in the `serde_with` crate under a feature flag, similar to what is done for other popular library, such as `hashbrown_0_14` or `chrono_0_4`. It has the disadvantage of pinning to one version. --- Cargo.toml | 7 +++++ src/arrayvec.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++--- tests/serde.rs | 47 ++++++++++++++++++++++++++++----- 3 files changed, 113 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f4fd2c..90dd3da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,11 @@ version = "1.0" optional = true default-features = false +[dependencies.serde_with] +version = "3" +optional = true +default-features = false + [dependencies.zeroize] version = "1.4" optional = true @@ -30,6 +35,7 @@ version = "1.0" [dev-dependencies] matches = { version = "0.1" } bencher = "0.1.4" +serde_with = { version = "3" } [[bench]] name = "extend" @@ -42,6 +48,7 @@ harness = false [features] default = ["std"] std = [] +serde = ["dep:serde", "dep:serde_with"] [profile.bench] debug = true diff --git a/src/arrayvec.rs b/src/arrayvec.rs index 37e151a..007980d 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -17,8 +17,11 @@ use std::io; use std::mem::ManuallyDrop; use std::mem::MaybeUninit; -#[cfg(feature="serde")] -use serde::{Serialize, Deserialize, Serializer, Deserializer}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "serde")] +use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs}; use crate::LenUint; use crate::errors::CapacityError; @@ -1262,7 +1265,18 @@ impl Serialize for ArrayVec { } } -#[cfg(feature="serde")] +#[cfg(feature = "serde")] +/// Requires crate feature `"serde"` +impl, const CAP: usize> SerializeAs> for ArrayVec { + fn serialize_as(source: &ArrayVec, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_seq(source.iter().map(SerializeAsWrap::::new)) + } +} + +#[cfg(feature = "serde")] /// Requires crate feature `"serde"` impl<'de, T: Deserialize<'de>, const CAP: usize> Deserialize<'de> for ArrayVec { fn deserialize(deserializer: D) -> Result @@ -1286,7 +1300,7 @@ impl<'de, T: Deserialize<'de>, const CAP: usize> Deserialize<'de> for ArrayVec::new(); while let Some(value) = seq.next_element()? { - if let Err(_) = values.try_push(value) { + if values.try_push(value).is_err() { return Err(SA::Error::invalid_length(CAP + 1, &self)); } } @@ -1298,3 +1312,51 @@ impl<'de, T: Deserialize<'de>, const CAP: usize> Deserialize<'de> for ArrayVec(PhantomData)) } } + +#[cfg(feature = "serde")] +/// Requires crate feature `"serde"` +impl<'de, T, U, const CAP: usize> DeserializeAs<'de, ArrayVec> for ArrayVec +where + U: DeserializeAs<'de, T>, +{ + fn deserialize_as(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + use serde::de::{Error, SeqAccess, Visitor}; + use std::marker::PhantomData; + + struct ArrayVecVisitor<'de, T, U, const CAP: usize>(PhantomData<(&'de (), U, [T; CAP])>); + + impl<'de, T, U, const CAP: usize> Visitor<'de> for ArrayVecVisitor<'de, T, U, CAP> + where + U: DeserializeAs<'de, T>, + { + type Value = ArrayVec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "an array with no more than {} items", CAP) + } + + fn visit_seq(self, mut seq: SA) -> Result + where + SA: SeqAccess<'de>, + { + let mut values = ArrayVec::::new(); + + while let Some(value) = seq + .next_element()? + .map(|v: DeserializeAsWrap| v.into_inner()) + { + if let Err(_) = values.try_push(value) { + return Err(SA::Error::invalid_length(CAP + 1, &self)); + } + } + + Ok(values) + } + } + + deserializer.deserialize_seq(ArrayVecVisitor::(PhantomData)) + } +} diff --git a/tests/serde.rs b/tests/serde.rs index f02c693..cdd6f25 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -5,7 +5,8 @@ extern crate serde_test; mod array_vec { use arrayvec::ArrayVec; - use serde_test::{Token, assert_tokens, assert_de_tokens_error}; + use serde_test::{assert_de_tokens_error, assert_tokens, Token}; + use serde_with::{serde_as, DisplayFromStr}; #[test] fn test_ser_de_empty() { @@ -36,12 +37,44 @@ mod array_vec { #[test] fn test_de_too_large() { - assert_de_tokens_error::>(&[ - Token::Seq { len: Some(3) }, - Token::U32(13), - Token::U32(42), - Token::U32(68), - ], "invalid length 3, expected an array with no more than 2 items"); + assert_de_tokens_error::>( + &[ + Token::Seq { len: Some(3) }, + Token::U32(13), + Token::U32(42), + Token::U32(68), + ], + "invalid length 3, expected an array with no more than 2 items", + ); + } + + #[serde_as] + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] + struct SerdeAs { + #[serde_as(as = "ArrayVec")] + x: ArrayVec + } + + #[test] + fn test_ser_de_as() { + let mut serde_as = SerdeAs {x: ArrayVec::::new()}; + serde_as.x.push(20); + serde_as.x.push(55); + serde_as.x.push(123); + + assert_tokens( + &serde_as, + &[ + Token::Struct { name: "SerdeAs", len: 1 }, + Token::Str("x"), + Token::Seq { len: Some(3) }, + Token::Str("20"), + Token::Str("55"), + Token::Str("123"), + Token::SeqEnd, + Token::StructEnd, + ], + ); } }