Skip to content

Commit

Permalink
Serialize hex strings to bytes (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
olanod authored Dec 6, 2021
1 parent 150ba73 commit 44c7460
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ scale-info = { version = "1.0.0", default-features = false, features = ["serde"]
serde = { version = "1.0.130", default-features = false }
serde_json = { version = "1.0.72", default-features = false, optional = true }
codec = { version = "2.3.1", package = "parity-scale-codec", default-features = false, optional = true }
hex = { version = "0.4.3", default-features = false, features = ["alloc"], optional = true }

[features]
default = ["std", "codec", "json", "experimental-serializer"]
default = ["std", "codec", "json", "hex", "experimental-serializer"]
std = ["scale-info/std", "bytes/std"]
experimental-serializer = []
json = ["serde_json/preserve_order"]
Expand Down
13 changes: 8 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ pub enum SpecificType {
Bool,
U8, U16, U32, U64, U128,
I8, I16, I32, I64, I128,
Bytes,
Char,
Str,
Bytes(TypeId),
Sequence(TypeId),
Map(TypeId, TypeId),
Tuple(TupleOrArray),
Expand Down Expand Up @@ -116,11 +116,14 @@ impl From<(&Type, &PortableRegistry)> for SpecificType {
}
Def::Variant(v) => Self::Variant(name.into(), v.variants().into(), None),
Def::Sequence(s) => {
let ty = resolve!(s.type_param());
if ty.path().segments() != ["u8"] {
Self::Sequence(s.type_param().id())
let ty = s.type_param();
if matches!(
resolve!(ty).type_def(),
Def::Primitive(TypeDefPrimitive::U8)
) {
Self::Bytes(ty.id())
} else {
Self::Bytes
Self::Sequence(ty.id())
}
}
Def::Array(a) => Self::Tuple(TupleOrArray::Array(a.type_param().id(), a.len())),
Expand Down
70 changes: 60 additions & 10 deletions src/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ where
}

fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
// all numbers in serde_json are the same
self.maybe_some()?;
// all numbers in serde_json are the same
match self.ty {
Some(SpecificType::I8) => self.serialize_i8(v as i8)?,
Some(SpecificType::I16) => self.serialize_i16(v as i16)?,
Expand Down Expand Up @@ -247,6 +247,8 @@ where

fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
self.maybe_some()?;

compact_number(v.len(), &mut self.out);
self.out.put(v);
Ok(())
}
Expand Down Expand Up @@ -309,7 +311,10 @@ where

fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
self.maybe_some()?;
if matches!(self.ty, None | Some(SpecificType::Sequence(_))) {
if matches!(
self.ty,
None | Some(SpecificType::Bytes(_)) | Some(SpecificType::Sequence(_))
) {
compact_number(len.expect("known length"), &mut self.out);
}
Ok(self.into())
Expand Down Expand Up @@ -392,7 +397,6 @@ where

#[inline]
fn maybe_other(&mut self, val: &str) -> Result<Option<()>> {
use core::any::type_name;
match self.ty {
Some(SpecificType::Str) | None => Ok(None),
// { "foo": "Bar" } => "Bar" might be an enum variant
Expand All @@ -405,9 +409,26 @@ where
Some(SpecificType::StructNewType(ty)) => match self.resolve(ty) {
// { "foo": "bar" } => "bar" might be a string wrapped in a type
SpecificType::Str => Ok(None),
_ => Err(Error::NotSupported(type_name::<&str>())),
ref ty => Err(Error::NotSupported(
type_name_of_val(val),
format!("{:?}", ty),
)),
},
Some(_) => Err(Error::NotSupported(type_name::<&str>())),
#[cfg(feature = "hex")]
Some(SpecificType::Bytes(_)) => {
if val.starts_with("0x") {
let bytes =
hex::decode(&val[2..]).map_err(|e| Error::BadInput(e.to_string()))?;
ser::Serializer::serialize_bytes(self, &bytes)?;
Ok(Some(()))
} else {
Err(Error::BadInput("Hex string must start with 0x".into()))
}
}
Some(ref ty) => Err(Error::NotSupported(
type_name_of_val(val),
format!("{:?}", ty),
)),
}
}
}
Expand All @@ -430,7 +451,7 @@ impl<'a, 'reg, B: 'a> From<&'a mut Serializer<'reg, B>> for TypedSerializer<'a,
Some(StructTuple(fields)) => Self::Composite(ser, fields),
Some(Tuple(TupleOrArray::Array(ty, _))) => Self::Sequence(ser, ty),
Some(Tuple(TupleOrArray::Tuple(fields))) => Self::Composite(ser, fields),
Some(Sequence(ty)) => Self::Sequence(ser, ty),
Some(Sequence(ty) | Bytes(ty)) => Self::Sequence(ser, ty),
Some(Map(_, _)) => Self::Empty(ser),
Some(var @ Variant(_, _, Some(_))) => match (&var).into() {
EnumVariant::Tuple(_, _, types) => Self::Composite(ser, types),
Expand Down Expand Up @@ -653,7 +674,7 @@ pub enum Error {
Ser(String),
BadInput(String),
Type(scale_info::Type<scale_info::form::PortableForm>),
NotSupported(&'static str),
NotSupported(&'static str, String),
}

impl fmt::Display for Error {
Expand All @@ -666,7 +687,9 @@ impl fmt::Display for Error {
"Unexpected type: {}",
ty.path().ident().unwrap_or_else(|| "Unknown".into())
),
Error::NotSupported(from) => write!(f, "Serializing {} as type is not supported", from),
Error::NotSupported(from, to) => {
write!(f, "Serializing {} as {} is not supported", from, to)
}
}
}
}
Expand All @@ -682,7 +705,7 @@ impl ser::Error for Error {
}
}

// from https://github.com/paritytech/parity-scale-codec/blob/master/src/compact.rs#L336
// adapted from https://github.com/paritytech/parity-scale-codec/blob/master/src/compact.rs#L336
#[allow(clippy::all)]
fn compact_number(n: usize, mut dest: impl BufMut) {
match n {
Expand All @@ -709,6 +732,11 @@ fn compact_number(n: usize, mut dest: impl BufMut) {
}
}

// nightly only
fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
core::any::type_name::<T>()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -1058,7 +1086,7 @@ mod tests {
let input = Foo {
bar: [Bar::That(i16::MAX), Bar::This].into(),
baz: Some(Baz("lorem ipsum".into())),
lol: b"\0xFFsome stuff\0x00",
lol: b"\xFFsome stuff\x00",
};
let mut out = Vec::<u8>::new();
let expected = input.encode();
Expand Down Expand Up @@ -1104,4 +1132,26 @@ mod tests {
assert_eq!(out, expected);
Ok(())
}

#[test]
fn test_bytes_as_hex_string() -> Result<()> {
#[derive(Debug, Encode, TypeInfo, Serialize)]
struct Foo {
bar: Vec<u8>,
}
let foo = Foo {
bar: b"\x00\x12\x34\x56".to_vec(),
};
let (ty, reg) = register(&foo);

let hex_string = "0x00123456";

let input = vec![("bar", crate::JsonValue::String(hex_string.into()))];

let out = to_vec_from_iter(input, (&reg, ty))?;
let expected = foo.encode();

assert_eq!(out, expected);
Ok(())
}
}
8 changes: 6 additions & 2 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ impl<'a> Serialize for Value<'a> {
I32 => ser.serialize_i32(data.get_i32_le()),
I64 => ser.serialize_i64(data.get_i64_le()),
I128 => ser.serialize_i128(data.get_i128_le()),
Bytes => ser.serialize_bytes(data.chunk()),
Bytes(_) => {
let (_, s) = sequence_len(data.chunk());
data.advance(s);
ser.serialize_bytes(data.chunk())
}
Char => ser.serialize_char(char::from_u32(data.get_u32_le()).unwrap()),
Str => {
let (_, s) = sequence_len(data.chunk());
Expand Down Expand Up @@ -442,7 +446,7 @@ mod tests {

#[test]
fn serialize_u8array() -> Result<(), Error> {
let in_value: Vec<u8> = [2u8, u8::MAX].into();
let in_value: Vec<u8> = [2u8; u8::MAX as usize].into();
let data = in_value.encode();
let (id, reg) = register(&in_value);

Expand Down

0 comments on commit 44c7460

Please sign in to comment.