From dd897a032482ce7b0b7292416d2505c39c0938aa Mon Sep 17 00:00:00 2001 From: Eval EXEC Date: Tue, 21 Mar 2023 21:58:06 +0800 Subject: [PATCH 1/2] Implement serde feature Signed-off-by: Eval EXEC --- Cargo.toml | 9 +++- src/lib.rs | 10 ++++ src/serde.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/serde.rs diff --git a/Cargo.toml b/Cargo.toml index 08f2986..3ce4263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,14 @@ exclude = [ "CHANGELOG.md" ] +[dependencies] +serde = { version = "1.0", optional = true, features = ["derive"]} + [features] -default = ["std"] +default = ["std", "serde"] std = ["alloc"] alloc = [] +serde = ["dep:serde"] @@ -28,6 +32,9 @@ criterion = "0.3" rustc-hex = "1.0" hex = "0.3.2" proptest = "1.0" +serde = { version = "1.0", features = ["derive"]} +bytes = {version = "1.4.0"} +serde_json ={ version = "*"} [[bench]] name = "hex" diff --git a/src/lib.rs b/src/lib.rs index 657f6b0..ac51a5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,10 @@ extern crate alloc; mod decode; mod encode; mod error; + +#[cfg(feature = "serde")] +mod serde; + pub use crate::decode::{ hex_check, hex_check_fallback, hex_check_with_case, hex_decode, hex_decode_fallback, hex_decode_unchecked, @@ -18,6 +22,12 @@ pub use crate::encode::{hex_string, hex_string_upper}; pub use crate::error::Error; +#[cfg(feature = "serde")] +pub use crate::serde::{ + deserialize, nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase, serialize, withpfx_ignorecase, + withpfx_lowercase, withpfx_uppercase, +}; + #[allow(deprecated)] pub use crate::encode::hex_to; diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..3bbf790 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,146 @@ +#![warn(missing_docs)] + +use std::iter::FromIterator; + +mod internal { + use crate::{ + decode::{hex_decode_with_case, CheckCase}, + encode::hex_encode_custom, + }; + use serde::{de::Error, Deserializer, Serializer}; + use std::iter::FromIterator; + + pub(crate) fn serialize( + data: T, + serializer: S, + with_prefix: bool, + case: CheckCase, + ) -> Result + where + S: Serializer, + T: AsRef<[u8]>, + { + let src: &[u8] = data.as_ref(); + + let mut dst_length = data.as_ref().len() << 1; + if with_prefix { + dst_length += 2; + } + + let mut dst = vec![0u8; dst_length]; + let mut dst_start = 0; + if with_prefix { + dst[0] = b'0'; + dst[1] = b'x'; + + dst_start = 2; + } + + hex_encode_custom(src, &mut dst[dst_start..], matches!(case, CheckCase::Upper)) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_str(unsafe { ::std::str::from_utf8_unchecked(&dst) }) + } + + pub(crate) fn deserialize<'de, D, T>( + deserializer: D, + with_prefix: bool, + check_case: CheckCase, + ) -> Result + where + D: Deserializer<'de>, + T: FromIterator, + { + let raw_src: &[u8] = serde::Deserialize::deserialize(deserializer)?; + if with_prefix && (raw_src.len() < 2 || raw_src[0] != b'0' || raw_src[1] != b'x') { + return Err(D::Error::custom("invalid prefix".to_string())); + } + + let src: &[u8] = { + if with_prefix { + &raw_src[2..] + } else { + raw_src + } + }; + + if src.len() & 1 != 0 { + return Err(D::Error::custom("invalid length".to_string())); + } + + // we have already checked src's length, so src's length is a even integer + let mut dst = vec![0; src.len() >> 1]; + hex_decode_with_case(src, &mut dst, check_case) + .map_err(|e| Error::custom(format!("{:?}", e)))?; + Ok(dst.into_iter().collect()) + } +} + +/// Serde: Serialize with 0x-prefix and ignore case +pub fn serialize(data: T, serializer: S) -> Result +where + S: serde::Serializer, + T: AsRef<[u8]>, +{ + withpfx_ignorecase::serialize(data, serializer) +} + +/// Serde: Deserialize with 0x-prefix and ignore case +pub fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + T: FromIterator, +{ + withpfx_ignorecase::deserialize(deserializer) +} + +/// Generate module with serde methods +#[macro_export] +macro_rules! faster_hex_serde_macros { + ($mod_name:ident, $with_pfx:expr, $check_case:expr) => { + /// Serialize and deserialize with or without 0x-prefix, + /// and lowercase or uppercase or ignorecase + pub mod $mod_name { + use crate::decode::CheckCase; + use crate::serde::internal; + use std::iter::FromIterator; + + /// Serializes `data` as hex string + pub fn serialize(data: T, serializer: S) -> Result + where + S: serde::Serializer, + T: AsRef<[u8]>, + { + internal::serialize(data, serializer, $with_pfx, $check_case) + } + + /// Deserializes a hex string into raw bytes. + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: FromIterator, + { + internal::deserialize(deserializer, $with_pfx, $check_case) + } + } + }; +} + +// /// Serialize with 0x-prefix and lowercase +// /// When deserialize, expect 0x-prefix and don't care case +faster_hex_serde_macros!(withpfx_ignorecase, true, CheckCase::None); +// /// Serialize without 0x-prefix and lowercase +// /// When deserialize, expect without 0x-prefix and don't care case +faster_hex_serde_macros!(nopfx_ignorecase, false, CheckCase::None); +// /// Serialize with 0x-prefix and lowercase +// /// When deserialize, expect with 0x-prefix and lower case +faster_hex_serde_macros!(withpfx_lowercase, true, CheckCase::Lower); +// /// Serialize without 0x-prefix and lowercase +// /// When deserialize, expect without 0x-prefix and lower case +faster_hex_serde_macros!(nopfx_lowercase, false, CheckCase::Lower); + +// /// Serialize with 0x-prefix and upper case +// /// When deserialize, expect with 0x-prefix and upper case +faster_hex_serde_macros!(withpfx_uppercase, true, CheckCase::Upper); +// /// Serialize without 0x-prefix and upper case +// /// When deserialize, expect without 0x-prefix and upper case +faster_hex_serde_macros!(nopfx_uppercase, false, CheckCase::Upper); From c45c026cc5531fbe065fb582a59a080fd2ca8945 Mon Sep 17 00:00:00 2001 From: Eval EXEC Date: Tue, 21 Mar 2023 21:58:43 +0800 Subject: [PATCH 2/2] Add serde feature test case Signed-off-by: Eval EXEC --- src/serde.rs | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/src/serde.rs b/src/serde.rs index 3bbf790..49b0f48 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -144,3 +144,217 @@ faster_hex_serde_macros!(withpfx_uppercase, true, CheckCase::Upper); // /// Serialize without 0x-prefix and upper case // /// When deserialize, expect without 0x-prefix and upper case faster_hex_serde_macros!(nopfx_uppercase, false, CheckCase::Upper); + +#[cfg(test)] +mod tests { + use super::{ + nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase, withpfx_ignorecase, withpfx_lowercase, + withpfx_uppercase, + }; + use crate as faster_hex; + use bytes::Bytes; + use proptest::proptest; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Simple { + #[serde(with = "faster_hex")] + bar: Vec, + } + + fn _test_simple(src: &str) { + let simple = Simple { bar: src.into() }; + let result = serde_json::to_string(&simple); + assert!(result.is_ok()); + let result = result.unwrap(); + + // #[serde(with = "faster_hex")] should result with 0x prefix + assert!(result.starts_with(r#"{"bar":"0x"#)); + + // #[serde(with = "faster_hex")] shouldn't contains uppercase + assert!(result[7..].chars().all(|c| !c.is_uppercase())); + + let decode_simple = serde_json::from_str::(&result); + assert!(decode_simple.is_ok()); + assert_eq!(decode_simple.unwrap(), simple); + } + + proptest! { + #[test] + fn test_simple(ref s in ".*") { + _test_simple(s); + } + } + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Foo { + #[serde(with = "nopfx_lowercase")] + bar_nopfx_lowercase_vec: Vec, + #[serde(with = "nopfx_lowercase")] + bar_nopfx_lowercase_bytes: Bytes, + + #[serde(with = "withpfx_lowercase")] + bar_withpfx_lowercase_vec: Vec, + #[serde(with = "withpfx_lowercase")] + bar_withpfx_lowercase_bytes: Bytes, + + #[serde(with = "nopfx_uppercase")] + bar_nopfx_uppercase_vec: Vec, + #[serde(with = "nopfx_uppercase")] + bar_nopfx_uppercase_bytes: Bytes, + + #[serde(with = "withpfx_uppercase")] + bar_withpfx_uppercase_vec: Vec, + #[serde(with = "withpfx_uppercase")] + bar_withpfx_uppercase_bytes: Bytes, + + #[serde(with = "withpfx_ignorecase")] + bar_withpfx_ignorecase_vec: Vec, + #[serde(with = "withpfx_ignorecase")] + bar_withpfx_ignorecase_bytes: Bytes, + + #[serde(with = "nopfx_ignorecase")] + bar_nopfx_ignorecase_vec: Vec, + #[serde(with = "nopfx_ignorecase")] + bar_nopfx_ignorecase_bytes: Bytes, + } + + #[test] + fn test_serde_default() { + { + let foo_defuault = Foo { + bar_nopfx_lowercase_vec: vec![], + bar_nopfx_lowercase_bytes: Default::default(), + bar_withpfx_lowercase_vec: vec![], + bar_withpfx_lowercase_bytes: Default::default(), + bar_nopfx_uppercase_vec: vec![], + bar_nopfx_uppercase_bytes: Default::default(), + bar_withpfx_uppercase_vec: vec![], + bar_withpfx_uppercase_bytes: Default::default(), + bar_withpfx_ignorecase_vec: vec![], + bar_withpfx_ignorecase_bytes: Default::default(), + bar_nopfx_ignorecase_vec: vec![], + bar_nopfx_ignorecase_bytes: Default::default(), + }; + let serde_result = serde_json::to_string(&foo_defuault).unwrap(); + let expect = "{\"bar_nopfx_lowercase_vec\":\"\",\"bar_nopfx_lowercase_bytes\":\"\",\"bar_withpfx_lowercase_vec\":\"0x\",\"bar_withpfx_lowercase_bytes\":\"0x\",\"bar_nopfx_uppercase_vec\":\"\",\"bar_nopfx_uppercase_bytes\":\"\",\"bar_withpfx_uppercase_vec\":\"0x\",\"bar_withpfx_uppercase_bytes\":\"0x\",\"bar_withpfx_ignorecase_vec\":\"0x\",\"bar_withpfx_ignorecase_bytes\":\"0x\",\"bar_nopfx_ignorecase_vec\":\"\",\"bar_nopfx_ignorecase_bytes\":\"\"}"; + assert_eq!(serde_result, expect); + + let foo_src: Foo = serde_json::from_str(&serde_result).unwrap(); + assert_eq!(foo_defuault, foo_src); + } + } + + fn _test_serde(src: &str) { + let foo = Foo { + bar_nopfx_lowercase_vec: Vec::from(src), + bar_nopfx_lowercase_bytes: Bytes::from(Vec::from(src)), + bar_withpfx_lowercase_vec: Vec::from(src), + bar_withpfx_lowercase_bytes: Bytes::from(Vec::from(src)), + bar_nopfx_uppercase_vec: Vec::from(src), + bar_nopfx_uppercase_bytes: Bytes::from(Vec::from(src)), + bar_withpfx_uppercase_vec: Vec::from(src), + bar_withpfx_uppercase_bytes: Bytes::from(Vec::from(src)), + + bar_withpfx_ignorecase_vec: Vec::from(src), + bar_withpfx_ignorecase_bytes: Bytes::from(Vec::from(src)), + bar_nopfx_ignorecase_vec: Vec::from(src), + bar_nopfx_ignorecase_bytes: Bytes::from(Vec::from(src)), + }; + let hex_str = hex::encode(src); + let hex_str_upper = hex::encode_upper(src); + let serde_result = serde_json::to_string(&foo).unwrap(); + + let expect = format!("{{\"bar_nopfx_lowercase_vec\":\"{}\",\"bar_nopfx_lowercase_bytes\":\"{}\",\"bar_withpfx_lowercase_vec\":\"0x{}\",\"bar_withpfx_lowercase_bytes\":\"0x{}\",\"bar_nopfx_uppercase_vec\":\"{}\",\"bar_nopfx_uppercase_bytes\":\"{}\",\"bar_withpfx_uppercase_vec\":\"0x{}\",\"bar_withpfx_uppercase_bytes\":\"0x{}\",\"bar_withpfx_ignorecase_vec\":\"0x{}\",\"bar_withpfx_ignorecase_bytes\":\"0x{}\",\"bar_nopfx_ignorecase_vec\":\"{}\",\"bar_nopfx_ignorecase_bytes\":\"{}\"}}", + hex_str, + hex_str, + hex_str, + hex_str, + hex_str_upper, + hex_str_upper, + hex_str_upper, + hex_str_upper, + hex_str, + hex_str, + hex_str, + hex_str, + + ); + assert_eq!(serde_result, expect); + + let foo_src: Foo = serde_json::from_str(&serde_result).unwrap(); + assert_eq!(foo, foo_src); + } + + proptest! { + #[test] + fn test_serde(ref s in ".*") { + _test_serde(s); + } + } + + fn _test_serde_deserialize(src: &str) { + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooNoPfxLower { + #[serde(with = "nopfx_lowercase")] + bar: Vec, + } + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooWithPfxLower { + #[serde(with = "withpfx_lowercase")] + bar: Vec, + } + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooNoPfxUpper { + #[serde(with = "nopfx_uppercase")] + bar: Vec, + } + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooWithPfxUpper { + #[serde(with = "withpfx_uppercase")] + bar: Vec, + } + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooNoPfxIgnoreCase { + #[serde(with = "nopfx_ignorecase")] + bar: Vec, + } + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct FooWithPfxIgnoreCase { + #[serde(with = "withpfx_ignorecase")] + bar: Vec, + } + + { + let hex_foo = serde_json::to_string(&FooNoPfxLower { bar: src.into() }).unwrap(); + let foo_pfx: serde_json::Result = serde_json::from_str(&hex_foo); + // assert foo_pfx is Error, and contains "invalid prefix" + assert!(foo_pfx.is_err()); + assert!(foo_pfx.unwrap_err().to_string().contains("invalid prefix")); + } + + { + let foo_lower = serde_json::to_string(&FooNoPfxLower { bar: src.into() }).unwrap(); + let foo_upper_result: serde_json::Result = + serde_json::from_str(&foo_lower); + if hex::encode(src).contains(char::is_lowercase) { + // FooNoPfxLower's foo field is lowercase, so we can't deserialize it to FooNoPfxUpper + assert!(foo_upper_result.is_err()); + assert!(foo_upper_result + .unwrap_err() + .to_string() + .contains("Invalid character")); + } + } + } + + proptest! { + #[test] + fn test_serde_deserialize(ref s in ".*") { + _test_serde_deserialize(s); + } + } +}