diff --git a/proto/src/serializers.rs b/proto/src/serializers.rs index c4db03f49..4f0df8fda 100644 --- a/proto/src/serializers.rs +++ b/proto/src/serializers.rs @@ -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; diff --git a/proto/src/serializers/bytes.rs b/proto/src/serializers/bytes.rs index f27ce4c03..4880cfccf 100644 --- a/proto/src/serializers/bytes.rs +++ b/proto/src/serializers/bytes.rs @@ -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` pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - let string = Option::::deserialize(deserializer)?.unwrap_or_default(); + let string = Option::::deserialize(deserializer)?.unwrap_or_default(); hex::decode_upper(&string) .or_else(|_| hex::decode(&string)) .map_err(serde::de::Error::custom) @@ -36,6 +37,7 @@ pub mod base64string { use subtle_encoding::base64; use crate::prelude::*; + use crate::serializers::cow_str::CowStr; /// Deserialize base64string into `Vec` pub fn deserialize<'de, D, T>(deserializer: D) -> Result @@ -43,7 +45,7 @@ pub mod base64string { D: Deserializer<'de>, Vec: Into, { - let s = Option::::deserialize(deserializer)?.unwrap_or_default(); + let s = Option::::deserialize(deserializer)?.unwrap_or_default(); let v = base64::decode(s).map_err(serde::de::Error::custom)?; Ok(v.into()) } @@ -53,7 +55,7 @@ pub mod base64string { where D: Deserializer<'de>, { - let s = Option::::deserialize(deserializer)?.unwrap_or_default(); + let s = Option::::deserialize(deserializer)?.unwrap_or_default(); String::from_utf8(base64::decode(s).map_err(serde::de::Error::custom)?) .map_err(serde::de::Error::custom) } @@ -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>` pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { - Option::>::deserialize(deserializer)? + Option::>::deserialize(deserializer)? .unwrap_or_default() .into_iter() .map(|s| base64::decode(s).map_err(serde::de::Error::custom)) @@ -111,13 +114,14 @@ pub mod option_base64string { use subtle_encoding::base64; use crate::prelude::*; + use crate::serializers::cow_str::CowStr; /// Deserialize `Option` into `Vec` or null pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - let s = Option::::deserialize(deserializer)?.unwrap_or_default(); + let s = Option::::deserialize(deserializer)?.unwrap_or_default(); base64::decode(s).map_err(serde::de::Error::custom) } @@ -138,6 +142,7 @@ pub mod string { use serde::{Deserialize, Deserializer, Serializer}; use crate::prelude::*; + use crate::serializers::cow_str::CowStr; /// Deserialize string into `Vec` #[allow(dead_code)] @@ -145,7 +150,7 @@ pub mod string { where D: Deserializer<'de>, { - let string = Option::::deserialize(deserializer)?.unwrap_or_default(); + let string = Option::::deserialize(deserializer)?.unwrap_or_default(); Ok(string.as_bytes().to_vec()) } diff --git a/proto/src/serializers/cow_str.rs b/proto/src/serializers/cow_str.rs new file mode 100644 index 000000000..de18782bc --- /dev/null +++ b/proto/src/serializers/cow_str.rs @@ -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(deserializer: D) -> Result + 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(self, value: &'de str) -> Result + where + E: de::Error, + { + Ok(CowStr(Cow::Borrowed(value))) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(CowStr(Cow::Owned(value.to_owned()))) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + Ok(CowStr(Cow::Owned(value))) + } + } + + deserializer.deserialize_str(Visitor) + } +} + +impl<'a> Serialize for CowStr<'a> { + fn serialize(&self, serializer: S) -> Result + 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 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> for Cow<'a, str> { + fn from(value: CowStr<'a>) -> Self { + value.0 + } +} + +impl<'a> From> 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 +where + S: Serializer, +{ + serializer.serialize_str(value) +} + +/// Deserialize `Cow<'_, str>`. +pub fn deserialize<'de, D>(deserializer: D) -> Result, 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(deserializer: D) -> Result + 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::("\"2\"").unwrap(); + assert_eq!(v.0, 2); + } + + #[test] + fn owned() { + struct Test(u32); + + impl<'de> Deserialize<'de> for Test { + fn deserialize(deserializer: D) -> Result + 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::("\"2\"").unwrap(); + let v = serde_json::from_value::(json_value).unwrap(); + assert_eq!(v.0, 2); + } +} diff --git a/proto/src/serializers/from_str.rs b/proto/src/serializers/from_str.rs index 76579bea3..ccfefa4dd 100644 --- a/proto/src/serializers/from_str.rs +++ b/proto/src/serializers/from_str.rs @@ -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 @@ -17,7 +17,7 @@ where T: FromStr, ::Err: Display, { - >::deserialize(deserializer)? + CowStr::deserialize(deserializer)? .parse::() .map_err(D::Error::custom) } diff --git a/proto/src/serializers/from_str_allow_null.rs b/proto/src/serializers/from_str_allow_null.rs index aaa5598b2..34e371980 100644 --- a/proto/src/serializers/from_str_allow_null.rs +++ b/proto/src/serializers/from_str_allow_null.rs @@ -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 @@ -26,7 +26,7 @@ where T: FromStr + Default, ::Err: Display, { - match >>::deserialize(deserializer)? { + match >::deserialize(deserializer)? { Some(s) => s.parse::().map_err(D::Error::custom), None => Ok(T::default()), } diff --git a/proto/src/serializers/optional_from_str.rs b/proto/src/serializers/optional_from_str.rs index b6152074b..c81af13c5 100644 --- a/proto/src/serializers/optional_from_str.rs +++ b/proto/src/serializers/optional_from_str.rs @@ -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(value: &Option, serializer: S) -> Result where @@ -24,7 +24,7 @@ where T: FromStr, T::Err: Display, { - let s = match Option::>::deserialize(deserializer)? { + let s = match Option::::deserialize(deserializer)? { Some(s) => s, None => return Ok(None), }; diff --git a/proto/src/serializers/time_duration.rs b/proto/src/serializers/time_duration.rs index b8af33043..bc0afd2f9 100644 --- a/proto/src/serializers/time_duration.rs +++ b/proto/src/serializers/time_duration.rs @@ -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 where D: Deserializer<'de>, { - let value = String::deserialize(deserializer)? + let value = CowStr::deserialize(deserializer)? .parse::() .map_err(|e| D::Error::custom(format!("{e}")))?; diff --git a/proto/src/serializers/timestamp.rs b/proto/src/serializers/timestamp.rs index 032d59345..03e7c8be2 100644 --- a/proto/src/serializers/timestamp.rs +++ b/proto/src/serializers/timestamp.rs @@ -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 @@ -32,7 +34,7 @@ pub fn deserialize<'de, D>(deserializer: D) -> Result 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) { diff --git a/proto/src/serializers/txs.rs b/proto/src/serializers/txs.rs index 44594c743..253f9ecc6 100644 --- a/proto/src/serializers/txs.rs +++ b/proto/src/serializers/txs.rs @@ -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>` pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { - let value_vec_base64string = Option::>::deserialize(deserializer)?; + let value_vec_base64string = Option::>::deserialize(deserializer)?; if value_vec_base64string.is_none() { return Ok(Vec::new()); } diff --git a/tendermint/src/hash.rs b/tendermint/src/hash.rs index 9926a0ab3..9e3255eee 100644 --- a/tendermint/src/hash.rs +++ b/tendermint/src/hash.rs @@ -8,6 +8,7 @@ use core::{ use bytes::Bytes; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use subtle_encoding::{base64, Encoding, Hex}; +use tendermint_proto::serializers::cow_str::CowStr; use tendermint_proto::Protobuf; use crate::{error::Error, prelude::*}; @@ -165,12 +166,12 @@ impl FromStr for Hash { impl<'de> Deserialize<'de> for Hash { fn deserialize>(deserializer: D) -> Result { - let hex = <&str>::deserialize(deserializer)?; + let hex = CowStr::deserialize(deserializer)?; if hex.is_empty() { Err(D::Error::custom("empty hash")) } else { - Ok(Self::from_str(hex).map_err(|e| D::Error::custom(format!("{e}")))?) + Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{e}")))?) } } } @@ -199,8 +200,8 @@ pub mod allow_empty { where D: Deserializer<'de>, { - let hex = <&str>::deserialize(deserializer)?; - Hash::from_str(hex).map_err(serde::de::Error::custom) + let hex = CowStr::deserialize(deserializer)?; + Hash::from_str(&hex).map_err(serde::de::Error::custom) } } @@ -300,15 +301,22 @@ mod tests { use super::*; #[derive(Debug, serde::Deserialize)] - struct Test { + struct AppHashTest { #[serde(default)] #[serde(with = "crate::serializers::apphash")] pub app_hash: AppHash, } + #[derive(Debug, serde::Deserialize)] + struct HashTest { + hash: Hash, + #[serde(with = "super::allow_empty")] + empty_hash: Hash, + } + #[test] fn apphash_decode_base64() { - let test = serde_json::from_str::( + let test = serde_json::from_str::( r#"{"app_hash":"MfX9f+bYoI8IioRb4YT/8/VhPvtNjgWFgTi4mmMSkBc="}"#, ) .unwrap(); @@ -325,7 +333,7 @@ mod tests { #[test] fn apphash_decode_hex() { - let test = serde_json::from_str::( + let test = serde_json::from_str::( r#"{"app_hash":"31F5FD7FE6D8A08F088A845BE184FFF3F5613EFB4D8E05858138B89A63129017"}"#, ) .unwrap(); @@ -339,4 +347,28 @@ mod tests { ] ); } + + #[test] + fn hash_decode_hex() { + let s = r#"{ + "hash": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", + "empty_hash": "" + }"#; + + let expected_hash = &[ + 0x9F, 0x86, 0xD0, 0x81, 0x88, 0x4C, 0x7D, 0x65, 0x9A, 0x2F, 0xEA, 0xA0, 0xC5, 0x5A, + 0xD0, 0x15, 0xA3, 0xBF, 0x4F, 0x1B, 0x2B, 0x0B, 0x82, 0x2C, 0xD1, 0x5D, 0x6C, 0x15, + 0xB0, 0xF0, 0x0A, 0x08, + ]; + + let test = serde_json::from_str::(s).unwrap(); + assert_eq!(test.hash.as_ref(), expected_hash); + assert_eq!(test.empty_hash, Hash::None); + + // Test issue 1474 + let json_value = serde_json::from_str::(s).unwrap(); + let test = serde_json::from_value::(json_value).unwrap(); + assert_eq!(test.hash.as_ref(), expected_hash); + assert_eq!(test.empty_hash, Hash::None); + } }