From e18f3b3d01ea17ea629bae618cd811f63b8d3eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 16 Oct 2023 20:45:52 +0300 Subject: [PATCH 1/3] feat: impl `BorshSerialize`/`BorshDeserialize` for `char` --- borsh/src/de/mod.rs | 14 ++++ borsh/src/ser/mod.rs | 7 ++ .../snapshots/test_primitives__char.snap | 10 +++ .../snapshots/test_primitives__char_vec.snap | 70 +++++++++++++++++++ borsh/tests/test_de_errors.rs | 18 +++++ borsh/tests/test_primitives.rs | 21 ++++++ 6 files changed, 140 insertions(+) create mode 100644 borsh/tests/snapshots/test_primitives__char.snap create mode 100644 borsh/tests/snapshots/test_primitives__char_vec.snap diff --git a/borsh/src/de/mod.rs b/borsh/src/de/mod.rs index 0d84a8dcb..1c3b9d51a 100644 --- a/borsh/src/de/mod.rs +++ b/borsh/src/de/mod.rs @@ -315,6 +315,20 @@ impl BorshDeserialize for bool { } } +impl BorshDeserialize for char { + #[inline] + fn deserialize_reader(reader: &mut R) -> Result { + let int: u32 = BorshDeserialize::deserialize_reader(reader)?; + + char::from_u32(int).ok_or_else(|| { + let msg = format!( + "to `char` conversion: u32 is not a valid Unicode Scalar Value: {}", + int + ); + Error::new(ErrorKind::InvalidData, msg) + }) + } +} impl BorshDeserialize for Option where T: BorshDeserialize, diff --git a/borsh/src/ser/mod.rs b/borsh/src/ser/mod.rs index e0f9f671c..e1a80a2fb 100644 --- a/borsh/src/ser/mod.rs +++ b/borsh/src/ser/mod.rs @@ -160,6 +160,13 @@ impl BorshSerialize for bool { } } +impl BorshSerialize for char { + #[inline] + fn serialize(&self, writer: &mut W) -> Result<()> { + (*self as u32).serialize(writer) + } +} + impl BorshSerialize for Option where T: BorshSerialize, diff --git a/borsh/tests/snapshots/test_primitives__char.snap b/borsh/tests/snapshots/test_primitives__char.snap new file mode 100644 index 000000000..64b73217f --- /dev/null +++ b/borsh/tests/snapshots/test_primitives__char.snap @@ -0,0 +1,10 @@ +--- +source: borsh/tests/test_primitives.rs +expression: buf +--- +[ + 97, + 0, + 0, + 0, +] diff --git a/borsh/tests/snapshots/test_primitives__char_vec.snap b/borsh/tests/snapshots/test_primitives__char_vec.snap new file mode 100644 index 000000000..4fd80ec29 --- /dev/null +++ b/borsh/tests/snapshots/test_primitives__char_vec.snap @@ -0,0 +1,70 @@ +--- +source: borsh/tests/test_primitives.rs +expression: buf +--- +[ + 15, + 0, + 0, + 0, + 104, + 0, + 0, + 0, + 100, + 39, + 0, + 0, + 15, + 254, + 0, + 0, + 108, + 0, + 0, + 0, + 108, + 0, + 0, + 0, + 111, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 208, + 143, + 0, + 0, + 40, + 117, + 0, + 0, + 113, + 95, + 0, + 0, + 205, + 84, + 0, + 0, + 229, + 78, + 0, + 0, + 190, + 143, + 0, + 0, + 238, + 118, + 0, + 0, + 132, + 118, + 0, + 0, +] diff --git a/borsh/tests/test_de_errors.rs b/borsh/tests/test_de_errors.rs index 88253823b..768597a75 100644 --- a/borsh/tests/test_de_errors.rs +++ b/borsh/tests/test_de_errors.rs @@ -203,6 +203,24 @@ fn test_zero_on_nonzero_integer_u32() { ); } +#[test] +fn test_invalid_char_1() { + let bytes = &[0u8, 0u8, 17, 0u8]; + assert_eq!( + from_slice::(bytes).unwrap_err().to_string(), + "to `char` conversion: u32 is not a valid Unicode Scalar Value: 1114112" + ); +} + +#[test] +fn test_invalid_char_2() { + let bytes = &[1u8, 222u8, 0u8, 0u8]; + assert_eq!( + from_slice::(bytes).unwrap_err().to_string(), + "to `char` conversion: u32 is not a valid Unicode Scalar Value: 56833" + ); +} + #[test] fn test_zero_on_nonzero_integer_i64() { let bytes = &[0; 8]; diff --git a/borsh/tests/test_primitives.rs b/borsh/tests/test_primitives.rs index aa74db385..cd5ffe71a 100644 --- a/borsh/tests/test_primitives.rs +++ b/borsh/tests/test_primitives.rs @@ -1,6 +1,12 @@ #![cfg_attr(not(feature = "std"), no_std)] + use borsh::{from_slice, to_vec}; +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + macro_rules! test_primitive { ($test_name: ident, $v: expr, $t: ty) => { #[test] @@ -24,3 +30,18 @@ test_primitive!(test_isize_max, isize::max_value(), isize); test_primitive!(test_usize, 100usize, usize); test_primitive!(test_usize_min, usize::min_value(), usize); test_primitive!(test_usize_max, usize::max_value(), usize); + +test_primitive!(test_char, 'a', char); + +#[test] +fn test_char_vec() { + let initial = "h❤️llo 运用影响以达目的"; + let expected = initial.chars().collect::>(); + + let buf = to_vec(&expected).unwrap(); + #[cfg(feature = "std")] + insta::assert_debug_snapshot!(buf); + let actual = from_slice::>(&buf).expect("failed to deserialize"); + + assert_eq!(actual, expected); +} From 52f719ecde47291fbd36ae9b604de1463f27aa79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 16 Oct 2023 21:11:00 +0300 Subject: [PATCH 2/3] feat: impl `BorshSchema` for `char` --- borsh/src/schema.rs | 15 +++++++++++++++ borsh/src/schema/container_ext/max_size.rs | 1 + 2 files changed, 16 insertions(+) diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs index 0fce4652a..24c37b379 100644 --- a/borsh/src/schema.rs +++ b/borsh/src/schema.rs @@ -350,6 +350,7 @@ macro_rules! impl_for_primitives { } impl_for_primitives!(bool => 1; f32 => 4; f64 => 8; i8 => 1; i16 => 2; i32 => 4; i64 => 8; i128 => 16); +impl_for_primitives!(char => 4); impl_for_primitives!(u8 => 1; u16 => 2; u32 => 4; u64 => 8; u128 => 16); impl_for_renamed_primitives!(isize: i64 => 8); impl_for_renamed_primitives!(usize: u64 => 8); @@ -737,6 +738,20 @@ mod tests { ); } + #[test] + fn char() { + let actual_name = char::declaration(); + let mut actual_defs = map!(); + char::add_definitions_recursively(&mut actual_defs); + assert_eq!("char", actual_name); + assert_eq!( + map! { + "char" => Definition::Primitive(4) + }, + actual_defs + ); + } + #[test] fn nested_option() { let actual_name = Option::>::declaration(); diff --git a/borsh/src/schema/container_ext/max_size.rs b/borsh/src/schema/container_ext/max_size.rs index 782955a87..469ef45bc 100644 --- a/borsh/src/schema/container_ext/max_size.rs +++ b/borsh/src/schema/container_ext/max_size.rs @@ -326,6 +326,7 @@ mod tests { fn max_serialized_size_primitives() { test_ok::<()>(0); test_ok::(1); + test_ok::(4); test_ok::(4); test_ok::(8); From de7598558203ff835d3e747bd3bf237ea2796af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 17 Oct 2023 13:58:37 +0300 Subject: [PATCH 3/3] chore: change u32 repr in error to hexadecimal --- borsh/src/de/mod.rs | 2 +- borsh/tests/test_de_errors.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/borsh/src/de/mod.rs b/borsh/src/de/mod.rs index 1c3b9d51a..2aa8a3112 100644 --- a/borsh/src/de/mod.rs +++ b/borsh/src/de/mod.rs @@ -322,7 +322,7 @@ impl BorshDeserialize for char { char::from_u32(int).ok_or_else(|| { let msg = format!( - "to `char` conversion: u32 is not a valid Unicode Scalar Value: {}", + "to `char` conversion: u32 is not a valid Unicode Scalar Value: {:#x}", int ); Error::new(ErrorKind::InvalidData, msg) diff --git a/borsh/tests/test_de_errors.rs b/borsh/tests/test_de_errors.rs index 768597a75..6a35de72c 100644 --- a/borsh/tests/test_de_errors.rs +++ b/borsh/tests/test_de_errors.rs @@ -208,7 +208,7 @@ fn test_invalid_char_1() { let bytes = &[0u8, 0u8, 17, 0u8]; assert_eq!( from_slice::(bytes).unwrap_err().to_string(), - "to `char` conversion: u32 is not a valid Unicode Scalar Value: 1114112" + "to `char` conversion: u32 is not a valid Unicode Scalar Value: 0x110000" ); } @@ -217,7 +217,7 @@ fn test_invalid_char_2() { let bytes = &[1u8, 222u8, 0u8, 0u8]; assert_eq!( from_slice::(bytes).unwrap_err().to_string(), - "to `char` conversion: u32 is not a valid Unicode Scalar Value: 56833" + "to `char` conversion: u32 is not a valid Unicode Scalar Value: 0xde01" ); }