diff --git a/sylvia-derive/src/parser/attributes/msg.rs b/sylvia-derive/src/parser/attributes/msg.rs index 25833ecf..c415f53a 100644 --- a/sylvia-derive/src/parser/attributes/msg.rs +++ b/sylvia-derive/src/parser/attributes/msg.rs @@ -1,6 +1,6 @@ use proc_macro_error::emit_error; use syn::parse::{Error, Parse, ParseStream, Parser}; -use syn::{Ident, MetaList, Result, Token}; +use syn::{bracketed, Ident, MetaList, Result, Token}; /// Type of message to be generated #[derive(PartialEq, Eq, Debug, Clone, Copy)] @@ -17,24 +17,47 @@ pub enum MsgType { #[derive(Default)] struct ArgumentParser { pub resp_type: Option, + pub reply_handlers: Vec, + pub reply_on: Option, } impl Parse for ArgumentParser { fn parse(input: ParseStream) -> Result { let mut result = Self::default(); + while input.peek2(Ident) { let _: Token![,] = input.parse()?; let arg_type: Ident = input.parse()?; - let _: Token![=] = input.parse()?; match arg_type.to_string().as_str() { "resp" => { + let _: Token![=] = input.parse()?; let resp_type: Ident = input.parse()?; result.resp_type = Some(resp_type); } + "handlers" => { + let _: Token![=] = input.parse()?; + let handlers_content; + bracketed!(handlers_content in input); + + while !handlers_content.is_empty() { + let handler = handlers_content.parse::()?; + result.reply_handlers.push(handler); + if !handlers_content.peek(Token![,]) { + break; + } + let _: Token![,] = handlers_content.parse()?; + } + } + "reply_on" => { + let _: Token![=] = input.parse()?; + let reply_on: Ident = input.parse()?; + let reply_on = ReplyOn::new(reply_on)?; + result.reply_on = Some(reply_on); + } _ => { return Err(Error::new( - input.span(), - "Invalid argument type, expected `resp` or no argument.", + arg_type.span(), + "Invalid argument type, expected `resp`, `handlers`, `reply_on` or no argument.", )) } } @@ -43,14 +66,42 @@ impl Parse for ArgumentParser { } } +/// Representation of `reply_on` parameter in `#[sv::msg(reply(...))]` attribute. +#[derive(Default, Clone)] +pub enum ReplyOn { + Success, + Failure, + #[default] + Always, +} + +impl ReplyOn { + pub fn new(reply_on: Ident) -> Result { + match reply_on.to_string().as_str() { + "success" => Ok(Self::Success), + "failure" => Ok(Self::Failure), + "always" => Ok(Self::Always), + _ => Err(Error::new( + reply_on.span(), + "Invalid argument type, expected one of `success`, `failure` or `always`.", + )), + } + } +} + /// Parsed representation of `#[sv::msg(...)]` attribute. #[derive(Clone)] pub enum MsgAttr { Exec, - Query { resp_type: Option }, + Query { + resp_type: Option, + }, Instantiate, Migrate, - Reply, + Reply { + _handlers: Vec, + _reply_on: ReplyOn, + }, Sudo, } @@ -85,14 +136,18 @@ impl MsgAttr { impl Parse for MsgAttr { fn parse(input: ParseStream) -> Result { let ty: Ident = input.parse()?; - let ArgumentParser { resp_type } = ArgumentParser::parse(input)?; + let ArgumentParser { + resp_type, + reply_handlers, + reply_on, + } = ArgumentParser::parse(input)?; let result = match ty.to_string().as_str() { "exec" => Self::Exec, "query" => Self::Query { resp_type }, "instantiate" => Self::Instantiate, "migrate" => Self::Migrate, - "reply" => Self::Reply, + "reply" => Self::Reply {_handlers: reply_handlers, _reply_on: reply_on.unwrap_or_default()}, "sudo" => Self::Sudo, _ => return Err(Error::new( input.span(), diff --git a/sylvia/tests/reply.rs b/sylvia/tests/reply.rs new file mode 100644 index 00000000..64593f29 --- /dev/null +++ b/sylvia/tests/reply.rs @@ -0,0 +1,46 @@ +use sylvia::contract; +use sylvia::cw_std::{Reply, Response, StdResult}; +use sylvia::types::{InstantiateCtx, ReplyCtx}; + +pub struct Contract; + +#[contract] +impl Contract { + pub fn new() -> Self { + Self + } + + #[sv::msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult { + Ok(Response::new()) + } + + #[sv::msg(reply)] + fn clean(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[sv::msg(reply, handlers=[handler_one, handler_two])] + fn custom_handlers(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[sv::msg(reply, reply_on = success)] + fn reply_on(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[sv::msg(reply, reply_on = always)] + fn reply_on_always(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[sv::msg(reply, handlers=[handler_one, handler_two], reply_on = failure)] + fn both_parameters(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } +} diff --git a/sylvia/tests/ui/attributes/msg/invalid_params.rs b/sylvia/tests/ui/attributes/msg/invalid_params.rs new file mode 100644 index 00000000..74fb6d4a --- /dev/null +++ b/sylvia/tests/ui/attributes/msg/invalid_params.rs @@ -0,0 +1,25 @@ +#![allow(unused_imports)] +use sylvia::contract; +use sylvia::cw_std::{Reply, Response, StdResult}; +use sylvia::types::{InstantiateCtx, ReplyCtx}; + +pub struct Contract; + +#[contract] +impl Contract { + pub fn new() -> Self { + Self + } + + #[sv::msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult { + Ok(Response::new()) + } + + #[sv::msg(reply, unknown_parameter)] + fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } +} + +fn main() {} diff --git a/sylvia/tests/ui/attributes/msg/invalid_params.stderr b/sylvia/tests/ui/attributes/msg/invalid_params.stderr new file mode 100644 index 00000000..a7b6c5b7 --- /dev/null +++ b/sylvia/tests/ui/attributes/msg/invalid_params.stderr @@ -0,0 +1,5 @@ +error: Invalid argument type, expected `resp`, `handlers`, `reply_on` or no argument. + --> tests/ui/attributes/msg/invalid_params.rs:19:22 + | +19 | #[sv::msg(reply, unknown_parameter)] + | ^^^^^^^^^^^^^^^^^