Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tendermint): Fix deserialization from serde_json::Value #1475

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions proto/src/serializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

pub mod allow_null;
pub mod bytes;
pub mod cow_str;
pub mod evidence;
pub mod from_str;
pub mod from_str_allow_null;
Expand Down
17 changes: 11 additions & 6 deletions proto/src/serializers/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ pub mod hexstring {
use subtle_encoding::hex;

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize a hex-encoded string into `Vec<u8>`
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let string = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();
hex::decode_upper(&string)
.or_else(|_| hex::decode(&string))
.map_err(serde::de::Error::custom)
Expand All @@ -36,14 +37,15 @@ pub mod base64string {
use subtle_encoding::base64;

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize base64string into `Vec<u8>`
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
Vec<u8>: Into<T>,
{
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();
let v = base64::decode(s).map_err(serde::de::Error::custom)?;
Ok(v.into())
}
Expand All @@ -53,7 +55,7 @@ pub mod base64string {
where
D: Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();
String::from_utf8(base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
}
Expand All @@ -76,13 +78,14 @@ pub mod vec_base64string {
use subtle_encoding::base64;

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize array into `Vec<Vec<u8>>`
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
Option::<Vec<String>>::deserialize(deserializer)?
Option::<Vec<CowStr>>::deserialize(deserializer)?
.unwrap_or_default()
.into_iter()
.map(|s| base64::decode(s).map_err(serde::de::Error::custom))
Expand Down Expand Up @@ -111,13 +114,14 @@ pub mod option_base64string {
use subtle_encoding::base64;

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize `Option<base64string>` into `Vec<u8>` or null
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();
base64::decode(s).map_err(serde::de::Error::custom)
}

Expand All @@ -138,14 +142,15 @@ pub mod string {
use serde::{Deserialize, Deserializer, Serializer};

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize string into `Vec<u8>`
#[allow(dead_code)]
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let string = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();
Ok(string.as_bytes().to_vec())
}

Expand Down
173 changes: 173 additions & 0 deletions proto/src/serializers/cow_str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Wrapper `Cow<'_, str>` for deserializing without allocation.
//!
//! This is a workaround for [serde's issue 1852](https://github.com/serde-rs/serde/issues/1852).

use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use core::fmt::{self, Debug, Display, Formatter};
use core::ops::Deref;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

/// Wrapper `Cow<'_, str>` for deserializing without allocation.
#[derive(Default)]
pub struct CowStr<'a>(Cow<'a, str>);

impl<'a> CowStr<'a> {
/// Convert into `Cow<'a, str>`.
pub fn into_inner(self) -> Cow<'a, str> {
self.0
}
}

impl<'de> Deserialize<'de> for CowStr<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = CowStr<'de>;

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

fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(CowStr(Cow::Borrowed(value)))
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(CowStr(Cow::Owned(value.to_owned())))
}

fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(CowStr(Cow::Owned(value)))
}
}

deserializer.deserialize_str(Visitor)
}
}

impl<'a> Serialize for CowStr<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}

impl<'a> Debug for CowStr<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<&str as Debug>::fmt(&&*self.0, f)
}
}

impl<'a> Display for CowStr<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<&str as Display>::fmt(&&*self.0, f)
}
}

impl<'a> Deref for CowStr<'a> {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'a> AsRef<str> for CowStr<'a> {
fn as_ref(&self) -> &str {
&self.0
}
}

impl<'a> AsRef<[u8]> for CowStr<'a> {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl<'a> From<CowStr<'a>> for Cow<'a, str> {
fn from(value: CowStr<'a>) -> Self {
value.0
}
}

impl<'a> From<Cow<'a, str>> for CowStr<'a> {
fn from(value: Cow<'a, str>) -> Self {
CowStr(value)
}
}

/// Serialize `Cow<'_, str>`.
pub fn serialize<'a, S>(value: &Cow<'a, str>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(value)
}

/// Deserialize `Cow<'_, str>`.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Cow<'de, str>, D::Error>
where
D: Deserializer<'de>,
{
CowStr::deserialize(deserializer).map(|value| value.into_inner())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn borrowed() {
struct Test(u32);

impl<'de> Deserialize<'de> for Test {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = CowStr::deserialize(deserializer)?;
assert!(matches!(s.0, Cow::Borrowed(_)));
Ok(Test(s.parse().unwrap()))
}
}

let v = serde_json::from_str::<Test>("\"2\"").unwrap();
assert_eq!(v.0, 2);
}

#[test]
fn owned() {
struct Test(u32);

impl<'de> Deserialize<'de> for Test {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = CowStr::deserialize(deserializer)?;
assert!(matches!(s.0, Cow::Owned(_)));
Ok(Test(s.parse().unwrap()))
}
}

let json_value = serde_json::from_str::<serde_json::Value>("\"2\"").unwrap();
let v = serde_json::from_value::<Test>(json_value).unwrap();
assert_eq!(v.0, 2);
}
}
4 changes: 2 additions & 2 deletions proto/src/serializers/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
//! and [`Display`] to convert from or into string. Note this can be used for
//! all primitive data types.

use alloc::borrow::Cow;
use core::fmt::Display;
use core::str::FromStr;

use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize string into T
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
Expand All @@ -17,7 +17,7 @@ where
T: FromStr,
<T as FromStr>::Err: Display,
{
<Cow<'_, str>>::deserialize(deserializer)?
CowStr::deserialize(deserializer)?
.parse::<T>()
.map_err(D::Error::custom)
}
Expand Down
4 changes: 2 additions & 2 deletions proto/src/serializers/from_str_allow_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
//! [`from_str`]: super::from_str
//! [`allow_null`]: super::allow_null

use alloc::borrow::Cow;
use core::fmt::Display;
use core::str::FromStr;

use serde::{de::Error as _, Deserialize, Deserializer, Serializer};

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize a nullable string into T
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
Expand All @@ -26,7 +26,7 @@ where
T: FromStr + Default,
<T as FromStr>::Err: Display,
{
match <Option<Cow<'_, str>>>::deserialize(deserializer)? {
match <Option<CowStr>>::deserialize(deserializer)? {
Some(s) => s.parse::<T>().map_err(D::Error::custom),
None => Ok(T::default()),
}
Expand Down
4 changes: 2 additions & 2 deletions proto/src/serializers/optional_from_str.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! De/serialize an optional type that must be converted from/to a string.

use alloc::borrow::Cow;
use core::{fmt::Display, str::FromStr};

use serde::{de::Error, Deserialize, Deserializer, Serializer};

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
Expand All @@ -24,7 +24,7 @@ where
T: FromStr,
T::Err: Display,
{
let s = match Option::<Cow<'_, str>>::deserialize(deserializer)? {
let s = match Option::<CowStr>::deserialize(deserializer)? {
Some(s) => s,
None => return Ok(None),
};
Expand Down
3 changes: 2 additions & 1 deletion proto/src/serializers/time_duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use core::time::Duration;
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize string into Duration
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?
let value = CowStr::deserialize(deserializer)?
.parse::<u64>()
.map_err(|e| D::Error::custom(format!("{e}")))?;

Expand Down
6 changes: 4 additions & 2 deletions proto/src/serializers/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use time::{
format_description::well_known::Rfc3339 as Rfc3339Format, macros::offset, OffsetDateTime,
};

use crate::{google::protobuf::Timestamp, prelude::*};
use crate::google::protobuf::Timestamp;
use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Helper struct to serialize and deserialize Timestamp into an RFC3339-compatible string
/// This is required because the serde `with` attribute is only available to fields of a struct but
Expand All @@ -32,7 +34,7 @@ pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
where
D: Deserializer<'de>,
{
let value_string = String::deserialize(deserializer)?;
let value_string = CowStr::deserialize(deserializer)?;
let t = OffsetDateTime::parse(&value_string, &Rfc3339Format).map_err(D::Error::custom)?;
let t = t.to_offset(offset!(UTC));
if !matches!(t.year(), 1..=9999) {
Expand Down
3 changes: 2 additions & 1 deletion proto/src/serializers/txs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use subtle_encoding::base64;

use crate::prelude::*;
use crate::serializers::cow_str::CowStr;

/// Deserialize transactions into `Vec<Vec<u8>>`
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let value_vec_base64string = Option::<Vec<String>>::deserialize(deserializer)?;
let value_vec_base64string = Option::<Vec<CowStr>>::deserialize(deserializer)?;
if value_vec_base64string.is_none() {
return Ok(Vec::new());
}
Expand Down
Loading