Skip to content

Commit

Permalink
Added serde feature
Browse files Browse the repository at this point in the history
  • Loading branch information
d-sonuga committed Dec 6, 2024
1 parent 237ceed commit ad61992
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
8 changes: 8 additions & 0 deletions piecrust-uplink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions piecrust-uplink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
137 changes: 137 additions & 0 deletions piecrust-uplink/src/serde_support.rs
Original file line number Diff line number Diff line change
@@ -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<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = hex::encode(self.to_bytes());
serializer.serialize_str(&s)
}
}

impl<'de> Deserialize<'de> for ContractId {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
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<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
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<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
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<A: MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
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,
)
}
}
45 changes: 45 additions & 0 deletions piecrust-uplink/tests/serde.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit ad61992

Please sign in to comment.