Repository: CosmWasm/sylvia
Pull request #148 (2023-05-31 12:56:12)

Custom attr
#140 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index ebc5bd9..bb4fa07 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -748,9 +748,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.50"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
+checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
 dependencies = [
  "unicode-ident",
 ]
@@ -780,9 +780,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.23"
+version = "1.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
 dependencies = [
  "proc-macro2",
 ]
diff --git a/README.md b/README.md
index 0b24477..642db77 100644
--- a/README.md
+++ b/README.md
@@ -454,6 +454,23 @@ to be part of a contract logic.
 For the interface implementation there should be at most one `messages` attribute used.
 In case of the contract implementation there can be multiple `messages` attributes used.
 
+```rust
+struct MyMsg;
+impl CustomMsg for MyMsg {}
+
+struct MyQuery;
+impl CustomQuery for MyMsg {}
+
+#[contract]
+#[sv::custom(msg=MyMsg, query=MyQuery)]
+impl MyContract {
+...
+}
+```
+
+`sv::custom` allows to define CustomMsg and CustomQuery for the contract. By default generated code
+will return `Response<Empty>` and will use `Deps<Empty>` and `DepsMut<Empty>`.
+
 ## Single module per macro
 
 Generated items and namespaces may overlap and it is suggested to split all macro calls
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index 1544789..b9e0965 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -9,7 +9,7 @@ use crate::crate_module;
 use crate::interfaces::Interfaces;
 use crate::message::{ContractEnumMessage, EnumMessage, GlueMessage, MsgVariants, StructMessage};
 use crate::multitest::{MultitestHelpers, TraitMultitestHelpers};
-use crate::parser::{ContractArgs, ContractErrorAttr, InterfaceArgs, MsgType};
+use crate::parser::{ContractArgs, ContractErrorAttr, Custom, InterfaceArgs, MsgType};
 use crate::remote::Remote;
 use crate::variant_descs::AsVariantDescs;
 
@@ -148,6 +148,7 @@ impl<'a> ImplInput<'a> {
 
         let interfaces = Interfaces::new(self.item);
         let variants = MsgVariants::new(self.item.as_variants(), &self.generics);
+        let _ = Custom::new(self.item);
 
         match is_trait {
             true => self.process_interface(&interfaces, variants, multitest_helpers),
diff --git a/sylvia-derive/src/parser.rs b/sylvia-derive/src/parser.rs
index 620f1a4..bd89430 100644
--- a/sylvia-derive/src/parser.rs
+++ b/sylvia-derive/src/parser.rs
@@ -3,7 +3,10 @@ use proc_macro_error::emit_error;
 use quote::quote;
 use syn::parse::{Error, Nothing, Parse, ParseBuffer, ParseStream, Parser};
 use syn::spanned::Spanned;
-use syn::{parenthesized, Ident, ImplItem, ImplItemMethod, ItemImpl, Path, Result, Token, Type};
+use syn::{
+    parenthesized, parse_quote, Attribute, Ident, ImplItem, ImplItemMethod, ItemImpl, Path, Result,
+    Token, Type,
+};
 
 use crate::crate_module;
 
@@ -342,3 +345,94 @@ pub fn parse_struct_message(source: &ItemImpl, ty: MsgType) -> Option<(&ImplItem
     }
     Some((method, msg_attr))
 }
+
+pub struct Custom {
+    _msg: Path,
+    _query: Path,
+}
+
+impl Default for Custom {
+    fn default() -> Self {
+        let sylvia = crate_module();
+
+        Self {
+            _msg: parse_quote!(#sylvia ::cw_std::Empty),
+            _query: parse_quote!(#sylvia ::cw_std::Empty),
+        }
+    }
+}
+
+impl Custom {
+    pub fn new(source: &ItemImpl) -> Self {
+        let custom: Vec<_> = source
+            .attrs
+            .iter()
+            .filter(|attr| match sylvia_attribute(attr) {
+                Some(attr) => attr == "custom",
+                None => false,
+            })
+            .filter_map(|attr| {
+                let custom = match Custom::parse.parse2(attr.tokens.clone()) {
+                    Ok(custom) => custom,
+                    Err(err) => {
+                        emit_error!(attr.span(), err);
+                        return None;
+                    }
+                };
+
+                Some(custom)
+            })
+            .collect();
+
+        match custom.len() {
+            0 => Self::default(),
+            1 => custom.into_iter().next().unwrap(),
+            _ => {
+                emit_error!(
+                    source.span(),
+                    "More than one `custom` attribute defined. It should be only one"
+                );
+                Self::default()
+            }
+        }
+    }
+}
+
+#[cfg(not(tarpaulin_include))]
+// False negative. It is being called in closure
+impl Parse for Custom {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let content;
+        parenthesized!(content in input);
+        let mut custom = Self::default();
+
+        while !content.is_empty() {
+            let ty: Ident = content.parse()?;
+            let _: Token![=] = content.parse()?;
+            if ty == "msg" {
+                custom._msg = content.parse()?
+            } else if ty == "query" {
+                custom._query = content.parse()?
+            } else {
+                return Err(Error::new(
+                    ty.span(),
+                    "Invalid custom type. Expected msg or query",
+                ));
+            };
+            if !content.peek(Token![,]) {
+                break;
+            }
+            let _: Token![,] = content.parse()?;
+        }
+
+        Ok(custom)
+    }
+}
+
+pub fn sylvia_attribute(attr: &Attribute) -> Option<&Ident> {
+    if attr.path.segments.len() == 2 && attr.path.segments[0].ident == "sv" {
+        Some(&attr.path.segments[1].ident)
+    } else {
+        None
+    }
+}
diff --git a/sylvia-derive/src/strip_input.rs b/sylvia-derive/src/strip_input.rs
index bf4f594..6cfc626 100644
--- a/sylvia-derive/src/strip_input.rs
+++ b/sylvia-derive/src/strip_input.rs
@@ -2,6 +2,8 @@ use syn::fold::{self, Fold};
 use syn::punctuated::Punctuated;
 use syn::{FnArg, ImplItemMethod, ItemImpl, PatType, Receiver, Signature, Token, TraitItemMethod};
 
+use crate::parser::sylvia_attribute;
+
 /// Utility for stripping all attributes from input before it is emitted
 pub struct StripInput;
 
@@ -57,7 +59,11 @@ impl Fold for StripInput {
         let attrs = i
             .attrs
             .into_iter()
-            .filter(|attr| !(attr.path.is_ident("messages") || attr.path.is_ident("error")))
+            .filter(|attr| {
+                !(attr.path.is_ident("messages")
+                    || attr.path.is_ident("error")
+                    || sylvia_attribute(attr).is_some())
+            })
             .collect();
 
         fold::fold_item_impl(self, ItemImpl { attrs, ..i })


Test patch:
diff --git a/sylvia/tests/custom_msg.rs b/sylvia/tests/custom_msg.rs
new file mode 100644
index 0000000..2d5a3e9
--- /dev/null
+++ b/sylvia/tests/custom_msg.rs
@@ -0,0 +1,41 @@
+use cosmwasm_std::{CustomMsg, CustomQuery, Response, StdResult};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use sylvia::contract;
+use sylvia::types::InstantiateCtx;
+
+#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
+struct MyMsg;
+
+impl CustomMsg for MyMsg {}
+
+#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
+struct MyQuery;
+
+impl CustomQuery for MyMsg {}
+
+pub struct MyContract;
+
+#[contract]
+#[sv::custom(msg=MyMsg, query=MyQuery)]
+impl MyContract {
+    #[allow(clippy::new_without_default)]
+    pub fn new() -> Self {
+        Self {}
+    }
+
+    #[msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::default())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::MyContract;
+
+    #[test]
+    fn test_custom() {
+        let _ = MyContract::new();
+    }
+}