Skip to content

Commit

Permalink
feat: Add auto deserialization of reply data (#445)
Browse files Browse the repository at this point in the history
  • Loading branch information
jawoznia committed Oct 21, 2024
1 parent b043037 commit c580365
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 8 deletions.
75 changes: 74 additions & 1 deletion sylvia-derive/src/contract/communication/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type};

use crate::crate_module;
use crate::parser::attributes::msg::ReplyOn;
use crate::parser::{MsgType, SylviaAttribute};
use crate::parser::{MsgType, ParsedSylviaAttributes, SylviaAttribute};
use crate::types::msg_field::MsgField;
use crate::types::msg_variant::{MsgVariant, MsgVariants};
use crate::utils::emit_turbofish;
Expand Down Expand Up @@ -190,12 +190,15 @@ struct ReplyData<'a> {
pub handler_id: &'a Ident,
/// Methods handling the reply id for the associated reply on.
pub handlers: Vec<(&'a Ident, ReplyOn)>,
/// Data parameter associated with the handlers.
pub data: Option<&'a MsgField<'a>>,
/// Payload parameters associated with the handlers.
pub payload: Vec<&'a MsgField<'a>>,
}

impl<'a> ReplyData<'a> {
pub fn new(reply_id: Ident, variant: &'a MsgVariant<'a>, handler_id: &'a Ident) -> Self {
let data = variant.fields().first();
// Skip the first field reserved for the `data`.
let payload = variant.fields().iter().skip(1).collect::<Vec<_>>();
let method_name = variant.function_name();
Expand All @@ -205,6 +208,7 @@ impl<'a> ReplyData<'a> {
reply_id,
handler_id,
handlers: vec![(method_name, reply_on)],
data,
payload,
}
}
Expand Down Expand Up @@ -372,12 +376,14 @@ impl<'a> ReplyData<'a> {
Some((method_name, reply_on)) if reply_on == &ReplyOn::Success => {
let payload_values = self.payload.iter().map(|field| field.name());
let payload_deserialization = self.payload.emit_payload_deserialization();
let data_deserialization = self.data.map(DataField::emit_data_deserialization);

quote! {
#sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
#[allow(deprecated)]
let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp;
#payload_deserialization
#data_deserialization

#contract_turbofish ::new(). #method_name ((deps, env, gas_used, events, msg_responses).into(), data, #(#payload_values),* )
}
Expand Down Expand Up @@ -475,6 +481,73 @@ impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
}
}

pub trait DataField {
fn emit_data_deserialization(&self) -> TokenStream;
}

impl DataField for MsgField<'_> {
fn emit_data_deserialization(&self) -> TokenStream {
let sylvia = crate_module();
let data = ParsedSylviaAttributes::new(self.attrs().iter()).data;
let is_data_attr = self
.attrs()
.iter()
.any(|attr| SylviaAttribute::new(attr) == Some(SylviaAttribute::Data));
let missing_data_err = "Missing reply data field.";
let invalid_reply_data_err = quote! {
format! {"Invalid reply data: {}\nSerde error while deserializing {}", data, err}
};
let data_deserialization = quote! {
let deserialized_data =
#sylvia ::cw_utils::parse_execute_response_data(data.as_slice())
.map_err(|err| #sylvia ::cw_std::StdError::generic_err(
format!("Failed deserializing protobuf data: {}", err)
))?;
let deserialized_data = match deserialized_data.data {
Some(data) => #sylvia ::cw_std::from_json(&data).map_err(|err| #sylvia ::cw_std::StdError::generic_err( #invalid_reply_data_err ))?,
None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
};
};

match data {
Some(data) if data.raw && data.opt => quote! {},
Some(data) if data.raw => quote! {
let data = match data {
Some(data) => data,
None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
};
},
Some(data) if data.opt => quote! {
let data = match data {
Some(data) => {
#data_deserialization

Some(deserialized_data)
},
None => None,
};
},
None if is_data_attr => quote! {
let data = match data {
Some(data) => {
#data_deserialization

deserialized_data
},
None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
};
},
_ => {
emit_error!(self.name().span(), "Invalid data usage.";
note = "Reply data should be marked with #[sv::data] attribute.";
note = "Remove this parameter or mark it with #[sv::data] attribute."

Check warning on line 543 in sylvia-derive/src/contract/communication/reply.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/contract/communication/reply.rs#L541-L543

Added lines #L541 - L543 were not covered by tests
);
quote! {}

Check warning on line 545 in sylvia-derive/src/contract/communication/reply.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/contract/communication/reply.rs#L545

Added line #L545 was not covered by tests
}
}
}
}

pub trait PayloadFields {
fn emit_payload_deserialization(&self) -> TokenStream;
fn emit_payload_serialization(&self) -> TokenStream;
Expand Down
48 changes: 48 additions & 0 deletions sylvia-derive/src/parser/attributes/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use proc_macro_error::emit_error;
use syn::parse::{Parse, ParseStream, Parser};
use syn::{Error, Ident, MetaList, Result, Token};

/// Type wrapping data parsed from `sv::data` attribute.
#[derive(Default, Debug)]
pub struct DataFieldParams {
pub raw: bool,
pub opt: bool,
}

impl DataFieldParams {
pub fn new(attr: &MetaList) -> Result<Self> {
DataFieldParams::parse
.parse2(attr.tokens.clone())
.map_err(|err| {
emit_error!(err.span(), err);
err

Check warning on line 18 in sylvia-derive/src/parser/attributes/data.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/data.rs#L17-L18

Added lines #L17 - L18 were not covered by tests
})
}
}

impl Parse for DataFieldParams {
fn parse(input: ParseStream) -> Result<Self> {
let mut data = Self::default();

while !input.is_empty() {
let option: Ident = input.parse()?;
match option.to_string().as_str() {
"raw" => data.raw = true,
"opt" => data.opt = true,
_ => {
return Err(Error::new(
option.span(),
"Invalid data parameter.\n
= note: Expected one of [`raw`, `opt`] comma separated.\n",

Check warning on line 36 in sylvia-derive/src/parser/attributes/data.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/data.rs#L33-L36

Added lines #L33 - L36 were not covered by tests
))
}
}
if !input.peek(Token![,]) {
break;
}
let _: Token![,] = input.parse()?;
}

Ok(data)
}
}
10 changes: 10 additions & 0 deletions sylvia-derive/src/parser/attributes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Module defining parsing of Sylvia attributes.
//! Every Sylvia attribute should be prefixed with `sv::`

use data::DataFieldParams;
use features::SylviaFeatures;
use proc_macro_error::emit_error;
use syn::spanned::Spanned;
use syn::{Attribute, MetaList, PathSegment};

pub mod attr;
pub mod custom;
pub mod data;
pub mod error;
pub mod features;
pub mod messages;
Expand All @@ -33,6 +35,7 @@ pub enum SylviaAttribute {
VariantAttrs,
MsgAttrs,
Payload,
Data,
Features,
}

Expand All @@ -56,6 +59,7 @@ impl SylviaAttribute {
"attr" => Some(Self::VariantAttrs),
"msg_attr" => Some(Self::MsgAttrs),
"payload" => Some(Self::Payload),
"data" => Some(Self::Data),
"features" => Some(Self::Features),
_ => None,
}
Expand All @@ -74,6 +78,7 @@ pub struct ParsedSylviaAttributes {
pub variant_attrs_forward: Vec<VariantAttrForwarding>,
pub msg_attrs_forward: Vec<MsgAttrForwarding>,
pub sv_features: SylviaFeatures,
pub data: Option<DataFieldParams>,
}

impl ParsedSylviaAttributes {
Expand Down Expand Up @@ -172,6 +177,11 @@ impl ParsedSylviaAttributes {
note = attr.span() => "The `sv::payload` should be used as a prefix for `Binary` payload.";
);
}
SylviaAttribute::Data => {
if let Ok(data) = DataFieldParams::new(attr) {
self.data = Some(data);
}
}
SylviaAttribute::Features => {
if let Ok(features) = SylviaFeatures::new(attr) {
self.sv_features = features;
Expand Down
4 changes: 4 additions & 0 deletions sylvia-derive/src/types/msg_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ impl<'a> MsgField<'a> {
self.ty
}

pub fn attrs(&self) -> &'a Vec<Attribute> {
self.attrs
}

pub fn contains_attribute(&self, sv_attr: SylviaAttribute) -> bool {
self.attrs
.iter()
Expand Down
Loading

0 comments on commit c580365

Please sign in to comment.