Skip to content
Merged
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
147 changes: 137 additions & 10 deletions sylvia-derive/src/contract/communication/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

use crate::crate_module;
use crate::parser::attributes::msg::ReplyOn;
use crate::parser::{MsgType, SylviaAttribute};
use crate::parser::{MsgType, ParsedSylviaAttributes};
use crate::types::msg_field::MsgField;
use crate::types::msg_variant::{MsgVariant, MsgVariants};
use crate::utils::emit_turbofish;

const NUMBER_OF_DATA_FIELDS: usize = 1;

pub struct Reply<'a> {
source: &'a ItemImpl,
generics: &'a [&'a GenericParam],
Expand Down Expand Up @@ -173,7 +175,7 @@
},
)
}
Some(existing_data) => existing_data.add_second_handler(handler),
Some(existing_data) => existing_data.merge(handler),
None => reply_data.push(ReplyData::new(reply_id, handler, handler_id)),
}
});
Expand All @@ -190,33 +192,44 @@
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 {
// Skip the first field reserved for the `data`.
let payload = variant.fields().iter().skip(1).collect::<Vec<_>>();
let data = variant.as_data_field();
variant.validate_fields_attributes();
let payload = variant.fields().iter();
let payload = if data.is_some() || variant.msg_attr().reply_on() != ReplyOn::Success {
payload.skip(NUMBER_OF_DATA_FIELDS).collect::<Vec<_>>()
} else {
payload.collect::<Vec<_>>()
};
let method_name = variant.function_name();
let reply_on = variant.msg_attr().reply_on();

Self {
reply_id,
handler_id,
handlers: vec![(method_name, reply_on)],
data,
payload,
}
}

/// Adds second handler to the reply data provdided their payload signature match.
pub fn add_second_handler(&mut self, new_handler: &'a MsgVariant<'a>) {
pub fn merge(&mut self, new_handler: &'a MsgVariant<'a>) {
let (current_method_name, _) = match self.handlers.first() {
Some(handler) => handler,
_ => return,
};

if self.payload.len() != new_handler.fields().len() - 1 {
let new_reply_data = ReplyData::new(self.reply_id.clone(), new_handler, self.handler_id);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L230 was not covered by tests

if self.payload.len() != new_reply_data.payload.len() {

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L232 was not covered by tests
emit_error!(current_method_name.span(), "Mismatched quantity of method parameters.";
note = self.handler_id.span() => format!("Both `{}` handlers should have the same number of parameters.", self.handler_id);
note = new_handler.function_name().span() => format!("Previous definition of {} handler.", self.handler_id)
Expand All @@ -225,7 +238,7 @@

self.payload
.iter()
.zip(new_handler.fields().iter().skip(1))
.zip(new_reply_data.payload.iter())
.for_each(|(current_field, new_field)|
{
if current_field.ty() != new_field.ty() {
Expand Down Expand Up @@ -372,14 +385,17 @@
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);
let data = self.data.map(|_| quote! { data, });

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),* )
#contract_turbofish ::new(). #method_name ((deps, env, gas_used, events, msg_responses).into(), #data #(#payload_values),* )
}
}
}
Expand Down Expand Up @@ -456,6 +472,8 @@

trait ReplyVariant<'a> {
fn as_variant_handlers_pair(&'a self) -> Vec<(&'a MsgVariant<'a>, &'a Ident)>;
fn as_data_field(&'a self) -> Option<&'a MsgField<'a>>;
fn validate_fields_attributes(&'a self);
}

impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
Expand All @@ -473,6 +491,112 @@

variant_handler_id_pair
}

/// Validates attributes and returns `Some(MsgField)` if a field marked with `sv::data` attribute
/// is present and the `reply_on` attribute is set to `ReplyOn::Success`.
fn as_data_field(&'a self) -> Option<&'a MsgField<'a>> {
let data_attrs = self.fields().first().map(|field| {
ParsedSylviaAttributes::new(field.attrs().iter())
.data
.is_some()
});
match data_attrs {
Some(attrs) if attrs && self.msg_attr().reply_on() == ReplyOn::Success => {
self.fields().first()
}
_ => None,
}
}

/// Validates if the fields attributes are correct.
fn validate_fields_attributes(&'a self) {
let field_attrs = self.fields().iter().flat_map(|field| field.attrs());
ParsedSylviaAttributes::new(field_attrs);
}
}

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

pub trait PayloadFields {
Expand Down Expand Up @@ -515,8 +639,11 @@
}

fn is_payload_marked(&self) -> bool {
self.iter()
.any(|field| field.contains_attribute(SylviaAttribute::Payload))
self.iter().any(|field| {
ParsedSylviaAttributes::new(field.attrs().iter())
.payload
.is_some()
})
}
}

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)
}
}
28 changes: 24 additions & 4 deletions sylvia-derive/src/parser/attributes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
//! Module defining parsing of Sylvia attributes.
//! Every Sylvia attribute should be prefixed with `sv::`

use data::DataFieldParams;
use features::SylviaFeatures;
use payload::PayloadFieldParam;
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;
pub mod msg;
pub mod override_entry_point;
pub mod payload;

pub use attr::{MsgAttrForwarding, VariantAttrForwarding};
pub use custom::Custom;
Expand All @@ -33,6 +37,7 @@
VariantAttrs,
MsgAttrs,
Payload,
Data,
Features,
}

Expand All @@ -56,6 +61,7 @@
"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 +80,8 @@
pub variant_attrs_forward: Vec<VariantAttrForwarding>,
pub msg_attrs_forward: Vec<MsgAttrForwarding>,
pub sv_features: SylviaFeatures,
pub data: Option<DataFieldParams>,
pub payload: Option<PayloadFieldParam>,
}

impl ParsedSylviaAttributes {
Expand All @@ -85,6 +93,14 @@

if let (Some(sylvia_attr), Ok(attr)) = (sylvia_attr, &attr_content) {
result.match_attribute(&sylvia_attr, attr);
} else if sylvia_attr == Some(SylviaAttribute::Data) {
// The `sv::data` attribute can be used without parameters.
result.data = Some(DataFieldParams::default());
} else if sylvia_attr == Some(SylviaAttribute::Payload) {
emit_error!(
attr.span(), "Missing parameters for `sv::payload`";
note = "Expected `#[sv::payload(raw)]`"

Check warning on line 102 in sylvia-derive/src/parser/attributes/mod.rs

View check run for this annotation

Codecov / codecov/patch

sylvia-derive/src/parser/attributes/mod.rs#L100-L102

Added lines #L100 - L102 were not covered by tests
);
}
}

Expand Down Expand Up @@ -167,10 +183,14 @@
}
}
SylviaAttribute::Payload => {
emit_error!(
attr, "The attribute `sv::payload` used in wrong context";
note = attr.span() => "The `sv::payload` should be used as a prefix for `Binary` payload.";
);
if let Ok(payload) = PayloadFieldParam::new(attr) {
self.payload = Some(payload);
}
}
SylviaAttribute::Data => {
if let Ok(data) = DataFieldParams::new(attr) {
self.data = Some(data);
}
}
SylviaAttribute::Features => {
if let Ok(features) = SylviaFeatures::new(attr) {
Expand Down
Loading
Loading