Skip to content

Commit 062717a

Browse files
authored
fix(cometbft): Fix deserialization from serde_json::Value (#112)
Port-of: cometbft/tendermint-rs#1475 Closes #111
1 parent d26dc71 commit 062717a

File tree

11 files changed

+243
-23
lines changed

11 files changed

+243
-23
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- [cometbft-proto] Intoduce `CowStr` deserializer.
2+
([\#111](https://github.com/cometbft/cometbft-rs/issues/111))
3+
- [cometbft] Fix deserialization from `serde_json::Value`.
4+
([\#111](https://github.com/cometbft/cometbft-rs/issues/111))

cometbft/src/hash.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use core::{
77
};
88

99
use bytes::Bytes;
10+
use cometbft_proto::serializers::cow_str::CowStr;
1011
use cometbft_proto::Protobuf;
1112
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
1213
use subtle_encoding::{base64, Encoding, Hex};
@@ -166,12 +167,12 @@ impl FromStr for Hash {
166167

167168
impl<'de> Deserialize<'de> for Hash {
168169
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
169-
let hex = <&str>::deserialize(deserializer)?;
170+
let hex = CowStr::deserialize(deserializer)?;
170171

171172
if hex.is_empty() {
172173
Err(D::Error::custom("empty hash"))
173174
} else {
174-
Ok(Self::from_str(hex).map_err(|e| D::Error::custom(format!("{e}")))?)
175+
Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{e}")))?)
175176
}
176177
}
177178
}
@@ -200,8 +201,8 @@ pub mod allow_empty {
200201
where
201202
D: Deserializer<'de>,
202203
{
203-
let hex = <&str>::deserialize(deserializer)?;
204-
Hash::from_str(hex).map_err(serde::de::Error::custom)
204+
let hex = CowStr::deserialize(deserializer)?;
205+
Hash::from_str(&hex).map_err(serde::de::Error::custom)
205206
}
206207
}
207208

@@ -301,15 +302,22 @@ mod tests {
301302
use super::*;
302303

303304
#[derive(Debug, serde::Deserialize)]
304-
struct Test {
305+
struct AppHashTest {
305306
#[serde(default)]
306307
#[serde(with = "crate::serializers::apphash")]
307308
pub app_hash: AppHash,
308309
}
309310

311+
#[derive(Debug, serde::Deserialize)]
312+
struct HashTest {
313+
hash: Hash,
314+
#[serde(with = "super::allow_empty")]
315+
empty_hash: Hash,
316+
}
317+
310318
#[test]
311319
fn apphash_decode_base64() {
312-
let test = serde_json::from_str::<Test>(
320+
let test = serde_json::from_str::<AppHashTest>(
313321
r#"{"app_hash":"MfX9f+bYoI8IioRb4YT/8/VhPvtNjgWFgTi4mmMSkBc="}"#,
314322
)
315323
.unwrap();
@@ -326,7 +334,7 @@ mod tests {
326334

327335
#[test]
328336
fn apphash_decode_hex() {
329-
let test = serde_json::from_str::<Test>(
337+
let test = serde_json::from_str::<AppHashTest>(
330338
r#"{"app_hash":"31F5FD7FE6D8A08F088A845BE184FFF3F5613EFB4D8E05858138B89A63129017"}"#,
331339
)
332340
.unwrap();
@@ -340,4 +348,28 @@ mod tests {
340348
]
341349
);
342350
}
351+
352+
#[test]
353+
fn hash_decode_hex() {
354+
let s = r#"{
355+
"hash": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08",
356+
"empty_hash": ""
357+
}"#;
358+
359+
let expected_hash = &[
360+
0x9F, 0x86, 0xD0, 0x81, 0x88, 0x4C, 0x7D, 0x65, 0x9A, 0x2F, 0xEA, 0xA0, 0xC5, 0x5A,
361+
0xD0, 0x15, 0xA3, 0xBF, 0x4F, 0x1B, 0x2B, 0x0B, 0x82, 0x2C, 0xD1, 0x5D, 0x6C, 0x15,
362+
0xB0, 0xF0, 0x0A, 0x08,
363+
];
364+
365+
let test = serde_json::from_str::<HashTest>(s).unwrap();
366+
assert_eq!(test.hash.as_ref(), expected_hash);
367+
assert_eq!(test.empty_hash, Hash::None);
368+
369+
// Test issue 1474
370+
let json_value = serde_json::from_str::<serde_json::Value>(s).unwrap();
371+
let test = serde_json::from_value::<HashTest>(json_value).unwrap();
372+
assert_eq!(test.hash.as_ref(), expected_hash);
373+
assert_eq!(test.empty_hash, Hash::None);
374+
}
343375
}

proto/src/serializers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555

5656
pub mod allow_null;
5757
pub mod bytes;
58+
pub mod cow_str;
5859
pub mod evidence;
5960
pub mod from_str;
6061
pub mod from_str_allow_null;

proto/src/serializers/bytes.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ pub mod hexstring {
66
use subtle_encoding::hex;
77

88
use crate::prelude::*;
9+
use crate::serializers::cow_str::CowStr;
910

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

3839
use crate::prelude::*;
40+
use crate::serializers::cow_str::CowStr;
3941

4042
/// Deserialize base64string into `Vec<u8>`
4143
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
4244
where
4345
D: Deserializer<'de>,
4446
Vec<u8>: Into<T>,
4547
{
46-
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
48+
let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default();
4749
let v = base64::decode(s).map_err(serde::de::Error::custom)?;
4850
Ok(v.into())
4951
}
@@ -53,7 +55,7 @@ pub mod base64string {
5355
where
5456
D: Deserializer<'de>,
5557
{
56-
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
58+
let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default();
5759
String::from_utf8(base64::decode(s).map_err(serde::de::Error::custom)?)
5860
.map_err(serde::de::Error::custom)
5961
}
@@ -76,13 +78,14 @@ pub mod vec_base64string {
7678
use subtle_encoding::base64;
7779

7880
use crate::prelude::*;
81+
use crate::serializers::cow_str::CowStr;
7982

8083
/// Deserialize array into `Vec<Vec<u8>>`
8184
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
8285
where
8386
D: Deserializer<'de>,
8487
{
85-
Option::<Vec<String>>::deserialize(deserializer)?
88+
Option::<Vec<CowStr<'_>>>::deserialize(deserializer)?
8689
.unwrap_or_default()
8790
.into_iter()
8891
.map(|s| base64::decode(s).map_err(serde::de::Error::custom))
@@ -111,13 +114,14 @@ pub mod option_base64string {
111114
use subtle_encoding::base64;
112115

113116
use crate::prelude::*;
117+
use crate::serializers::cow_str::CowStr;
114118

115119
/// Deserialize `Option<base64string>` into `Vec<u8>` or null
116120
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
117121
where
118122
D: Deserializer<'de>,
119123
{
120-
let s = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
124+
let s = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default();
121125
base64::decode(s).map_err(serde::de::Error::custom)
122126
}
123127

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

140144
use crate::prelude::*;
145+
use crate::serializers::cow_str::CowStr;
141146

142147
/// Deserialize string into `Vec<u8>`
143148
#[allow(dead_code)]
144149
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
145150
where
146151
D: Deserializer<'de>,
147152
{
148-
let string = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
153+
let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default();
149154
Ok(string.as_bytes().to_vec())
150155
}
151156

proto/src/serializers/cow_str.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! Wrapper `Cow<'_, str>` for deserializing without allocation.
2+
//!
3+
//! This is a workaround for [serde's issue 1852](https://github.com/serde-rs/serde/issues/1852).
4+
5+
use alloc::borrow::{Cow, ToOwned};
6+
use alloc::string::String;
7+
use core::fmt::{self, Debug, Display, Formatter};
8+
use core::ops::Deref;
9+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
10+
11+
/// Wrapper `Cow<'_, str>` for deserializing without allocation.
12+
#[derive(Default)]
13+
pub struct CowStr<'a>(Cow<'a, str>);
14+
15+
impl<'a> CowStr<'a> {
16+
/// Convert into `Cow<'a, str>`.
17+
pub fn into_inner(self) -> Cow<'a, str> {
18+
self.0
19+
}
20+
}
21+
22+
impl<'de> Deserialize<'de> for CowStr<'de> {
23+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24+
where
25+
D: Deserializer<'de>,
26+
{
27+
struct Visitor;
28+
29+
impl<'de> serde::de::Visitor<'de> for Visitor {
30+
type Value = CowStr<'de>;
31+
32+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33+
formatter.write_str("a string")
34+
}
35+
36+
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
37+
where
38+
E: de::Error,
39+
{
40+
Ok(CowStr(Cow::Borrowed(value)))
41+
}
42+
43+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
44+
where
45+
E: de::Error,
46+
{
47+
Ok(CowStr(Cow::Owned(value.to_owned())))
48+
}
49+
50+
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
51+
where
52+
E: de::Error,
53+
{
54+
Ok(CowStr(Cow::Owned(value)))
55+
}
56+
}
57+
58+
deserializer.deserialize_str(Visitor)
59+
}
60+
}
61+
62+
impl Serialize for CowStr<'_> {
63+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64+
where
65+
S: Serializer,
66+
{
67+
serializer.serialize_str(&self.0)
68+
}
69+
}
70+
71+
impl Debug for CowStr<'_> {
72+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
73+
<&str as Debug>::fmt(&&*self.0, f)
74+
}
75+
}
76+
77+
impl Display for CowStr<'_> {
78+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79+
<&str as Display>::fmt(&&*self.0, f)
80+
}
81+
}
82+
83+
impl Deref for CowStr<'_> {
84+
type Target = str;
85+
86+
fn deref(&self) -> &Self::Target {
87+
&self.0
88+
}
89+
}
90+
91+
impl AsRef<str> for CowStr<'_> {
92+
fn as_ref(&self) -> &str {
93+
&self.0
94+
}
95+
}
96+
97+
impl AsRef<[u8]> for CowStr<'_> {
98+
fn as_ref(&self) -> &[u8] {
99+
self.0.as_bytes()
100+
}
101+
}
102+
103+
impl<'a> From<CowStr<'a>> for Cow<'a, str> {
104+
fn from(value: CowStr<'a>) -> Self {
105+
value.0
106+
}
107+
}
108+
109+
impl<'a> From<Cow<'a, str>> for CowStr<'a> {
110+
fn from(value: Cow<'a, str>) -> Self {
111+
CowStr(value)
112+
}
113+
}
114+
115+
/// Serialize `Cow<'_, str>`.
116+
#[allow(clippy::ptr_arg)]
117+
pub fn serialize<S>(value: &Cow<'_, str>, serializer: S) -> Result<S::Ok, S::Error>
118+
where
119+
S: Serializer,
120+
{
121+
serializer.serialize_str(value)
122+
}
123+
124+
/// Deserialize `Cow<'_, str>`.
125+
pub fn deserialize<'de, D>(deserializer: D) -> Result<Cow<'de, str>, D::Error>
126+
where
127+
D: Deserializer<'de>,
128+
{
129+
CowStr::deserialize(deserializer).map(|value| value.into_inner())
130+
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use super::*;
135+
136+
#[test]
137+
fn borrowed() {
138+
struct Test(u32);
139+
140+
impl<'de> Deserialize<'de> for Test {
141+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142+
where
143+
D: Deserializer<'de>,
144+
{
145+
let s = CowStr::deserialize(deserializer)?;
146+
assert!(matches!(s.0, Cow::Borrowed(_)));
147+
Ok(Test(s.parse().unwrap()))
148+
}
149+
}
150+
151+
let v = serde_json::from_str::<Test>("\"2\"").unwrap();
152+
assert_eq!(v.0, 2);
153+
}
154+
155+
#[test]
156+
fn owned() {
157+
struct Test(u32);
158+
159+
impl<'de> Deserialize<'de> for Test {
160+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161+
where
162+
D: Deserializer<'de>,
163+
{
164+
let s = CowStr::deserialize(deserializer)?;
165+
assert!(matches!(s.0, Cow::Owned(_)));
166+
Ok(Test(s.parse().unwrap()))
167+
}
168+
}
169+
170+
let json_value = serde_json::from_str::<serde_json::Value>("\"2\"").unwrap();
171+
let v = serde_json::from_value::<Test>(json_value).unwrap();
172+
assert_eq!(v.0, 2);
173+
}
174+
}

proto/src/serializers/from_str.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
//! and [`Display`] to convert from or into string. Note this can be used for
33
//! all primitive data types.
44
5-
use alloc::borrow::Cow;
65
use core::fmt::Display;
76
use core::str::FromStr;
87

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

1110
use crate::prelude::*;
11+
use crate::serializers::cow_str::CowStr;
1212

1313
/// Deserialize string into T
1414
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
@@ -17,7 +17,7 @@ where
1717
T: FromStr,
1818
<T as FromStr>::Err: Display,
1919
{
20-
<Cow<'_, str>>::deserialize(deserializer)?
20+
CowStr::deserialize(deserializer)?
2121
.parse::<T>()
2222
.map_err(D::Error::custom)
2323
}

0 commit comments

Comments
 (0)