Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auto deserialization of reply data (#445) #448

Open
wants to merge 2 commits into
base: feat/replies
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 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 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 @@
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 @@
reply_id,
handler_id,
handlers: vec![(method_name, reply_on)],
data,
payload,
}
}
Expand Down Expand Up @@ -372,12 +376,14 @@
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,101 @@
}
}

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 execute_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 ))),
};
};

let instantiate_data_deserialization = quote! {
let deserialized_data =
#sylvia ::cw_utils::parse_instantiate_response_data(data.as_slice())
.map_err(|err| #sylvia ::cw_std::StdError::generic_err(
format!("Failed deserializing protobuf 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.instantiate && data.opt => quote! {
let data = match data {
Some(data) => {
#instantiate_data_deserialization

Some(deserialized_data)
},
None => None,
};
},
Some(data) if data.instantiate => quote! {
let data = match data {
Some(data) => {
#instantiate_data_deserialization

deserialized_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) => {
#execute_data_deserialization

Some(deserialized_data)
},
None => None,
};
},
None if is_data_attr => quote! {
let data = match data {
Some(data) => {
#execute_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 571 in sylvia-derive/src/contract/communication/reply.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/contract/communication/reply.rs#L569-L571

Added lines #L569 - L571 were not covered by tests
);
quote! {}

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

View check run for this annotation

Codecov / codecov/patch

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

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

pub trait PayloadFields {
fn emit_payload_deserialization(&self) -> TokenStream;
fn emit_payload_serialization(&self) -> TokenStream;
Expand Down
61 changes: 61 additions & 0 deletions sylvia-derive/src/parser/attributes/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use proc_macro_error::emit_error;
use syn::parse::{Parse, ParseStream, Parser};
use syn::spanned::Spanned;
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,
pub instantiate: bool,
}

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

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

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/data.rs#L19-L20

Added lines #L19 - L20 were not covered by tests
})?;

if data.instantiate && data.raw {
emit_error!(
attr.tokens.span(),
"The `instantiate` cannot be used in pair with `raw` parameter.";
note = "Use any combination of [`raw`, `opt`] or [`instantiate`, `opt`] pairs."

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

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/data.rs#L24-L27

Added lines #L24 - L27 were not covered by tests
);
}

Ok(data)
}
}

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,
"instantiate" => data.instantiate = true,
_ => {
return Err(Error::new(
option.span(),
"Invalid data parameter.\n
= note: Expected one of [`raw`, `opt`, `instantiate`] comma separated.\n",

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

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/data.rs#L46-L49

Added lines #L46 - L49 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
Loading