diff --git a/Cargo.lock b/Cargo.lock index 48ac1b0d..a4611c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4756,6 +4756,7 @@ version = "0.2.0" dependencies = [ "paste", "serde", + "serde_bytes", "serde_test", "tangle-subxt", ] diff --git a/Cargo.toml b/Cargo.toml index 2a844379..ef20b058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ reqwest = "0.12.7" rustdoc-types = "0.31.0" schnorrkel = { version = "0.11.4", default-features = false, features = ["preaudit_deprecated", "getrandom"] } serde = { version = "1.0.208", default-features = false } +serde_bytes = { version = "0.11.15", default-features = false } serde_json = "1.0" serde_test = "1.0.177" sha2 = "0.10.8" diff --git a/blueprint-serde/Cargo.toml b/blueprint-serde/Cargo.toml index 4be99d22..13fc0431 100644 --- a/blueprint-serde/Cargo.toml +++ b/blueprint-serde/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] paste.workspace = true serde.workspace = true +serde_bytes = { workspace = true, features = ["alloc"] } tangle-subxt.workspace = true [dev-dependencies] @@ -21,4 +22,7 @@ workspace = true [features] default = ["std"] -std = [] +std = [ + "serde/std", + "serde_bytes/std" +] diff --git a/blueprint-serde/src/de.rs b/blueprint-serde/src/de.rs index 87d0b12f..ffbd6cd8 100644 --- a/blueprint-serde/src/de.rs +++ b/blueprint-serde/src/de.rs @@ -1,10 +1,10 @@ use crate::error::{Error, Result, UnsupportedType}; use crate::Field; use alloc::collections::BTreeMap; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; -use serde::de; use serde::de::IntoDeserializer; +use serde::{de, forward_to_deserialize_any}; use tangle_subxt::subxt_core::utils::AccountId32; use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::BoundedString; @@ -16,24 +16,6 @@ use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives: /// See [`crate::from_field`]. pub struct Deserializer(pub(crate) Field); -macro_rules! deserialize_primitive { - ($($t:ty => $pat:pat),+ $(,)?) => { - $( - paste::paste! { - fn [](self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.0 { - $pat(value) => visitor.[](value), - _ => Err(self.invalid_type(&visitor)) - } - } - } - )+ - } -} - impl<'de> de::Deserializer<'de> for Deserializer { type Error = Error; @@ -56,25 +38,16 @@ impl<'de> de::Deserializer<'de> for Deserializer { let s = String::from_utf8(s.0 .0)?; visitor.visit_string(s) } - Field::Bytes(b) => visitor.visit_bytes(b.0.as_slice()), + Field::Bytes(b) => { + // Unless `deserialize_bytes` is explicitly called, assume a sequence is desired + de::value::SeqDeserializer::new(b.0.into_iter()).deserialize_any(visitor) + } Field::Array(seq) | Field::List(seq) => visit_seq(seq.0, visitor), Field::Struct(_, fields) => visit_struct(*fields, visitor), - Field::AccountId(a) => visitor.visit_bytes(a.0.as_slice()), + Field::AccountId(a) => visitor.visit_string(a.to_string()), } } - deserialize_primitive!( - bool => Field::Bool, - i8 => Field::Int8, - i16 => Field::Int16, - i32 => Field::Int32, - i64 => Field::Int64, - u8 => Field::Uint8, - u16 => Field::Uint16, - u32 => Field::Uint32, - u64 => Field::Uint64, - ); - fn deserialize_f32(self, _visitor: V) -> Result where V: de::Visitor<'de>, @@ -118,36 +91,6 @@ impl<'de> de::Deserializer<'de> for Deserializer { self.deserialize_string(visitor) } - fn deserialize_string(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.0 { - Field::String(bound_string) => { - visitor.visit_string(String::from_utf8(bound_string.0 .0)?) - } - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.deserialize_byte_buf(visitor) - } - - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.0 { - Field::Bytes(seq) => visitor.visit_byte_buf(seq.0), - Field::String(s) => visitor.visit_string(String::from_utf8(s.0 .0)?), - _ => Err(self.invalid_type(&visitor)), - } - } - fn deserialize_option(self, visitor: V) -> Result where V: de::Visitor<'de>, @@ -182,23 +125,6 @@ impl<'de> de::Deserializer<'de> for Deserializer { visitor.visit_newtype_struct(self) } - fn deserialize_seq(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.0 { - Field::Array(seq) | Field::List(seq) => visit_seq(seq.0, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_tuple(self, _len: usize, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.deserialize_seq(visitor) - } - fn deserialize_tuple_struct( self, _name: &'static str, @@ -228,21 +154,6 @@ impl<'de> de::Deserializer<'de> for Deserializer { Err(Error::UnsupportedType(UnsupportedType::Map)) } - fn deserialize_struct( - self, - _name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - match self.0 { - Field::Struct(_name, fields) => visit_struct(*fields, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - fn deserialize_enum( self, _name: &'static str, @@ -261,13 +172,6 @@ impl<'de> de::Deserializer<'de> for Deserializer { } } - fn deserialize_identifier(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.deserialize_str(visitor) - } - fn deserialize_ignored_any(self, visitor: V) -> Result where V: de::Visitor<'de>, @@ -275,6 +179,11 @@ impl<'de> de::Deserializer<'de> for Deserializer { drop(self); visitor.visit_unit() } + + forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 string + bytes byte_buf seq struct identifier tuple + } } impl Deserializer { diff --git a/blueprint-serde/src/lib.rs b/blueprint-serde/src/lib.rs index c1df4d12..53270c8b 100644 --- a/blueprint-serde/src/lib.rs +++ b/blueprint-serde/src/lib.rs @@ -58,6 +58,7 @@ use tangle_subxt::subxt_core::utils::AccountId32; pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field; pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; pub use ser::new_bounded_string; +pub use serde_bytes::ByteBuf; use error::Result; /// Derive a [`Field`] from an instance of type `S` diff --git a/blueprint-serde/src/ser.rs b/blueprint-serde/src/ser.rs index 04b02036..5099b3d8 100644 --- a/blueprint-serde/src/ser.rs +++ b/blueprint-serde/src/ser.rs @@ -21,10 +21,10 @@ impl<'a> serde::Serializer for &'a mut Serializer { type SerializeSeq = SerializeSeq<'a>; type SerializeTuple = Self::SerializeSeq; type SerializeTupleStruct = SerializeTupleStruct<'a>; - type SerializeTupleVariant = Self; - type SerializeMap = Self; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = ser::Impossible; type SerializeStruct = SerializeStruct<'a>; - type SerializeStructVariant = Self; + type SerializeStructVariant = ser::Impossible; fn serialize_bool(self, v: bool) -> Result { Ok(Field::Bool(v)) @@ -164,7 +164,7 @@ impl<'a> serde::Serializer for &'a mut Serializer { _variant: &'static str, _len: usize, ) -> Result { - Ok(self) + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) } fn serialize_map(self, _len: Option) -> Result { @@ -187,7 +187,7 @@ impl<'a> serde::Serializer for &'a mut Serializer { _variant: &'static str, _len: usize, ) -> Result { - Ok(self) + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) } fn is_human_readable(&self) -> bool { @@ -342,63 +342,6 @@ impl ser::SerializeStruct for SerializeStruct<'_> { } } -// === UNSUPPORTED TYPES === - -impl ser::SerializeTupleVariant for &mut Serializer { - type Ok = Field; - type Error = crate::error::Error; - - fn serialize_field(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) - } - - fn end(self) -> Result { - Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) - } -} - -impl ser::SerializeMap for &mut Serializer { - type Ok = Field; - type Error = crate::error::Error; - - fn serialize_key(&mut self, _key: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - Err(Self::Error::UnsupportedType(UnsupportedType::Map)) - } - - fn serialize_value(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - Err(Self::Error::UnsupportedType(UnsupportedType::Map)) - } - - fn end(self) -> Result { - Err(Self::Error::UnsupportedType(UnsupportedType::Map)) - } -} - -impl ser::SerializeStructVariant for &mut Serializer { - type Ok = Field; - type Error = crate::error::Error; - - fn serialize_field(&mut self, _key: &'static str, _value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) - } - - fn end(self) -> Result { - Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) - } -} - pub fn new_bounded_string(s: S) -> BoundedString where S: Into, diff --git a/blueprint-serde/src/tests.rs b/blueprint-serde/src/tests.rs index b14d6d01..200c01f1 100644 --- a/blueprint-serde/src/tests.rs +++ b/blueprint-serde/src/tests.rs @@ -456,6 +456,83 @@ mod sequences { use super::*; use alloc::vec::Vec; + fn expected_empty_bytes_field() -> Field { + Field::Bytes(BoundedVec(Vec::new())) + } + + #[test] + fn test_ser_bytes_empty() { + let bytes: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(Vec::new()); + + assert_ser_tokens(&bytes, &[Token::Bytes(&[])]); + + let field = to_field(&bytes).unwrap(); + assert_eq!(field, expected_empty_bytes_field()); + } + + #[test] + fn test_de_bytes_empty() { + let bytes: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(Vec::new()); + + assert_de_tokens(&bytes, &[Token::Bytes(&[])]); + + let bytes_de: serde_bytes::ByteBuf = from_field(expected_empty_bytes_field()).unwrap(); + assert_eq!(bytes, bytes_de); + } + + #[test] + fn test_de_bytes_seq_empty() { + let bytes: Vec = Vec::new(); + + assert_de_tokens(&bytes, &[Token::Seq { len: Some(0) }, Token::SeqEnd]); + + let bytes_de: Vec = from_field(expected_empty_bytes_field()).unwrap(); + assert_eq!(bytes, bytes_de); + } + + fn expected_bytes_field() -> Field { + Field::Bytes(BoundedVec(vec![1, 2, 3])) + } + + #[test] + fn test_ser_bytes() { + let bytes: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(vec![1, 2, 3]); + + assert_ser_tokens(&bytes, &[Token::Bytes(&[1, 2, 3])]); + + let field = to_field(&bytes).unwrap(); + assert_eq!(field, expected_bytes_field()); + } + + #[test] + fn test_de_bytes() { + let bytes: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(vec![1, 2, 3]); + + assert_de_tokens(&bytes, &[Token::Bytes(&[1, 2, 3])]); + + let bytes_de: serde_bytes::ByteBuf = from_field(expected_bytes_field()).unwrap(); + assert_eq!(bytes, bytes_de); + } + + #[test] + fn test_de_bytes_seq() { + let bytes: Vec = vec![1, 2, 3]; + + assert_de_tokens( + &bytes, + &[ + Token::Seq { len: Some(3) }, + Token::U8(1), + Token::U8(2), + Token::U8(3), + Token::SeqEnd, + ], + ); + + let bytes_de: Vec = from_field(expected_bytes_field()).unwrap(); + assert_eq!(bytes, bytes_de); + } + fn expected_vec_field() -> Field { Field::List(BoundedVec(vec![ Field::Uint32(1), @@ -641,3 +718,47 @@ mod sequences { assert_eq!(tuple, tuple_de); } } + +mod accountid32 { + use super::*; + use core::str::FromStr; + + fn expected_accountid32_field() -> Field { + Field::AccountId( + AccountId32::from_str("12bzRJfh7arnnfPPUZHeJUaE62QLEwhK48QnH9LXeK2m1iZU").unwrap(), + ) + } + + #[test] + #[should_panic = "assertion `left == right` failed"] // TODO: No way to differentiate, AccountId32 is serialized as a string. + fn test_ser_accountid32() { + let account_id: AccountId32 = + AccountId32::from_str("12bzRJfh7arnnfPPUZHeJUaE62QLEwhK48QnH9LXeK2m1iZU").unwrap(); + + assert_ser_tokens( + &account_id, + &[Token::Str( + "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV", + )], + ); + + let field = to_field(account_id).unwrap(); + assert_eq!(field, expected_accountid32_field()); + } + + #[test] + fn test_de_accountid32() { + let account_id: AccountId32 = + AccountId32::from_str("12bzRJfh7arnnfPPUZHeJUaE62QLEwhK48QnH9LXeK2m1iZU").unwrap(); + + assert_de_tokens( + &account_id, + &[Token::Str( + "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV", + )], + ); + + let account_id_de: AccountId32 = from_field(expected_accountid32_field()).unwrap(); + assert_eq!(account_id, account_id_de); + } +} diff --git a/macros/blueprint-proc-macro/src/shared.rs b/macros/blueprint-proc-macro/src/shared.rs index fd61df12..f8b8f840 100644 --- a/macros/blueprint-proc-macro/src/shared.rs +++ b/macros/blueprint-proc-macro/src/shared.rs @@ -88,6 +88,10 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { .last() .ok_or_else(|| syn::Error::new_spanned(path, "path must have at least one segment"))?; let ident = &seg.ident; + if ident == "ByteBuf" { + return Ok(FieldType::Bytes); + } + let args = &seg.arguments; match args { syn::PathArguments::None => { @@ -104,10 +108,7 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { let inner_arg = &inner.args[0]; if let syn::GenericArgument::Type(inner_ty) = inner_arg { let inner_type = type_to_field_type(inner_ty)?; - match inner_type.ty { - FieldType::Uint8 => Ok(FieldType::Bytes), - others => Ok(FieldType::List(Box::new(others))), - } + Ok(FieldType::List(Box::new(inner_type.ty))) } else { Err(syn::Error::new_spanned( inner_arg, diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 7feadd15..e406de43 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -71,6 +71,7 @@ pub mod utils; // Re-exports pub use alloy_rpc_types; pub use async_trait; +pub use blueprint_serde::ByteBuf; pub use clap; pub use error::Error; pub use futures;