diff --git a/Cargo.toml b/Cargo.toml index 1012956..809c1a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,8 @@ ripemd160 = "0.9.1" secp256k1 = "0.20.3" bech32 = "0.8.1" -sha2 = { version = "0.9.1", default-features = false } \ No newline at end of file +sha2 = { version = "0.9.1", default-features = false } +sha3 = "0.10.8" + +[dev-dependencies] +serde_derive = "1.0.80" diff --git a/src/lib.rs b/src/lib.rs index 25b85a5..78f8465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod permit; +pub mod pretty; pub mod transaction; pub mod viewing_keys; diff --git a/src/permit.rs b/src/permit.rs index f14d796..227d84f 100644 --- a/src/permit.rs +++ b/src/permit.rs @@ -3,7 +3,8 @@ use crate::transaction::{PermitSignature, PubKeyValue, SignedTx}; use bech32::FromBase32; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Uint128}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; +use sha3::{Digest, Keccak256}; // NOTE: Struct order is very important for signatures @@ -46,24 +47,60 @@ impl Permit { let signed_bytes = to_binary(signed_tx)?; let signed_bytes_hash = sha_256(signed_bytes.as_slice()); + let verification_result = + api.secp256k1_verify(&signed_bytes_hash, &signature.signature.0, &pubkey.0); + + if let Ok(verified) = verification_result { + if verified { + return Ok(PubKeyValue(pubkey.clone())); + } + } + + // Try validating Ethereum signature + + let mut signed_bytes = vec![]; + signed_bytes.extend_from_slice(b"\x19Ethereum Signed Message:\n"); + + let signed_tx_pretty_amino_json = to_binary_pretty(signed_tx)?; + + signed_bytes.extend_from_slice(signed_tx_pretty_amino_json.len().to_string().as_bytes()); + signed_bytes.extend_from_slice(signed_tx_pretty_amino_json.as_slice()); + + let mut hasher = Keccak256::new(); + + hasher.update(&signed_bytes); + + let signed_bytes_hash = hasher.finalize(); + let verified = api .secp256k1_verify(&signed_bytes_hash, &signature.signature.0, &pubkey.0) .map_err(|err| StdError::generic_err(err.to_string()))?; - if !verified { - return Err(StdError::generic_err("Signature verification failed")); + if verified { + return Ok(PubKeyValue(pubkey.clone())); } - Ok(PubKeyValue(pubkey.clone())) + Err(StdError::generic_err("Signature verification failed")) } } +fn to_binary_pretty(data: &T) -> StdResult +where + T: Serialize + ?Sized, +{ + const INDENT: &[u8; 4] = b" "; + super::pretty::to_vec_pretty(data, INDENT) + .map_err(|e| StdError::serialize_err(std::any::type_name::(), e)) + .map(Binary) +} + #[cfg(test)] mod signature_tests { use super::*; use crate::transaction::PubKey; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::{Addr, Uint128}; + use serde::Deserialize; #[remain::sorted] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -132,6 +169,72 @@ mod signature_tests { //assert!(permit.validate(&deps.api, None).is_err()); } + #[test] + fn test_pretty_print() { + let permit = TestPermit { + params: TestPermitMsg { + address: ADDRESS.to_string(), + some_number: Uint128::new(10), + }, + chain_id: Some("pulsar-1".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64(PUBKEY).unwrap()), + signature: Binary::from_base64(SIGNED_TX).unwrap(), + }, + account_number: None, + memo: None, + }; + + let signed_tx = permit.create_signed_tx(None); + + let mut signed_bytes = vec![]; + signed_bytes.extend_from_slice(b"\x19Ethereum Signed Message:\n"); + + let signed_tx_pretty_amino_json = to_binary_pretty(&signed_tx).unwrap(); + + signed_bytes.extend_from_slice(signed_tx_pretty_amino_json.len().to_string().as_bytes()); + signed_bytes.extend_from_slice(signed_tx_pretty_amino_json.as_slice()); + println!("{:?}", signed_bytes); + + let full_readable_message = String::from_utf8(signed_bytes.clone()).unwrap(); + println!("{}", full_readable_message); + + let mut hasher = Keccak256::new(); + + hasher.update(&signed_bytes); + + let signed_bytes_hash = hasher.finalize(); + println!("{:?}", signed_bytes_hash); + + const INDENT: &[u8; 4] = b" "; + let pretty_json_signed_tx = crate::pretty::to_string_pretty(&signed_tx, INDENT).unwrap(); + println!("{}", pretty_json_signed_tx); + + let pretty_json = crate::pretty::to_string_pretty(&permit, INDENT).unwrap(); + + assert_eq!( + pretty_json, + r#"{ + "params": { + "address": "secret102nasmxnxvwp5agc4lp3flc6s23335xm8g7gn9", + "some_number": "10" + }, + "signature": { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A0qzJ3s16OKUfn1KFyh533vBnBOQIT0jm+R/FBobJCfa" + }, + "signature": "4pZtghyHKHHmwiGNC5JD8JxCJiO+44j6GqaLPc19Q7lt85tr0IRZHYcnc0pkokIds8otxU9rcuvPXb0+etLyVA==" + }, + "account_number": null, + "chain_id": "pulsar-1", + "sequence": null, + "memo": null +}"# + ) + } + const FILLERPERMITNAME: &str = "wasm/MsgExecuteContract"; type MemoPermit = Permit; @@ -191,4 +294,45 @@ mod signature_tests { // NOTE: SN mock deps doesnt have a valid working implementation of the dep functons for some reason //assert!(permit.validate(&deps.api, Some(FILLERPERMITNAME.to_string())).is_err()) } + + #[remain::sorted] + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + #[serde(rename_all = "snake_case")] + struct PermitData { + pub data: String, + pub key: String, + } + + type QueryPermit = Permit; + + #[test] + fn ethereum_signature() { + const ADDRESS: &str = "secret1tvsne5ugx9e60qdq6whua6j5pjnv5jf3d7k0va"; + const PUBKEY: &str = "AguNQmQKft7WQd1CrZHXya42RKJBK9/xdHkAEndOVSij"; + const SIGNATURE: &str = + "xqJJQEoVnHsNqHrtwB4YZKvanT4QPqkeCmuJJncTiNxqyvlmA//cjhU7Jc6ROT4lrDgWYpky7L6YywwbXgtygQ=="; + + let permit = QueryPermit { + params: PermitData { + data: "e30=".to_string(), + key: "shade-master-permit".to_string(), + }, + chain_id: Some("pulsar-2".to_string()), + sequence: Some(Uint128::zero()), + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64(PUBKEY).unwrap()), + signature: Binary::from_base64(SIGNATURE).unwrap(), + }, + account_number: Some(Uint128::zero()), + memo: Some("".to_string()), + }; + + let deps = mock_dependencies(); + let addr = permit.validate(&deps.api, None).unwrap(); + println!("{}", addr.as_addr(None).unwrap()); + assert_eq!( + addr.as_addr(None).unwrap(), + Addr::unchecked(ADDRESS.to_string()) + ); + } } diff --git a/src/pretty/map.rs b/src/pretty/map.rs new file mode 100644 index 0000000..3981427 --- /dev/null +++ b/src/pretty/map.rs @@ -0,0 +1,265 @@ +use std::fmt; + +use serde::{ser, Serialize}; + +use super::{seq::SerializeSeq, struct_::SerializeStruct}; +use super::{Error, Result, Serializer, Unreachable}; + +pub struct SerializeMap<'serializer, 'indent> { + ser: &'serializer mut Serializer<'indent>, + first: bool, +} + +impl<'serializer, 'indent: 'serializer> SerializeMap<'serializer, 'indent> { + pub(crate) fn new(ser: &'serializer mut Serializer<'indent>) -> Self { + SerializeMap { ser, first: true } + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeMap for SerializeMap<'serializer, 'indent> { + type Ok = (); + type Error = Error; + + fn end(self) -> Result { + self.ser.current_indent -= 1; + if !self.first { + self.ser.buf.push(b'\n'); + self.ser.indent()?; + } + self.ser.buf.push(b'}'); + Ok(()) + } + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ser::Serialize, + { + if !self.first { + self.ser.buf.push(b','); + } + self.first = false; + self.ser.buf.push(b'\n'); + self.ser.indent()?; + + // Use key serializer to unsure key type validity. + key.serialize(MapKeySerializer { ser: self.ser })?; + self.ser.buf.extend_from_slice(b": "); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ser::Serialize, + { + value.serialize(&mut *self.ser)?; + Ok(()) + } +} + +/// Wrapper around Serializer that only allows serialization of valid JSON key types (strings). +struct MapKeySerializer<'serializer, 'indent> { + ser: &'serializer mut Serializer<'indent>, +} + +pub(crate) fn key_must_be_a_string() -> Error { + Error::Custom("JSON object key is required to be a string type.".to_string()) +} + +macro_rules! serialize_unsigned_key { + ($self:ident, $N:expr, $v:expr) => {{ + let ser = $self.ser; + ser.buf.push(b'"'); + let res: Result = super::serialize_unsigned!(ser, $N, $v); + res?; + ser.buf.push(b'"'); + Ok(()) + }}; +} + +macro_rules! serialize_signed_key { + ($self:ident, $N:expr, $v:expr, $ixx:ident, $uxx:ident) => {{ + let ser = $self.ser; + ser.buf.push(b'"'); + let res: Result = super::serialize_signed!(ser, $N, $v, $ixx, $uxx); + res?; + ser.buf.push(b'"'); + Ok(()) + }}; +} + +impl<'serializer, 'indent: 'serializer> ser::Serializer for MapKeySerializer<'serializer, 'indent> { + type Ok = (); + type Error = Error; + type SerializeSeq = SerializeSeq<'serializer, 'indent>; + type SerializeTuple = SerializeSeq<'serializer, 'indent>; + type SerializeTupleStruct = Unreachable; + type SerializeTupleVariant = Unreachable; + type SerializeMap = SerializeMap<'serializer, 'indent>; + type SerializeStruct = SerializeStruct<'serializer, 'indent>; + type SerializeStructVariant = SerializeStruct<'serializer, 'indent>; + + fn serialize_bool(self, _value: bool) -> Result<()> { + Err(key_must_be_a_string()) + } + #[inline] + fn serialize_str(self, value: &str) -> Result<()> { + self.ser.serialize_str(value) + } + + #[inline] + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.ser.serialize_str(variant) + } + + #[inline] + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_i8(self, value: i8) -> Result<()> { + serialize_signed_key!(self, 4, value, i8, u8) + } + + fn serialize_i16(self, value: i16) -> Result<()> { + serialize_signed_key!(self, 6, value, i16, u16) + } + + fn serialize_i32(self, value: i32) -> Result<()> { + serialize_signed_key!(self, 11, value, i32, u32) + } + + fn serialize_i64(self, value: i64) -> Result<()> { + serialize_signed_key!(self, 20, value, i64, u64) + } + + fn serialize_i128(self, value: i128) -> Result<()> { + serialize_signed_key!(self, 40, value, i128, u128) + } + + fn serialize_u8(self, value: u8) -> Result<()> { + serialize_unsigned_key!(self, 3, value) + } + + fn serialize_u16(self, value: u16) -> Result<()> { + serialize_unsigned_key!(self, 5, value) + } + + fn serialize_u32(self, value: u32) -> Result<()> { + serialize_unsigned_key!(self, 10, value) + } + + fn serialize_u64(self, value: u64) -> Result<()> { + serialize_unsigned_key!(self, 20, value) + } + + fn serialize_u128(self, value: u128) -> Result<()> { + serialize_unsigned_key!(self, 39, value) + } + + fn serialize_f32(self, _value: f32) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_f64(self, _value: f64) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_char(self, value: char) -> Result<()> { + self.ser.serialize_str(&value.to_string()) + } + + fn serialize_bytes(self, _value: &[u8]) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_unit(self) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_none(self) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_some(self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn collect_str(self, _value: &T) -> Result<()> + where + T: ?Sized + fmt::Display, + { + unreachable!() + } +} diff --git a/src/pretty/mod.rs b/src/pretty/mod.rs new file mode 100644 index 0000000..a56eee8 --- /dev/null +++ b/src/pretty/mod.rs @@ -0,0 +1,813 @@ +//! Serialize a Rust data structure into pretty-printed JSON data + +use std::{error, fmt}; + +use serde::ser; + +use std::vec::Vec; + +use serde::ser::SerializeStruct as _; + +use self::map::SerializeMap; +use self::seq::SerializeSeq; +use self::struct_::SerializeStruct; + +mod map; +mod seq; +mod struct_; + +/// Serialization result +pub type Result = ::core::result::Result; + +/// This type represents all possible errors that can occur when serializing JSON data +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + /// Buffer is full + BufferFull, + + /// Custom error message from serde + Custom(String), +} + +impl From<()> for Error { + fn from(_: ()) -> Error { + Error::BufferFull + } +} + +impl From for Error { + fn from(_: u8) -> Error { + Error::BufferFull + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + } + + fn description(&self) -> &str { + "(use display)" + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::BufferFull => write!(f, "Buffer is full"), + Error::Custom(msg) => write!(f, "{}", &msg), + } + } +} + +/// Number of bytes reserved by default for the output JSON +static INITIAL_CAPACITY: usize = 1024; + +/// Serializer implements serde::ser::Serializer and allows us to serialize a +/// serde struct into JSON +pub struct Serializer<'indent> { + buf: Vec, + current_indent: usize, + indent: &'indent [u8], +} + +impl<'indent> Serializer<'indent> { + fn new(indent: &'indent [u8]) -> Self { + Serializer { + buf: Vec::with_capacity(INITIAL_CAPACITY), + current_indent: 0, + indent, + } + } + + /// Indent the content + pub fn indent(&mut self) -> Result<()> { + for _ in 0..self.current_indent { + self.buf.extend_from_slice(self.indent); + } + Ok(()) + } +} + +// NOTE(serialize_*signed) This is basically the numtoa implementation minus the lookup tables, +// which take 200+ bytes of ROM / Flash +macro_rules! serialize_unsigned { + ($self:ident, $N:expr, $v:expr) => {{ + let mut buf = [0u8; $N]; + + let mut v = $v; + let mut i = $N - 1; + loop { + buf[i] = (v % 10) as u8 + b'0'; + v /= 10; + + if v == 0 { + break; + } else { + i -= 1; + } + } + + $self.buf.extend_from_slice(&buf[i..]); + Ok(()) + }}; +} +// Export for use in map +pub(crate) use serialize_unsigned; + +macro_rules! serialize_signed { + ($self:ident, $N:expr, $v:expr, $ixx:ident, $uxx:ident) => {{ + let v = $v; + let (signed, mut v) = if v == $ixx::min_value() { + (true, $ixx::max_value() as $uxx + 1) + } else if v < 0 { + (true, -v as $uxx) + } else { + (false, v as $uxx) + }; + + let mut buf = [0u8; $N]; + let mut i = $N - 1; + loop { + buf[i] = (v % 10) as u8 + b'0'; + v /= 10; + + i -= 1; + + if v == 0 { + break; + } + } + + if signed { + buf[i] = b'-'; + } else { + i += 1; + } + $self.buf.extend_from_slice(&buf[i..]); + Ok(()) + }}; +} +// Export for use in map +pub(crate) use serialize_signed; + +/// Upper-case hex for value in 0..16, encoded as ASCII bytes +fn hex_4bit(c: u8) -> u8 { + if c <= 9 { + 0x30 + c + } else { + 0x41 + (c - 10) + } +} + +/// Upper-case hex for value in 0..256, encoded as ASCII bytes +fn hex(c: u8) -> (u8, u8) { + (hex_4bit(c >> 4), hex_4bit(c & 0x0F)) +} + +impl<'serializer, 'indent: 'serializer> ser::Serializer for &'serializer mut Serializer<'indent> { + type Ok = (); + type Error = Error; + type SerializeSeq = SerializeSeq<'serializer, 'indent>; + type SerializeTuple = SerializeSeq<'serializer, 'indent>; + type SerializeTupleStruct = Unreachable; + type SerializeTupleVariant = SerializeSeq<'serializer, 'indent>; + type SerializeMap = SerializeMap<'serializer, 'indent>; + type SerializeStruct = SerializeStruct<'serializer, 'indent>; + type SerializeStructVariant = SerializeStruct<'serializer, 'indent>; + + fn serialize_bool(self, v: bool) -> Result { + if v { + self.buf.extend_from_slice(b"true"); + } else { + self.buf.extend_from_slice(b"false"); + } + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + // "-128" + serialize_signed!(self, 4, v, i8, u8) + } + + fn serialize_i16(self, v: i16) -> Result { + // "-32768" + serialize_signed!(self, 6, v, i16, u16) + } + + fn serialize_i32(self, v: i32) -> Result { + // "-2147483648" + serialize_signed!(self, 11, v, i32, u32) + } + + fn serialize_i64(self, v: i64) -> Result { + // "-9223372036854775808" + serialize_signed!(self, 20, v, i64, u64) + } + + fn serialize_i128(self, v: i128) -> Result { + // -170141183460469231731687303715884105728 + self.buf.push(b'"'); + let res: Result = serialize_signed!(self, 40, v, i128, u128); + res?; + self.buf.push(b'"'); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + // "255" + serialize_unsigned!(self, 3, v) + } + + fn serialize_u16(self, v: u16) -> Result { + // "65535" + serialize_unsigned!(self, 5, v) + } + + fn serialize_u32(self, v: u32) -> Result { + // "4294967295" + serialize_unsigned!(self, 10, v) + } + + fn serialize_u64(self, v: u64) -> Result { + // "18446744073709551615" + serialize_unsigned!(self, 20, v) + } + + fn serialize_u128(self, v: u128) -> Result { + // 340282366920938463463374607431768211455 + self.buf.push(b'"'); + let res: Result = serialize_unsigned!(self, 39, v); + res?; + self.buf.push(b'"'); + Ok(()) + } + + fn serialize_f32(self, _v: f32) -> Result { + unreachable!() + } + + fn serialize_f64(self, _v: f64) -> Result { + unreachable!() + } + + fn serialize_char(self, _v: char) -> Result { + unreachable!() + } + + fn serialize_str(self, v: &str) -> Result { + self.buf.push(b'"'); + + // Do escaping according to "6. MUST represent all strings (including object member names) in + // their minimal-length UTF-8 encoding": https://gibson042.github.io/canonicaljson-spec/ + // + // We don't need to escape lone surrogates because surrogate pairs do not exist in valid UTF-8, + // even if they can exist in JSON or JavaScript strings (UCS-2 based). As a result, lone surrogates + // cannot exist in a Rust String. If they do, the bug is in the String constructor. + // An excellent explanation is available at https://www.youtube.com/watch?v=HhIEDWmQS3w + + // Temporary storage for encoded a single char. + // A char is up to 4 bytes long when encoded to UTF-8. + let mut encoding_tmp = [0u8; 4]; + + for c in v.chars() { + match c { + '\\' => { + self.buf.push(b'\\'); + self.buf.push(b'\\'); + } + '"' => { + self.buf.push(b'\\'); + self.buf.push(b'"'); + } + '\u{0008}' => { + self.buf.push(b'\\'); + self.buf.push(b'b'); + } + '\u{0009}' => { + self.buf.push(b'\\'); + self.buf.push(b't'); + } + '\u{000A}' => { + self.buf.push(b'\\'); + self.buf.push(b'n'); + } + '\u{000C}' => { + self.buf.push(b'\\'); + self.buf.push(b'f'); + } + '\u{000D}' => { + self.buf.push(b'\\'); + self.buf.push(b'r'); + } + '\u{0000}'..='\u{001F}' => { + self.buf.push(b'\\'); + self.buf.push(b'u'); + self.buf.push(b'0'); + self.buf.push(b'0'); + let (hex1, hex2) = hex(c as u8); + self.buf.push(hex1); + self.buf.push(hex2); + } + _ => { + if c.len_utf8() == 1 { + self.buf.push(c as u8); + } else { + let encoded = c.encode_utf8(&mut encoding_tmp as &mut [u8]); + self.buf.extend_from_slice(encoded.as_bytes()); + } + } + } + } + + self.buf.push(b'"'); + Ok(()) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + unreachable!() + } + + fn serialize_none(self) -> Result { + self.buf.extend_from_slice(b"null"); + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + // The unit type is a zero element tuple, so the consistent way to serialize this would be "[]". + // However, for compatibility with serde_json we serialize to "null". + self.buf.extend_from_slice(b"null"); + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + // Unit struct is serialized to (serde_json compatible) "null" + self.buf.extend_from_slice(b"null"); + Ok(()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ser::Serialize, + { + self.current_indent += 1; + self.buf.push(b'{'); + let mut s = SerializeStruct::new(self); + s.serialize_field(variant, value)?; + s.end()?; + Ok(()) + } + + fn serialize_seq(self, _len: Option) -> Result { + self.current_indent += 1; + self.buf.push(b'['); + + Ok(SerializeSeq::new(self)) + } + + fn serialize_tuple(self, _len: usize) -> Result { + self.serialize_seq(Some(_len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!() + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.current_indent += 1; + self.buf.push(b'{'); + self.serialize_str(variant)?; + self.buf.push(b':'); + self.serialize_tuple(len) + } + + fn serialize_map(self, _len: Option) -> Result { + self.current_indent += 1; + self.buf.push(b'{'); + + Ok(SerializeMap::new(self)) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + self.current_indent += 1; + self.buf.push(b'{'); + + Ok(SerializeStruct::new(self)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.current_indent += 1; + self.buf.extend_from_slice(b"{\n"); + self.indent()?; + self.buf.push(b'"'); + self.buf.extend_from_slice(variant.as_bytes()); + self.buf.extend_from_slice(b"\": {"); + self.current_indent += 1; + + Ok(SerializeStruct::new(self)) + } + + fn collect_str(self, _value: &T) -> Result + where + T: fmt::Display, + { + unreachable!() + } +} + +/// Serializes the given data structure as a pretty-printed string of JSON text +// #[cfg(feature = "heapless")] +pub fn to_string_pretty(value: &T, indent: &[u8]) -> Result +where + T: ser::Serialize + ?Sized, +{ + let mut ser = Serializer::new(indent); + value.serialize(&mut ser)?; + Ok(unsafe { String::from_utf8_unchecked(ser.buf) }) +} + +/// Serializes the given data structure as a pretty-printed JSON byte vector +pub fn to_vec_pretty(value: &T, indent: &[u8]) -> Result> +where + T: ser::Serialize + ?Sized, +{ + let mut ser = Serializer::new(indent); + value.serialize(&mut ser)?; + Ok(ser.buf) +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + Error::Custom(msg.to_string()) + } +} + +/// Unreachable is a placeholder for features that are not supported +/// (and should be unreachable, unless you use unsupported serde flags) +pub enum Unreachable {} + +impl ser::SerializeTupleStruct for Unreachable { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _value: &T) -> Result<()> { + unreachable!() + } + + fn end(self) -> Result { + unreachable!() + } +} + +impl ser::SerializeTupleVariant for Unreachable { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _value: &T) -> Result<()> { + unreachable!() + } + + fn end(self) -> Result { + unreachable!() + } +} + +impl ser::SerializeMap for Unreachable { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, _key: &T) -> Result<()> + where + T: ser::Serialize, + { + unreachable!() + } + + fn serialize_value(&mut self, _value: &T) -> Result<()> + where + T: ser::Serialize, + { + unreachable!() + } + + fn end(self) -> Result { + unreachable!() + } +} + +impl ser::SerializeStructVariant for Unreachable { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _key: &'static str, _value: &T) -> Result<()> + where + T: ser::Serialize, + { + unreachable!() + } + + fn end(self) -> Result { + unreachable!() + } +} + +#[cfg(test)] +mod tests { + + use super::to_string_pretty; + use serde_derive::Serialize; + + const INDENT: &[u8] = b" "; + + #[test] + fn enum_() { + #[derive(Serialize)] + enum Type { + #[serde(rename = "boolean")] + Boolean, + #[serde(rename = "number")] + Number, + } + + assert_eq!( + to_string_pretty(&Type::Boolean, INDENT).unwrap(), + r#""boolean""# + ); + + assert_eq!( + to_string_pretty(&Type::Number, INDENT).unwrap(), + r#""number""# + ); + } + + #[test] + fn str() { + assert_eq!(to_string_pretty("hello", INDENT).unwrap(), r#""hello""#); + assert_eq!(to_string_pretty("", INDENT).unwrap(), r#""""#); + + // Characters unescaped if possible + assert_eq!(to_string_pretty("ä", INDENT).unwrap(), r#""ä""#); + assert_eq!(to_string_pretty("৬", INDENT).unwrap(), r#""৬""#); + // assert_eq!(to_string_pretty("\u{A0}").unwrap(), r#"" ""#); // non-breaking space + assert_eq!(to_string_pretty("ℝ", INDENT).unwrap(), r#""ℝ""#); // 3 byte character + assert_eq!(to_string_pretty("💣", INDENT).unwrap(), r#""💣""#); // 4 byte character + + // " and \ must be escaped + assert_eq!( + to_string_pretty("foo\"bar", INDENT).unwrap(), + r#""foo\"bar""# + ); + assert_eq!( + to_string_pretty("foo\\bar", INDENT).unwrap(), + r#""foo\\bar""# + ); + + // \b, \t, \n, \f, \r must be escaped in their two-character escaping + assert_eq!(to_string_pretty(" \u{0008} ", INDENT).unwrap(), r#"" \b ""#); + assert_eq!(to_string_pretty(" \u{0009} ", INDENT).unwrap(), r#"" \t ""#); + assert_eq!(to_string_pretty(" \u{000A} ", INDENT).unwrap(), r#"" \n ""#); + assert_eq!(to_string_pretty(" \u{000C} ", INDENT).unwrap(), r#"" \f ""#); + assert_eq!(to_string_pretty(" \u{000D} ", INDENT).unwrap(), r#"" \r ""#); + + // U+0000 through U+001F is escaped using six-character \u00xx uppercase hexadecimal escape sequences + assert_eq!( + to_string_pretty(" \u{0000} ", INDENT).unwrap(), + r#"" \u0000 ""# + ); + assert_eq!( + to_string_pretty(" \u{0001} ", INDENT).unwrap(), + r#"" \u0001 ""# + ); + assert_eq!( + to_string_pretty(" \u{0007} ", INDENT).unwrap(), + r#"" \u0007 ""# + ); + assert_eq!( + to_string_pretty(" \u{000e} ", INDENT).unwrap(), + r#"" \u000E ""# + ); + assert_eq!( + to_string_pretty(" \u{001D} ", INDENT).unwrap(), + r#"" \u001D ""# + ); + assert_eq!( + to_string_pretty(" \u{001f} ", INDENT).unwrap(), + r#"" \u001F ""# + ); + } + + #[test] + fn struct_bool() { + #[derive(Serialize)] + struct Led { + led: bool, + } + + assert_eq!( + to_string_pretty(&Led { led: true }, INDENT).unwrap(), + r#"{ + "led": true +}"# + ); + } + + #[test] + fn struct_i8() { + #[derive(Serialize)] + struct Temperature { + temperature: i8, + } + + assert_eq!( + to_string_pretty(&Temperature { temperature: 127 }, INDENT).unwrap(), + r#"{ + "temperature": 127 +}"# + ); + + assert_eq!( + to_string_pretty(&Temperature { temperature: 20 }, INDENT).unwrap(), + r#"{ + "temperature": 20 +}"# + ); + + assert_eq!( + to_string_pretty(&Temperature { temperature: -17 }, INDENT).unwrap(), + r#"{ + "temperature": -17 +}"# + ); + + assert_eq!( + to_string_pretty(&Temperature { temperature: -128 }, INDENT).unwrap(), + r#"{ + "temperature": -128 +}"# + ); + } + + #[test] + fn struct_option() { + #[derive(Serialize)] + struct Property<'a> { + description: Option<&'a str>, + } + + assert_eq!( + to_string_pretty( + &Property { + description: Some("An ambient temperature sensor"), + }, + INDENT + ) + .unwrap(), + r#"{ + "description": "An ambient temperature sensor" +}"# + ); + + // XXX Ideally this should produce "{}" + assert_eq!( + to_string_pretty(&Property { description: None }, INDENT).unwrap(), + r#"{ + "description": null +}"# + ); + } + + #[test] + fn struct_u8() { + #[derive(Serialize)] + struct Temperature { + temperature: u8, + } + + assert_eq!( + to_string_pretty(&Temperature { temperature: 20 }, INDENT).unwrap(), + r#"{ + "temperature": 20 +}"# + ); + } + + #[test] + fn struct_() { + #[derive(Serialize)] + struct Empty {} + + assert_eq!(to_string_pretty(&Empty {}, INDENT).unwrap(), r#"{}"#); + + #[derive(Serialize)] + struct Tuple { + a: bool, + b: bool, + } + + assert_eq!( + to_string_pretty(&Tuple { a: true, b: false }, INDENT).unwrap(), + r#"{ + "a": true, + "b": false +}"# + ); + } + + #[test] + fn test_unit() { + let a = (); + assert_eq!(to_string_pretty(&a, INDENT).unwrap(), r#"null"#); + } + + #[test] + fn test_newtype_struct() { + #[derive(Serialize)] + struct A(pub u32); + let a = A(54); + assert_eq!(to_string_pretty(&a, INDENT).unwrap(), r#"54"#); + } + + #[test] + fn test_newtype_variant() { + #[derive(Serialize)] + enum A { + A(u32), + } + let a = A::A(54); + + assert_eq!( + to_string_pretty(&a, INDENT).unwrap(), + r#"{ + "A": 54 +}"# + ); + } + + #[test] + fn test_struct_variant() { + #[derive(Serialize)] + enum A { + A { x: u32, y: u16 }, + } + let a = A::A { x: 54, y: 720 }; + + assert_eq!( + to_string_pretty(&a, INDENT).unwrap(), + r#"{ + "A": { + "x": 54, + "y": 720 + } +}"# + ); + } +} diff --git a/src/pretty/seq.rs b/src/pretty/seq.rs new file mode 100644 index 0000000..ae77d38 --- /dev/null +++ b/src/pretty/seq.rs @@ -0,0 +1,83 @@ +use serde::ser; + +use super::{Error, Result, Serializer}; + +pub struct SerializeSeq<'serializer, 'indent> { + ser: &'serializer mut Serializer<'indent>, + first: bool, +} + +impl<'serializer, 'indent: 'serializer> SerializeSeq<'serializer, 'indent> { + pub(crate) fn new(ser: &'serializer mut Serializer<'indent>) -> Self { + SerializeSeq { ser, first: true } + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeSeq for SerializeSeq<'serializer, 'indent> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ser::Serialize, + { + if !self.first { + self.ser.buf.push(b','); + } + self.first = false; + + self.ser.buf.push(b'\n'); + self.ser.indent()?; + + value.serialize(&mut *self.ser)?; + Ok(()) + } + + fn end(self) -> Result { + self.ser.current_indent -= 1; + if !self.first { + self.ser.buf.push(b'\n'); + self.ser.indent()?; + } + self.ser.buf.push(b']'); + Ok(()) + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeTuple for SerializeSeq<'serializer, 'indent> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ser::Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeTupleVariant + for SerializeSeq<'serializer, 'indent> +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ser::Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + // close sequence + self.ser.buf.push(b']'); + // close surrounding enum + self.ser.buf.push(b'}'); + Ok(()) + } +} diff --git a/src/pretty/struct_.rs b/src/pretty/struct_.rs new file mode 100644 index 0000000..4a59011 --- /dev/null +++ b/src/pretty/struct_.rs @@ -0,0 +1,92 @@ +use serde::ser; + +use super::{Error, Result, Serializer}; + +pub struct SerializeStruct<'serializer, 'indent> { + ser: &'serializer mut Serializer<'indent>, + first: bool, +} + +impl<'serializer, 'indent: 'serializer> SerializeStruct<'serializer, 'indent> { + pub(crate) fn new(ser: &'serializer mut Serializer<'indent>) -> Self { + SerializeStruct { ser, first: true } + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeStruct + for SerializeStruct<'serializer, 'indent> +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ser::Serialize, + { + // XXX if `value` is `None` we not produce any output for this field + if !self.first { + self.ser.buf.push(b','); + } + self.first = false; + + self.ser.buf.push(b'\n'); + self.ser.indent()?; + self.ser.buf.push(b'"'); + self.ser.buf.extend_from_slice(key.as_bytes()); + self.ser.buf.extend_from_slice(b"\": "); + + value.serialize(&mut *self.ser)?; + + Ok(()) + } + + fn end(self) -> Result { + self.ser.current_indent -= 1; + if !self.first { + self.ser.buf.push(b'\n'); + self.ser.indent()?; + } + self.ser.buf.push(b'}'); + Ok(()) + } +} + +impl<'serializer, 'indent: 'serializer> ser::SerializeStructVariant + for SerializeStruct<'serializer, 'indent> +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ser::Serialize, + { + // XXX if `value` is `None` we not produce any output for this field + if !self.first { + self.ser.buf.push(b','); + } + self.first = false; + self.ser.buf.push(b'\n'); + self.ser.indent()?; + + self.ser.buf.push(b'"'); + self.ser.buf.extend_from_slice(key.as_bytes()); + self.ser.buf.extend_from_slice(b"\": "); + + value.serialize(&mut *self.ser)?; + + Ok(()) + } + + fn end(self) -> Result { + for _ in 0..2 { + self.ser.current_indent -= 1; + if !self.first { + self.ser.buf.push(b'\n'); + self.ser.indent()?; + } + self.ser.buf.push(b'}'); + } + Ok(()) + } +}