From 6949a0d341bb7a54f9af3830eaeff8bbd21a188c Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Thu, 21 Mar 2019 14:06:30 +0000 Subject: [PATCH] add string_or_u64 serialization (#2698) --- core/src/core/transaction.rs | 2 + core/src/libtx/secp_ser.rs | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 3828a41b2..95c5cc7ab 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -159,9 +159,11 @@ pub struct TxKernel { /// Options for a kernel's structure or use pub features: KernelFeatures, /// Fee originally included in the transaction this proof is for. + #[serde(with = "secp_ser::string_or_u64")] pub fee: u64, /// This kernel is not valid earlier than lock_height blocks /// The max lock_height of all *inputs* to this transaction + #[serde(with = "secp_ser::string_or_u64")] pub lock_height: u64, /// Remainder of the sum of all transaction commitments. If the transaction /// is well formed, amounts components should sum to zero and the excess diff --git a/core/src/libtx/secp_ser.rs b/core/src/libtx/secp_ser.rs index b0460c7bd..b486f21d1 100644 --- a/core/src/libtx/secp_ser.rs +++ b/core/src/libtx/secp_ser.rs @@ -164,6 +164,104 @@ where serializer.serialize_str(&to_hex(bytes.as_ref().to_vec())) } +/// Used to ensure u64s are serialised in json +/// as strings by default, since it can't be guaranteed that consumers +/// will know what to do with u64 literals (e.g. Javascript). However, +/// fields using this tag can be deserialized from literals or strings. +/// From solutions on: +/// https://github.com/serde-rs/json/issues/329 +pub mod string_or_u64 { + use std::fmt; + + use serde::{de, Deserializer, Serializer}; + + /// serialize into a string + pub fn serialize(value: &T, serializer: S) -> Result + where + T: fmt::Display, + S: Serializer, + { + serializer.collect_str(value) + } + + /// deserialize from either literal or string + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct Visitor; + impl<'a> de::Visitor<'a> for Visitor { + type Value = u64; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "a string containing digits or an int fitting into u64" + ) + } + fn visit_u64(self, v: u64) -> Result { + Ok(v) + } + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + s.parse().map_err(de::Error::custom) + } + } + deserializer.deserialize_any(Visitor) + } +} + +/// As above, for Options +pub mod opt_string_or_u64 { + use std::fmt; + + use serde::{de, Deserializer, Serializer}; + + /// serialize into string or none + pub fn serialize(value: &Option, serializer: S) -> Result + where + T: fmt::Display, + S: Serializer, + { + match value { + Some(v) => serializer.collect_str(v), + None => serializer.serialize_none(), + } + } + + /// deser from 'null', literal or string + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct Visitor; + impl<'a> de::Visitor<'a> for Visitor { + type Value = Option; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "null, a string containing digits or an int fitting into u64" + ) + } + fn visit_unit(self) -> Result { + Ok(None) + } + fn visit_u64(self, v: u64) -> Result { + Ok(Some(v)) + } + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + let val: u64 = s.parse().map_err(de::Error::custom)?; + Ok(Some(val)) + } + } + deserializer.deserialize_any(Visitor) + } +} + // Test serialization methods of components that are being used #[cfg(test)] mod test { @@ -185,6 +283,10 @@ mod test { pub opt_sig: Option, #[serde(with = "sig_serde")] pub sig: Signature, + #[serde(with = "string_or_u64")] + pub num: u64, + #[serde(with = "opt_string_or_u64")] + pub opt_num: Option, } impl SerTest { @@ -200,6 +302,8 @@ mod test { pub_key: PublicKey::from_secret_key(&secp, &sk).unwrap(), opt_sig: Some(sig.clone()), sig: sig.clone(), + num: 30, + opt_num: Some(33), } } }