From ad61992beec55b3f40397278849250b3be829952 Mon Sep 17 00:00:00 2001 From: Demilade Sonuga Date: Fri, 6 Dec 2024 10:19:13 +0100 Subject: [PATCH] Added serde feature --- piecrust-uplink/Cargo.toml | 8 ++ piecrust-uplink/src/lib.rs | 3 + piecrust-uplink/src/serde_support.rs | 137 +++++++++++++++++++++++++++ piecrust-uplink/tests/serde.rs | 45 +++++++++ 4 files changed, 193 insertions(+) create mode 100644 piecrust-uplink/src/serde_support.rs create mode 100644 piecrust-uplink/tests/serde.rs diff --git a/piecrust-uplink/Cargo.toml b/piecrust-uplink/Cargo.toml index fc4b7a68..af21bb86 100644 --- a/piecrust-uplink/Cargo.toml +++ b/piecrust-uplink/Cargo.toml @@ -16,10 +16,18 @@ license = "MPL-2.0" rkyv = { version = "0.7", default-features = false, features = ["size_32", "alloc", "validation"] } bytecheck = { version = "0.6", default-features = false } dlmalloc = { version = "0.2", optional = true, features = ["global"] } +serde = { version = "1.0", optional = true } +hex = { version = "0.4" , optional = true } +base64 = { version = "0.22", optional = true } +serde_json = { version = "1.0", optional = true } + +[dev-dependencies] +rand = "0.8" [features] abi = [] debug = [] +serde = ["dep:serde", "serde_json", "hex", "base64"] [package.metadata.docs.rs] all-features = true diff --git a/piecrust-uplink/src/lib.rs b/piecrust-uplink/src/lib.rs index 2745ca97..7633d20a 100644 --- a/piecrust-uplink/src/lib.rs +++ b/piecrust-uplink/src/lib.rs @@ -78,6 +78,9 @@ pub use types::*; mod error; pub use error::*; +#[cfg(feature = "serde")] +mod serde_support; + /// How many bytes to use for scratch space when serializing pub const SCRATCH_BUF_BYTES: usize = 1024; diff --git a/piecrust-uplink/src/serde_support.rs b/piecrust-uplink/src/serde_support.rs new file mode 100644 index 00000000..0c762666 --- /dev/null +++ b/piecrust-uplink/src/serde_support.rs @@ -0,0 +1,137 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use alloc::format; +use alloc::string::String; + +use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; +use serde::{ + de::{self, Error, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use crate::{ContractId, Event, CONTRACT_ID_BYTES}; + +impl Serialize for ContractId { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.to_bytes()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for ContractId { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match hex::decode(s) { + Ok(bytes) => { + let bytes: [u8; CONTRACT_ID_BYTES] = bytes.try_into().map_err(|_| Error::custom("Failed to deserialize ContractId: invalid byte length"))?; + Ok(bytes.into()) + } + Err(e) => Err(Error::custom(format!( + "Failed to deserialize ContractId: {e}" + ))), + } + } +} + +impl Serialize for Event { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut struct_ser = serializer.serialize_struct("Event", 3)?; + struct_ser.serialize_field("source", &self.source)?; + struct_ser.serialize_field("topic", &self.topic)?; + struct_ser + .serialize_field("data", &BASE64_STANDARD.encode(&self.data))?; + struct_ser.end() + } +} + +impl<'de> Deserialize<'de> for Event { + fn deserialize>( + deserializer: D, + ) -> Result { + struct StructVisitor; + + impl<'de> Visitor<'de> for StructVisitor { + type Value = Event; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter + .write_str("a struct with fields: source, topic, and data") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let (mut source, mut topic, mut data) = (None, None, None); + while let Some(key) = map.next_key()? { + match key { + "source" => { + if source.is_some() { + return Err(de::Error::duplicate_field( + "source", + )); + } + source = Some(map.next_value()?); + } + "topic" => { + if topic.is_some() { + return Err(de::Error::duplicate_field( + "topic", + )); + } + topic = Some(map.next_value()?); + } + "data" => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = Some(map.next_value()?); + } + field => { + return Err(de::Error::unknown_field( + field, + &["source", "topic", "data"], + )) + } + }; + } + let data: String = + data.ok_or_else(|| de::Error::missing_field("data"))?; + let data = BASE64_STANDARD.decode(data).map_err(|e| { + de::Error::custom(format!( + "failed to base64 decode Event data: {e}" + )) + })?; + Ok(Event { + source: source + .ok_or_else(|| de::Error::missing_field("source"))?, + topic: topic + .ok_or_else(|| de::Error::missing_field("topic"))?, + data, + }) + } + } + + deserializer.deserialize_struct( + "Event", + &["source", "topic", "data"], + StructVisitor, + ) + } +} diff --git a/piecrust-uplink/tests/serde.rs b/piecrust-uplink/tests/serde.rs new file mode 100644 index 00000000..fbd12a45 --- /dev/null +++ b/piecrust-uplink/tests/serde.rs @@ -0,0 +1,45 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +#[cfg(feature = "serde")] +mod serde { + use piecrust_uplink::{ContractId, Event, CONTRACT_ID_BYTES}; + use rand::{rngs::StdRng, RngCore, SeedableRng}; + + fn rand_contract_id(rng: &mut StdRng) -> ContractId { + let mut bytes = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut bytes); + bytes.into() + } + + fn rand_event(rng: &mut StdRng) -> Event { + let mut data = [0; 50]; + rng.fill_bytes(&mut data); + Event { + source: rand_contract_id(rng), + topic: "a-contract-topic".into(), + data: data.into(), + } + } + + #[test] + fn contract_id() { + let mut rng = StdRng::seed_from_u64(0xdead); + let id: ContractId = rand_contract_id(&mut rng); + let ser = serde_json::to_string(&id).unwrap(); + let deser: ContractId = serde_json::from_str(&ser).unwrap(); + assert_eq!(id, deser); + } + + #[test] + fn event() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let event = rand_event(&mut rng); + let ser = serde_json::to_string(&event).unwrap(); + let deser: Event = serde_json::from_str(&ser).unwrap(); + assert_eq!(event, deser); + } +}