Skip to content

Commit 71934af

Browse files
authored
Merge pull request #48 from zoedberg/change_variant_serde_serialization
change Variant serde (de|)serialization
2 parents f7be0a7 + ac23647 commit 71934af

File tree

4 files changed

+227
-3
lines changed

4 files changed

+227
-3
lines changed

Cargo.lock

Lines changed: 118 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ serde_crate = { workspace = true, optional = true }
2020

2121
[dev-dependencies]
2222
amplify = { workspace = true, features = ["proc_attr", "hex"] }
23+
ciborium = "0.2.2"
24+
half = "<2.5.0"
25+
serde_json = "1.0.140"
26+
serde_yaml = "0.9.34"
2327
strict_encoding_test = { path = "./test_helpers" }
2428

2529
[target.'cfg(target_arch = "wasm32")'.dependencies]

rust/src/reader.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ pub struct TupleReader<'parent, R: ReadRaw> {
234234
parent: &'parent mut StrictReader<R>,
235235
}
236236

237-
impl<'parent, R: ReadRaw> ReadTuple for TupleReader<'parent, R> {
237+
impl<R: ReadRaw> ReadTuple for TupleReader<'_, R> {
238238
fn read_field<T: StrictDecode>(&mut self) -> Result<T, DecodeError> {
239239
self.read_fields += 1;
240240
T::strict_decode(self.parent)
@@ -247,7 +247,7 @@ pub struct StructReader<'parent, R: ReadRaw> {
247247
parent: &'parent mut StrictReader<R>,
248248
}
249249

250-
impl<'parent, R: ReadRaw> ReadStruct for StructReader<'parent, R> {
250+
impl<R: ReadRaw> ReadStruct for StructReader<'_, R> {
251251
fn read_field<T: StrictDecode>(&mut self, field: FieldName) -> Result<T, DecodeError> {
252252
self.named_fields.push(field);
253253
T::strict_decode(self.parent)

rust/src/util.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,84 @@ impl Display for Sizing {
8484
}
8585

8686
#[derive(Clone, Eq, Debug)]
87-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
8887
pub struct Variant {
8988
pub name: VariantName,
9089
pub tag: u8,
9190
}
9291
impl_strict_struct!(Variant, STRICT_TYPES_LIB; name, tag);
9392

93+
#[cfg(feature = "serde")]
94+
// The manual serde implementation is needed due to `Variant` bein used as a key in maps (like enum
95+
// or union fields), and serde text implementations such as JSON can't serialize map keys if they
96+
// are not strings. This solves the issue, by putting string serialization of `Variant` for
97+
// human-readable serializers
98+
mod _serde {
99+
use std::str::FromStr;
100+
101+
use serde_crate::ser::SerializeStruct;
102+
use serde_crate::{Deserialize, Deserializer, Serialize, Serializer};
103+
104+
use super::*;
105+
106+
impl Serialize for Variant {
107+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108+
where S: Serializer {
109+
if serializer.is_human_readable() {
110+
serializer.serialize_str(&format!("{}:{}", self.name, self.tag))
111+
} else {
112+
let mut s = serializer.serialize_struct("Variant", 2)?;
113+
s.serialize_field("name", &self.name)?;
114+
s.serialize_field("tag", &self.tag)?;
115+
s.end()
116+
}
117+
}
118+
}
119+
120+
impl<'de> Deserialize<'de> for Variant {
121+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122+
where D: Deserializer<'de> {
123+
if deserializer.is_human_readable() {
124+
let s = String::deserialize(deserializer)?;
125+
let mut split = s.split(':');
126+
let (name, tag) = (split.next(), split.next());
127+
if split.next().is_some() {
128+
return Err(serde::de::Error::custom(format!(
129+
"Invalid variant format: '{}'. Expected 'name:tag'",
130+
s
131+
)));
132+
}
133+
match (name, tag) {
134+
(Some(name), Some(tag)) => {
135+
let name = VariantName::from_str(name).map_err(|e| {
136+
serde::de::Error::custom(format!("Invalid variant name: {}", e))
137+
})?;
138+
let tag = tag.parse::<u8>().map_err(|e| {
139+
serde::de::Error::custom(format!("Invalid variant tag: {}", e))
140+
})?;
141+
Ok(Variant { name, tag })
142+
}
143+
_ => Err(serde::de::Error::custom(format!(
144+
"Invalid variant format: '{}'. Expected 'name:tag'",
145+
s
146+
))),
147+
}
148+
} else {
149+
#[cfg_attr(
150+
feature = "serde",
151+
derive(Deserialize),
152+
serde(crate = "serde_crate", rename = "Variant")
153+
)]
154+
struct VariantFields {
155+
name: VariantName,
156+
tag: u8,
157+
}
158+
let VariantFields { name, tag } = VariantFields::deserialize(deserializer)?;
159+
Ok(Variant { name, tag })
160+
}
161+
}
162+
}
163+
}
164+
94165
impl Variant {
95166
pub fn named(tag: u8, name: VariantName) -> Variant { Variant { name, tag } }
96167

@@ -138,3 +209,34 @@ impl Display for Variant {
138209
Ok(())
139210
}
140211
}
212+
213+
#[cfg(test)]
214+
mod test {
215+
#![allow(unused)]
216+
217+
use std::io::Cursor;
218+
219+
use crate::*;
220+
221+
#[cfg(feature = "serde")]
222+
#[test]
223+
fn variant_serde_roundtrip() {
224+
let variant_orig = Variant::strict_dumb();
225+
226+
// CBOR
227+
let mut buf = Vec::new();
228+
ciborium::into_writer(&variant_orig, &mut buf).unwrap();
229+
let variant_post: Variant = ciborium::from_reader(Cursor::new(&buf)).unwrap();
230+
assert_eq!(variant_orig, variant_post);
231+
232+
// JSON
233+
let variant_str = serde_json::to_string(&variant_orig).unwrap();
234+
let variant_post: Variant = serde_json::from_str(&variant_str).unwrap();
235+
assert_eq!(variant_orig, variant_post);
236+
237+
// YAML
238+
let variant_str = serde_yaml::to_string(&variant_orig).unwrap();
239+
let variant_post: Variant = serde_yaml::from_str(&variant_str).unwrap();
240+
assert_eq!(variant_orig, variant_post);
241+
}
242+
}

0 commit comments

Comments
 (0)