diff --git a/Cargo.lock b/Cargo.lock index 5d7abca..b120134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -114,6 +141,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "getrandom" version = "0.2.15" @@ -127,12 +166,44 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.72" @@ -154,6 +225,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "minicov" version = "0.3.7" @@ -233,6 +310,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -268,6 +351,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_str_helpers" version = "0.1.2" @@ -278,6 +373,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "shlex" version = "1.3.0" @@ -289,9 +397,13 @@ name = "strict_encoding" version = "2.8.1" dependencies = [ "amplify", + "ciborium", "getrandom", + "half", "rand", "serde", + "serde_json", + "serde_yaml", "strict_encoding_derive", "strict_encoding_test", "wasm-bindgen", @@ -358,6 +470,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8b105e9..134ecc7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -20,6 +20,10 @@ serde_crate = { workspace = true, optional = true } [dev-dependencies] amplify = { workspace = true, features = ["proc_attr", "hex"] } +ciborium = "0.2.2" +half = "<2.5.0" +serde_json = "1.0.140" +serde_yaml = "0.9.34" strict_encoding_test = { path = "./test_helpers" } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/rust/src/reader.rs b/rust/src/reader.rs index 152c018..0129cde 100644 --- a/rust/src/reader.rs +++ b/rust/src/reader.rs @@ -234,7 +234,7 @@ pub struct TupleReader<'parent, R: ReadRaw> { parent: &'parent mut StrictReader, } -impl<'parent, R: ReadRaw> ReadTuple for TupleReader<'parent, R> { +impl ReadTuple for TupleReader<'_, R> { fn read_field(&mut self) -> Result { self.read_fields += 1; T::strict_decode(self.parent) @@ -247,7 +247,7 @@ pub struct StructReader<'parent, R: ReadRaw> { parent: &'parent mut StrictReader, } -impl<'parent, R: ReadRaw> ReadStruct for StructReader<'parent, R> { +impl ReadStruct for StructReader<'_, R> { fn read_field(&mut self, field: FieldName) -> Result { self.named_fields.push(field); T::strict_decode(self.parent) diff --git a/rust/src/util.rs b/rust/src/util.rs index 4daa095..722a895 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -84,13 +84,84 @@ impl Display for Sizing { } #[derive(Clone, Eq, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] pub struct Variant { pub name: VariantName, pub tag: u8, } impl_strict_struct!(Variant, STRICT_TYPES_LIB; name, tag); +#[cfg(feature = "serde")] +// The manual serde implementation is needed due to `Variant` bein used as a key in maps (like enum +// or union fields), and serde text implementations such as JSON can't serialize map keys if they +// are not strings. This solves the issue, by putting string serialization of `Variant` for +// human-readable serializers +mod _serde { + use std::str::FromStr; + + use serde_crate::ser::SerializeStruct; + use serde_crate::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::*; + + impl Serialize for Variant { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + if serializer.is_human_readable() { + serializer.serialize_str(&format!("{}:{}", self.name, self.tag)) + } else { + let mut s = serializer.serialize_struct("Variant", 2)?; + s.serialize_field("name", &self.name)?; + s.serialize_field("tag", &self.tag)?; + s.end() + } + } + } + + impl<'de> Deserialize<'de> for Variant { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + let mut split = s.split(':'); + let (name, tag) = (split.next(), split.next()); + if split.next().is_some() { + return Err(serde::de::Error::custom(format!( + "Invalid variant format: '{}'. Expected 'name:tag'", + s + ))); + } + match (name, tag) { + (Some(name), Some(tag)) => { + let name = VariantName::from_str(name).map_err(|e| { + serde::de::Error::custom(format!("Invalid variant name: {}", e)) + })?; + let tag = tag.parse::().map_err(|e| { + serde::de::Error::custom(format!("Invalid variant tag: {}", e)) + })?; + Ok(Variant { name, tag }) + } + _ => Err(serde::de::Error::custom(format!( + "Invalid variant format: '{}'. Expected 'name:tag'", + s + ))), + } + } else { + #[cfg_attr( + feature = "serde", + derive(Deserialize), + serde(crate = "serde_crate", rename = "Variant") + )] + struct VariantFields { + name: VariantName, + tag: u8, + } + let VariantFields { name, tag } = VariantFields::deserialize(deserializer)?; + Ok(Variant { name, tag }) + } + } + } +} + impl Variant { pub fn named(tag: u8, name: VariantName) -> Variant { Variant { name, tag } } @@ -138,3 +209,34 @@ impl Display for Variant { Ok(()) } } + +#[cfg(test)] +mod test { + #![allow(unused)] + + use std::io::Cursor; + + use crate::*; + + #[cfg(feature = "serde")] + #[test] + fn variant_serde_roundtrip() { + let variant_orig = Variant::strict_dumb(); + + // CBOR + let mut buf = Vec::new(); + ciborium::into_writer(&variant_orig, &mut buf).unwrap(); + let variant_post: Variant = ciborium::from_reader(Cursor::new(&buf)).unwrap(); + assert_eq!(variant_orig, variant_post); + + // JSON + let variant_str = serde_json::to_string(&variant_orig).unwrap(); + let variant_post: Variant = serde_json::from_str(&variant_str).unwrap(); + assert_eq!(variant_orig, variant_post); + + // YAML + let variant_str = serde_yaml::to_string(&variant_orig).unwrap(); + let variant_post: Variant = serde_yaml::from_str(&variant_str).unwrap(); + assert_eq!(variant_orig, variant_post); + } +}