Repository: CosmWasm/sylvia (rust)

Issue:
Generate the `const fn messages() -> [&'static str; N]` function returning generated messages variants
The function should be generated for both query and exec messages. The idea is to be able to use them to verify if message variants collide, and do it in compile time. For example, consider the following trait:

```rust
#[sylvia::interface]
pub trait Group {
  type Error: From<StdError>;

  #[msg(exec)]
  fn update_admin(&self, ctx: ExecCtx, admin: Option<String>) -> Result<Response, Self::Error>;
  
  #[msg(exec)]
  fn update_members(&self, ctx: ExecCtx, remove: Vec<String>, add: Vec<Member>) -> Result<Response, Self::Error>;
  
  #[msg(query)]
  fn member(&self, ctx: QueryCtx, addr: String) -> Result<MemberResp, Self::Error>;
}
```

The following should be generated:

```rust
impl ExecMsg {
  const fn messages() -> [&'static str; 2] {
    ["update_admin", "update_members"]
  }
}

impl QueryMsg {
  const fn messages() -> [&'static str; 1] {
    ["member"]
  }
}
```

Note, that it is probably beneficial to keep all items sorted internally (so it would be linear to look for name collisions).

Pull request description:
Print messages
Print messages generated by Sylvia.
close #7 

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e25b3a9..6aaee3b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -256,6 +256,16 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "cw4"
+version = "0.1.2"
+dependencies = [
+ "cosmwasm-std",
+ "schemars",
+ "serde",
+ "sylvia",
+]
+
 [[package]]
 name = "der"
 version = "0.5.1"
diff --git a/Cargo.toml b/Cargo.toml
index d09e89f..220fb5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,2 @@
 [workspace]
-members = ["sylvia", "sylvia-derive", "contracts/cw1", "contracts/cw1-whitelist"]
+members = ["sylvia", "sylvia-derive", "contracts/cw1", "contracts/cw1-whitelist", "contracts/cw4"]
diff --git a/contracts/cw1/Cargo.toml b/contracts/cw1/Cargo.toml
index be43a29..6eee594 100644
--- a/contracts/cw1/Cargo.toml
+++ b/contracts/cw1/Cargo.toml
@@ -12,4 +12,4 @@ homepage = "https://cosmwasm.com"
 cosmwasm-std = { version = "1.0.0", features = ["staking"] }
 schemars = "0.8.1"
 serde = { version = "1", default-features = false, features = ["derive"] }
-sylvia = { version = "0.1.2", path = "../../sylvia" }
\ No newline at end of file
+sylvia = { version = "0.1.2", path = "../../sylvia" }
diff --git a/contracts/cw1/src/lib.rs b/contracts/cw1/src/lib.rs
index 8d93e92..3d3c9f9 100644
--- a/contracts/cw1/src/lib.rs
+++ b/contracts/cw1/src/lib.rs
@@ -82,4 +82,14 @@ mod tests {
             }
         );
     }
+
+    #[test]
+    fn query_msgs() {
+        assert_eq!(QueryMsg::messages(), ["find_member"]);
+    }
+
+    #[test]
+    fn exec_msgs() {
+        assert_eq!(ExecMsg::messages(), ["add_member"]);
+    }
 }
diff --git a/contracts/cw4/Cargo.toml b/contracts/cw4/Cargo.toml
new file mode 100644
index 0000000..88f2855
--- /dev/null
+++ b/contracts/cw4/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "cw4"
+version = "0.1.2"
+authors = ["Jan Woźniak <jan@confio.gmbh>"]
+edition = "2021"
+description = "Coverage of cw4 sylvia message generation."
+license = "Apache-2.0"
+repository = "https://github.com/CosmWasm/sylvia"
+homepage = "https://cosmwasm.com"
+
+[dependencies]
+cosmwasm-std = { version = "1.0.0", features = ["staking"] }
+schemars = "0.8.1"
+serde = { version = "1", default-features = false, features = ["derive"] }
+sylvia = { version = "0.1.2", path = "../../sylvia" }
diff --git a/contracts/cw4/src/lib.rs b/contracts/cw4/src/lib.rs
new file mode 100644
index 0000000..59ad028
--- /dev/null
+++ b/contracts/cw4/src/lib.rs
@@ -0,0 +1,122 @@
+#![allow(dead_code)]
+use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Response, StdError};
+
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use sylvia::interface;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
+pub struct FindMemberResponse {
+    pub is_present: bool,
+}
+
+#[interface]
+pub trait Cw4 {
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn update_admin(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        admin: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn update_members(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        members: Vec<String>,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn add_hook(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        hook: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn remove_hook(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        hook: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn member(&self, ctx: (Deps, Env), member: String) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn list_members(&self, ctx: (Deps, Env)) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn total_weight(&self, ctx: (Deps, Env)) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn admin(&self, ctx: (Deps, Env)) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn hooks(&self, ctx: (Deps, Env)) -> Result<Response, Self::Error>;
+}
+
+#[cfg(test)]
+mod tests {
+    use cosmwasm_std::{from_binary, from_slice, to_binary};
+
+    use super::*;
+
+    #[test]
+    fn execute() {
+        let original_msg = ExecMsg::UpdateAdmin {
+            admin: "admin_name".to_owned(),
+        };
+
+        let serialized_msg = to_binary(&original_msg).unwrap();
+        let serialized_msg: ExecMsg = from_binary(&serialized_msg).unwrap();
+
+        assert_eq!(serialized_msg, original_msg);
+    }
+
+    #[test]
+    fn query() {
+        let original_msg = QueryMsg::Admin {};
+
+        let serialized_msg = to_binary(&original_msg).unwrap();
+        let serialized_msg: QueryMsg = from_binary(&serialized_msg).unwrap();
+
+        assert_eq!(serialized_msg, original_msg);
+    }
+
+    #[test]
+    fn execute_from_slice() {
+        let deserialized: ExecMsg =
+            from_slice(br#"{"update_admin": {"admin": "admin_name"}}"#).unwrap();
+        assert_eq!(
+            deserialized,
+            ExecMsg::UpdateAdmin {
+                admin: "admin_name".to_owned()
+            }
+        );
+    }
+
+    #[test]
+    fn query_from_slice() {
+        let deserialized: QueryMsg = from_slice(br#"{"admin": {}}"#).unwrap();
+        assert_eq!(deserialized, QueryMsg::Admin {});
+    }
+
+    #[test]
+    fn exec_msgs() {
+        assert_eq!(
+            ExecMsg::messages(),
+            ["update_admin", "update_members", "add_hook", "remove_hook"]
+        );
+    }
+
+    #[test]
+    fn query_msgs() {
+        assert_eq!(
+            QueryMsg::messages(),
+            ["member", "list_members", "total_weight", "admin", "hooks"]
+        );
+    }
+}
diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs
index fe9486c..86a327e 100644
--- a/sylvia-derive/src/message.rs
+++ b/sylvia-derive/src/message.rs
@@ -291,6 +291,11 @@ impl<'a> EnumMessage<'a> {
         let match_arms = variants
             .iter()
             .map(|variant| variant.emit_dispatch_leg(*msg_ty));
+        let msgs: Vec<String> = variants
+            .iter()
+            .map(|var| var.name.to_string().to_case(Case::Snake))
+            .collect();
+        let msgs_cnt = msgs.len();
         let variants = variants.iter().map(MsgVariant::emit);
         let where_clause = if !wheres.is_empty() {
             quote! {
@@ -332,6 +337,9 @@ impl<'a> EnumMessage<'a> {
                         #(#match_arms,)*
                     }
                 }
+                pub const fn messages() -> [&'static str; #msgs_cnt] {
+                    [#(#msgs,)*]
+                }
             }
         }
     }
@@ -591,7 +599,6 @@ impl<'a> GlueMessage<'a> {
                         #(#dispatch_arms,)*
                     }
                 }
-
             }
 
             impl<'de> serde::Deserialize<'de> for #name {


Test patch:
diff --git a/contracts/cw1-whitelist/src/multitest.rs b/contracts/cw1-whitelist/src/multitest.rs
index 71e4be9..6caa149 100644
--- a/contracts/cw1-whitelist/src/multitest.rs
+++ b/contracts/cw1-whitelist/src/multitest.rs
@@ -1,6 +1,6 @@
 #[cfg(test)]
 mod test {
-    use crate::contract::{InstantiateMsg, Cw1WhitelistContract};
+    use crate::contract::{Cw1WhitelistContract, InstantiateMsg};
     use anyhow::{bail, Result as AnyResult};
     use cosmwasm_std::{
         from_slice, Addr, Binary, DepsMut, Empty, Env, MessageInfo, Reply, Response,