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 @@
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();

Check failure on line 16 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:16:31 | 16 | let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | = note: `-D elided-lifetimes-in-paths` implied by `-D rust-2018-idioms` = help: to override `-D rust-2018-idioms` add `#[allow(elided_lifetimes_in_paths)]` help: indicate the anonymous lifetime | 16 | let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ Raw Output: proto/src/serializers/bytes.rs:16:31:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:16:31 | 16 | let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | = note: `-D elided-lifetimes-in-paths` implied by `-D rust-2018-idioms` = help: to override `-D rust-2018-idioms` add `#[allow(elided_lifetimes_in_paths)]` help: indicate the anonymous lifetime | 16 | let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ __END__
hex::decode_upper(&string)
.or_else(|_| hex::decode(&string))
.map_err(serde::de::Error::custom)
Expand All @@ -36,14 +37,15 @@
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();

Check failure on line 48 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:48:26 | 48 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 48 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ Raw Output: proto/src/serializers/bytes.rs:48:26:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:48:26 | 48 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 48 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ __END__
let v = base64::decode(s).map_err(serde::de::Error::custom)?;
Ok(v.into())
}
Expand All @@ -53,7 +55,7 @@
where
D: Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default();

Check failure on line 58 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:58:26 | 58 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 58 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ Raw Output: proto/src/serializers/bytes.rs:58:26:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:58:26 | 58 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 58 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ __END__
String::from_utf8(base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
}
Expand All @@ -76,13 +78,14 @@
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)?

Check failure on line 88 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:88:22 | 88 | Option::<Vec<CowStr>>::deserialize(deserializer)? | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 88 | Option::<Vec<CowStr<'_>>>::deserialize(deserializer)? | ++++ Raw Output: proto/src/serializers/bytes.rs:88:22:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:88:22 | 88 | Option::<Vec<CowStr>>::deserialize(deserializer)? | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 88 | Option::<Vec<CowStr<'_>>>::deserialize(deserializer)? | ++++ __END__
.unwrap_or_default()
.into_iter()
.map(|s| base64::decode(s).map_err(serde::de::Error::custom))
Expand Down Expand Up @@ -111,13 +114,14 @@
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();

Check failure on line 124 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:124:26 | 124 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 124 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ Raw Output: proto/src/serializers/bytes.rs:124:26:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:124:26 | 124 | let s = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 124 | let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ __END__
base64::decode(s).map_err(serde::de::Error::custom)
}

Expand All @@ -138,14 +142,15 @@
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();

Check failure on line 153 in proto/src/serializers/bytes.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:153:31 | 153 | let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 153 | let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ Raw Output: proto/src/serializers/bytes.rs:153:31:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/bytes.rs:153:31 | 153 | let string = Option::<CowStr>::deserialize(deserializer)?.unwrap_or_default(); | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 153 | let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default(); | ++++ __END__
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 {

Check failure on line 32 in proto/src/serializers/cow_str.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/cow_str.rs:32:54 | 32 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | -----^^^^^^^^^ | | | expected lifetime parameter | help: indicate the anonymous lifetime | 32 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | ++++ Raw Output: proto/src/serializers/cow_str.rs:32:49:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/cow_str.rs:32:54 | 32 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | -----^^^^^^^^^ | | | expected lifetime parameter | help: indicate the anonymous lifetime | 32 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | ++++ __END__
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 @@
T: FromStr + Default,
<T as FromStr>::Err: Display,
{
match <Option<Cow<'_, str>>>::deserialize(deserializer)? {
match <Option<CowStr>>::deserialize(deserializer)? {

Check failure on line 29 in proto/src/serializers/from_str_allow_null.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/from_str_allow_null.rs:29:19 | 29 | match <Option<CowStr>>::deserialize(deserializer)? { | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 29 | match <Option<CowStr<'_>>>::deserialize(deserializer)? { | ++++ Raw Output: proto/src/serializers/from_str_allow_null.rs:29:19:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/from_str_allow_null.rs:29:19 | 29 | match <Option<CowStr>>::deserialize(deserializer)? { | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 29 | match <Option<CowStr<'_>>>::deserialize(deserializer)? { | ++++ __END__
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 @@
T: FromStr,
T::Err: Display,
{
let s = match Option::<Cow<'_, str>>::deserialize(deserializer)? {
let s = match Option::<CowStr>::deserialize(deserializer)? {

Check failure on line 27 in proto/src/serializers/optional_from_str.rs

View workflow job for this annotation

GitHub Actions / clippy-json-output

[clippy] reported by reviewdog 🐶 error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/optional_from_str.rs:27:28 | 27 | let s = match Option::<CowStr>::deserialize(deserializer)? { | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 27 | let s = match Option::<CowStr<'_>>::deserialize(deserializer)? { | ++++ Raw Output: proto/src/serializers/optional_from_str.rs:27:28:e:error: hidden lifetime parameters in types are deprecated --> proto/src/serializers/optional_from_str.rs:27:28 | 27 | let s = match Option::<CowStr>::deserialize(deserializer)? { | ^^^^^^ expected lifetime parameter | help: indicate the anonymous lifetime | 27 | let s = match Option::<CowStr<'_>>::deserialize(deserializer)? { | ++++ __END__
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
Loading