Skip to content

Commit

Permalink
Fixing json representation of various types
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Zajkowski committed Nov 26, 2024
1 parent 50bd79c commit 3b4c746
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 81 deletions.
28 changes: 28 additions & 0 deletions node/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use derive_more::From;
use futures::channel::oneshot;
use once_cell::sync::Lazy;
use rand::Rng;
use regex::Regex;
use serde_json::Value;
use tempfile::TempDir;
use tokio::runtime::{self, Runtime};
Expand Down Expand Up @@ -424,6 +425,33 @@ pub fn assert_schema(schema_path: String, actual_schema: String) {
temp_file_path.display()
);
assert_json_eq!(actual_schema, expected_schema);

// Check for the following pattern in the JSON as this points to a byte array or vec (e.g.
// a hash digest) not being represented as a hex-encoded string:
//
// ```json
// "type": "array",
// "items": {
// "type": "integer",
// "format": "uint8",
// "minimum": 0.0
// },
// ```
//
// The type/variant in question (most easily identified from the git diff) might be easily
// fixed via application of a serde attribute, e.g.
// `#[serde(with = "serde_helpers::raw_32_byte_array")]`. It will likely require a
// schemars attribute too, indicating it is a hex-encoded string. See for example
// `TransactionInvocationTarget::Package::addr`.
let schema = fs::read_to_string(&schema_path).unwrap();
let regex = Regex::new(
r#"\s*"type":\s*"array",\s*"items":\s*\{\s*"type":\s*"integer",\s*"format":\s*"uint8",\s*"minimum":\s*0\.0\s*\},"#
).unwrap();
assert!(
!regex.is_match(&schema),
"seems like a byte array is not hex-encoded - see comment in `json_schema_check` for \
further info"
);
}

#[cfg(test)]
Expand Down
25 changes: 3 additions & 22 deletions resources/test/sse_data_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2449,14 +2449,7 @@
],
"properties": {
"Purse": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"maxItems": 32,
"minItems": 32
"type": "string"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -3245,14 +3238,7 @@
],
"properties": {
"DelegatedPurse": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"maxItems": 32,
"minItems": 32
"type": "string"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -3937,12 +3923,7 @@
],
"properties": {
"RawBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
"type": "string"
}
},
"additionalProperties": false
Expand Down
21 changes: 16 additions & 5 deletions types/src/stored_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub enum StoredValue {
EntryPoint(EntryPointValue),
/// Raw bytes. Similar to a [`crate::StoredValue::CLValue`] but does not incur overhead of a
/// [`crate::CLValue`] and [`crate::CLType`].
RawBytes(Vec<u8>),
RawBytes(#[cfg_attr(feature = "json-schema", schemars(with = "String"))] Vec<u8>),
}

impl StoredValue {
Expand Down Expand Up @@ -915,7 +915,7 @@ mod serde_helpers {
Reservation(&'a PrepaidKind),
EntryPoint(&'a EntryPointValue),
/// Raw bytes.
RawBytes(&'a Vec<u8>),
RawBytes(Bytes),
}

#[derive(Deserialize)]
Expand All @@ -940,7 +940,7 @@ mod serde_helpers {
NamedKey(NamedKeyValue),
EntryPoint(EntryPointValue),
/// Raw bytes.
RawBytes(Vec<u8>),
RawBytes(Bytes),
}

impl<'a> From<&'a StoredValue> for HumanReadableSerHelper<'a> {
Expand Down Expand Up @@ -976,7 +976,9 @@ mod serde_helpers {
StoredValue::NamedKey(payload) => HumanReadableSerHelper::NamedKey(payload),
StoredValue::Prepaid(payload) => HumanReadableSerHelper::Reservation(payload),
StoredValue::EntryPoint(payload) => HumanReadableSerHelper::EntryPoint(payload),
StoredValue::RawBytes(bytes) => HumanReadableSerHelper::RawBytes(bytes),
StoredValue::RawBytes(bytes) => {
HumanReadableSerHelper::RawBytes(bytes.as_slice().into())
}
}
}
}
Expand Down Expand Up @@ -1015,7 +1017,7 @@ mod serde_helpers {
}
HumanReadableDeserHelper::NamedKey(payload) => StoredValue::NamedKey(payload),
HumanReadableDeserHelper::EntryPoint(payload) => StoredValue::EntryPoint(payload),
HumanReadableDeserHelper::RawBytes(bytes) => StoredValue::RawBytes(bytes),
HumanReadableDeserHelper::RawBytes(bytes) => StoredValue::RawBytes(bytes.into()),
}
}
}
Expand Down Expand Up @@ -1113,6 +1115,15 @@ mod tests {
.contains("duplicate contract version: ContractVersionKey(1, 1)"));
}

#[test]
fn json_serialization_of_raw_bytes() {
let stored_value = StoredValue::RawBytes(vec![1, 2, 3, 4]);
assert_eq!(
serde_json::to_string(&stored_value).unwrap(),
r#"{"RawBytes":"01020304"}"#
);
}

proptest! {

#[test]
Expand Down
143 changes: 94 additions & 49 deletions types/src/system/auction/delegator_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use schemars::JsonSchema;
use serde::{de::Error as SerdeError, Deserializer, Serializer};
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "std", test))]
use serde_helpers::{HumanReadableDelegatorKind, NonHumanReadableDelegatorKind};
#[cfg(any(feature = "std", test))]
use thiserror::Error;

/// DelegatorKindTag variants.
Expand Down Expand Up @@ -161,48 +163,6 @@ impl Distribution<DelegatorKind> for Standard {
}
}

#[cfg(any(feature = "std", test))]
#[derive(Serialize, Deserialize)]
enum HumanReadableDelegatorKind {
PublicKey(PublicKey),
Purse(String),
}

#[cfg(any(feature = "std", test))]
impl From<&DelegatorKind> for HumanReadableDelegatorKind {
fn from(delegator_kind: &DelegatorKind) -> Self {
match delegator_kind {
DelegatorKind::PublicKey(public_key) => {
HumanReadableDelegatorKind::PublicKey(public_key.clone())
}
DelegatorKind::Purse(uref_addr) => {
HumanReadableDelegatorKind::Purse(base16::encode_lower(uref_addr))
}
}
}
}

#[cfg(any(feature = "std", test))]
#[derive(Serialize, Deserialize)]
enum NonHumanReadableDelegatorKind {
PublicKey(PublicKey),
Purse(URefAddr),
}

#[cfg(any(feature = "std", test))]
impl From<&DelegatorKind> for NonHumanReadableDelegatorKind {
fn from(delegator_kind: &DelegatorKind) -> Self {
match delegator_kind {
DelegatorKind::PublicKey(public_key) => {
NonHumanReadableDelegatorKind::PublicKey(public_key.clone())
}
DelegatorKind::Purse(uref_addr) => {
NonHumanReadableDelegatorKind::Purse(uref_addr.clone())
}
}
}
}

#[cfg(any(feature = "std", test))]
impl Serialize for DelegatorKind {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Expand All @@ -216,15 +176,14 @@ impl Serialize for DelegatorKind {

#[cfg(any(feature = "std", test))]
#[derive(Error, Debug)]
enum DelegatorKindSerializationError {
#[error("Error when serializing DelegatorKind: {0}")]
SerializationError(String),
enum DelegatorKindError {
#[error("Error when deserializing DelegatorKind: {0}")]
DeserializationError(String),
}

#[cfg(any(feature = "std", test))]
impl TryFrom<HumanReadableDelegatorKind> for DelegatorKind {
type Error = DelegatorKindSerializationError;
type Error = DelegatorKindError;

fn try_from(value: HumanReadableDelegatorKind) -> Result<Self, Self::Error> {
match value {
Expand All @@ -233,13 +192,13 @@ impl TryFrom<HumanReadableDelegatorKind> for DelegatorKind {
}
HumanReadableDelegatorKind::Purse(encoded) => {
let decoded = checksummed_hex::decode(&encoded).map_err(|e| {
DelegatorKindSerializationError::DeserializationError(format!(
DelegatorKindError::DeserializationError(format!(
"Failed to decode encoded URefAddr: {}",
e
))
})?;
let uref_addr = URefAddr::try_from(decoded.as_ref()).map_err(|e| {
DelegatorKindSerializationError::DeserializationError(format!(
DelegatorKindError::DeserializationError(format!(
"Failed to build uref address: {}",
e
))
Expand Down Expand Up @@ -274,9 +233,95 @@ impl<'de> Deserialize<'de> for DelegatorKind {
}
}

#[cfg(any(feature = "std", test))]
mod serde_helpers {
use super::DelegatorKind;
use crate::{PublicKey, URefAddr};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub(super) enum HumanReadableDelegatorKind {
PublicKey(PublicKey),
Purse(String),
}

#[derive(Serialize, Deserialize)]
pub(super) enum NonHumanReadableDelegatorKind {
PublicKey(PublicKey),
Purse(URefAddr),
}

impl From<&DelegatorKind> for HumanReadableDelegatorKind {
fn from(delegator_kind: &DelegatorKind) -> Self {
match delegator_kind {
DelegatorKind::PublicKey(public_key) => {
HumanReadableDelegatorKind::PublicKey(public_key.clone())
}
DelegatorKind::Purse(uref_addr) => {
HumanReadableDelegatorKind::Purse(base16::encode_lower(uref_addr))
}
}
}
}

impl From<&DelegatorKind> for NonHumanReadableDelegatorKind {
fn from(delegator_kind: &DelegatorKind) -> Self {
match delegator_kind {
DelegatorKind::PublicKey(public_key) => {
NonHumanReadableDelegatorKind::PublicKey(public_key.clone())
}
DelegatorKind::Purse(uref_addr) => {
NonHumanReadableDelegatorKind::Purse(uref_addr.clone())
}
}
}
}
}

#[cfg(test)]
mod tests {
use crate::{bytesrepr, system::auction::delegator_kind::DelegatorKind, PublicKey, SecretKey};
use rand::Rng;

use crate::{
bytesrepr, system::auction::delegator_kind::DelegatorKind, testing::TestRng, PublicKey,
SecretKey,
};

#[test]
fn purse_serialized_as_string() {
let delegator_kind_payload = DelegatorKind::Purse([1; 32]);
let serialized = serde_json::to_string(&delegator_kind_payload).unwrap();
assert_eq!(
serialized,
"{\"Purse\":\"0101010101010101010101010101010101010101010101010101010101010101\"}"
);
}

#[test]
fn given_broken_address_purse_deserialziation_fails() {
let failing =
"{\"Purse\":\"Z101010101010101010101010101010101010101010101010101010101010101\"}";
let ret = serde_json::from_str::<DelegatorKind>(failing);
assert!(ret.is_err());
let failing = "{\"Purse\":\"01010101010101010101010101010101010101010101010101010101\"}";
let ret = serde_json::from_str::<DelegatorKind>(failing);
assert!(ret.is_err());
}

#[test]
fn json_roundtrip() {
let rng = &mut TestRng::new();

let delegator_kind_payload = DelegatorKind::PublicKey(PublicKey::random(rng));
let json_string = serde_json::to_string_pretty(&delegator_kind_payload).unwrap();
let decoded: DelegatorKind = serde_json::from_str(&json_string).unwrap();
assert_eq!(decoded, delegator_kind_payload);

let delegator_kind_payload = DelegatorKind::Purse(rng.gen());
let json_string = serde_json::to_string_pretty(&delegator_kind_payload).unwrap();
let decoded: DelegatorKind = serde_json::from_str(&json_string).unwrap();
assert_eq!(decoded, delegator_kind_payload);
}

#[test]
fn serialization_roundtrip() {
Expand Down
Loading

0 comments on commit 3b4c746

Please sign in to comment.