From 6c8bcb3a3c4164dd83a59340ff4c8734d5042e97 Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 26 Nov 2021 21:15:44 +0900 Subject: [PATCH 1/4] add IntoEvent derive macro move IntoEvent macro to cosmwasm-derive feat: add attribute "use_to_string" to IntoEvent derive macro --- Cargo.lock | 9 +++ packages/derive/Cargo.toml | 3 + packages/derive/src/into_event.rs | 110 ++++++++++++++++++++++++++++++ packages/derive/src/lib.rs | 44 ++++++++++++ packages/derive/tests/test.rs | 82 ++++++++++++++++++++++ packages/std/src/lib.rs | 2 +- 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 packages/derive/src/into_event.rs create mode 100644 packages/derive/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 3eedfd8bdb..8f1b363454 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -428,7 +434,10 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", "cosmwasm-std", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/packages/derive/Cargo.toml b/packages/derive/Cargo.toml index b402b15d6a..d55216de16 100644 --- a/packages/derive/Cargo.toml +++ b/packages/derive/Cargo.toml @@ -16,6 +16,9 @@ default = [] [dependencies] syn = { version = "1.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0.32" +convert_case = "0.4.0" [dev-dependencies] # Needed for testing docs diff --git a/packages/derive/src/into_event.rs b/packages/derive/src/into_event.rs new file mode 100644 index 0000000000..f45656ca0a --- /dev/null +++ b/packages/derive/src/into_event.rs @@ -0,0 +1,110 @@ +extern crate proc_macro; + +use convert_case::{Case, Casing}; +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{Attribute, DataStruct, DeriveInput, Error, Field, Ident, Result}; + +/// scan attrs and get `to_string_fn` attribute +fn scan_to_string_fn(field: &Field) -> Result> { + let filtered: Vec<&Attribute> = field + .attrs + .iter() + .filter(|a| a.path.is_ident("to_string_fn")) + .collect(); + if filtered.len() > 1 { + return Err(Error::new( + field.span(), + "[IntoEvent] Only one or zero `to_string_fn` can be applied to one field.", + )); + }; + if filtered.is_empty() { + Ok(None) + } else { + Ok(Some(filtered[0].tokens.clone())) + } +} + +/// scan attrs and return if it has any `to_string` +fn has_use_to_string(field: &Field) -> Result { + let mut filtered = field + .attrs + .iter() + .filter(|a| a.path.is_ident("use_to_string")); + if filtered.clone().any(|a| !a.tokens.is_empty()) { + return Err(Error::new( + field.span(), + "[IntoEvent] attribute `use_to_string` has some value. If you intend to specify the cast function to string, use `to_string_fn` instead.", + )); + } + Ok(filtered.next().is_some()) +} + +/// generate an ast for `impl Into` from a struct +fn make_init_from_struct(id: Ident, struct_data: DataStruct) -> Result { + // snake case of struct ident + let name = id.to_string().as_str().to_case(Case::Snake); + + // generate the body of `fn into` + // generating `Event::new()` part + let mut fn_body = quote!( + cosmwasm_std::Event::new(#name) + ); + + // chain `.add_attribute`s to `Event::new()` part + for field in struct_data.fields { + let field_id = match field.clone().ident { + None => { + return Err(Error::new( + field.span(), + "[IntoEvent] Unexpected unnamed field.", + )) + } + Some(field_id) => field_id, + }; + let value = match (scan_to_string_fn(&field)?, has_use_to_string(&field)?) { + (Some(_), true) => return Err(Error::new( + field.span(), + "[IntoEvent] Both `use_to_string` and `to_string_fn` are applied to an field. Only one can be applied.", + )), + (Some(to_string_fn), false) => quote!(#to_string_fn(self.#field_id)), + (None, true) => quote!(self.#field_id.to_string()), + (None, false) => quote!(self.#field_id), + }; + fn_body.extend(quote!( + .add_attribute(stringify!(#field_id), #value) + )) + } + + // generate the `impl Into` from generated `fn_body` + let gen = quote!( + impl Into for #id { + fn into(self) -> cosmwasm_std::Event { + #fn_body + } + } + ); + Ok(gen) +} + +/// derive `IntoEvent` from a derive input. The input needs to be a struct. +pub fn derive_into_event(input: DeriveInput) -> TokenStream { + match input.data { + syn::Data::Struct(struct_data) => make_init_from_struct(input.ident, struct_data) + .unwrap_or_else(|e| e.to_compile_error()) + .into(), + syn::Data::Enum(enum_data) => Error::new( + enum_data.enum_token.span, + "[IntoEvent] `derive(IntoEvent)` cannot be applied to Enum.", + ) + .to_compile_error() + .into(), + syn::Data::Union(union_data) => Error::new( + union_data.union_token.span, + "[IntoEvent] `derive(IntoEvent)` cannot be applied to Union.", + ) + .to_compile_error() + .into(), + } +} diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 1a0f516f1f..cd8d8c9576 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate syn; +mod into_event; + use proc_macro::TokenStream; use std::str::FromStr; @@ -79,3 +81,45 @@ pub fn entry_point(_attr: TokenStream, mut item: TokenStream) -> TokenStream { item.extend(entry); item } + +/// generate an ast for `impl Into` from a struct +/// +/// Structure: +/// +/// ```no_test +/// #[derive(IntoEvent)] +/// struct StructName { +/// field_name_1: field_type_1, +/// // if the value's type does not implement `Into` trait +/// // and it implements `ToString` trait, programmers can specify +/// // to use `field_name_1.to_string()` to get string +/// // by applying `use_to_string`. +/// #[use_to_string] +/// field_name_2: field_type_2, +/// // if the value's type does not implement both `Into` and +/// // `ToString` traits, programmers need specify a function +/// // to get string with `casting_fn(field_name_2)` by applying +/// // `to_string_fn(casting_fn)` attribute. +/// // this `casting_fn` needs to have the type `field_type -> String`. +/// #[to_string_fn(cast_fn_3)] +/// field_name_3: field_type_3, +/// } +/// ``` +/// +/// Output AST: +/// +/// ```no_test +/// impl Into for `StructName` { +/// fn into(self) -> Event { +/// Event::new("struct_name") +/// .add_attribute("field_name_1", self.field_value_1) +/// .add_attribute("field_name_2", self.field_value_2.to_string()) +/// .add_attribute("field_name_3", casting_fn(self.field_value_3)) +/// } +/// } +/// ``` +#[proc_macro_derive(IntoEvent, attributes(to_string_fn, use_to_string))] +pub fn derive_into_event(input: TokenStream) -> TokenStream { + let derive_input = parse_macro_input!(input as syn::DeriveInput); + into_event::derive_into_event(derive_input) +} diff --git a/packages/derive/tests/test.rs b/packages/derive/tests/test.rs new file mode 100644 index 0000000000..ac279469b6 --- /dev/null +++ b/packages/derive/tests/test.rs @@ -0,0 +1,82 @@ +extern crate cosmwasm_derive; + +use cosmwasm_derive::IntoEvent; +use cosmwasm_std::{attr, coins, Addr, Coin, Event}; + +fn coins_to_string(coins: Vec) -> String { + format!( + "[{}]", + coins + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(", ") + ) +} + +#[test] +fn into_event_basic() { + #[derive(IntoEvent)] + struct TransferEvent { + #[use_to_string] + id: u64, + from: Addr, + receiver: Addr, + #[to_string_fn(coins_to_string)] + amount: Vec, + } + + let transfer = TransferEvent { + id: 42, + from: Addr::unchecked("alice"), + receiver: Addr::unchecked("bob"), + amount: coins(42, "link"), + }; + let expected = Event::new("transfer_event").add_attributes(vec![ + attr("id", "42"), + attr("from", "alice"), + attr("receiver", "bob"), + attr("amount", coins_to_string(coins(42, "link"))), + ]); + let transfer_event: Event = transfer.into(); + assert_eq!(transfer_event, expected); +} + +#[test] +fn into_event_with_non_related_attribute() { + #[derive(IntoEvent)] + struct TransferEvent { + #[rustfmt::skip] + from: Addr, + #[rustfmt::skip] + receiver: Addr, + #[rustfmt::skip] + #[to_string_fn(coins_to_string)] + amount: Vec, + } + + let transfer = TransferEvent { + from: Addr::unchecked("alice"), + receiver: Addr::unchecked("bob"), + amount: coins(42, "link"), + }; + let expected = Event::new("transfer_event").add_attributes(vec![ + attr("from", "alice"), + attr("receiver", "bob"), + attr("amount", coins_to_string(coins(42, "link"))), + ]); + let transfer_event: Event = transfer.into(); + assert_eq!(transfer_event, expected); +} + +#[test] +fn into_event_no_fields() { + #[derive(IntoEvent)] + struct A {} + + let a = A {}; + let expected = Event::new("a"); + + let a_event: Event = a.into(); + assert_eq!(a_event, expected); +} diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 75ab78e1e2..de35299c77 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -134,4 +134,4 @@ pub mod testing; // Re-exports -pub use cosmwasm_derive::entry_point; +pub use cosmwasm_derive::{entry_point, IntoEvent}; From a64896fd0346d7ec071e972d74ea8adef5dbccc3 Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 26 Nov 2021 22:15:43 +0900 Subject: [PATCH 2/4] make `add_event` and `add_events` able to takes types implements `IntoEvent` --- packages/std/src/results/response.rs | 84 ++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/packages/std/src/results/response.rs b/packages/std/src/results/response.rs index 7ab54ced94..29310cd877 100644 --- a/packages/std/src/results/response.rs +++ b/packages/std/src/results/response.rs @@ -129,8 +129,8 @@ impl Response { /// /// The `wasm-` prefix will be appended by the runtime to the provided type /// of event. - pub fn add_event(mut self, event: Event) -> Self { - self.events.push(event); + pub fn add_event(mut self, event: impl Into) -> Self { + self.events.push(event.into()); self } @@ -219,8 +219,11 @@ impl Response { /// /// The `wasm-` prefix will be appended by the runtime to the provided types /// of events. - pub fn add_events(mut self, events: impl IntoIterator) -> Self { - self.events.extend(events); + pub fn add_events(mut self, events: impl IntoIterator) -> Self + where + E: Into, + { + self.events.extend(events.into_iter().map(|e| e.into())); self } @@ -236,7 +239,7 @@ mod tests { use super::super::BankMsg; use super::*; use crate::results::submessages::{ReplyOn, UNUSED_MSG_ID}; - use crate::{coins, from_json, to_json_vec, ContractResult}; + use crate::{attr, coins, from_json, to_json_vec, Addr, Coin, ContractResult, IntoEvent}; #[test] fn response_add_attributes_works() { @@ -331,4 +334,75 @@ mod tests { assert!(failure.is_err()); assert!(!success.is_err()); } + + #[test] + fn using_into_event() { + // IntoEvent can be used only when cosmwasm_std is imported as `cosmwasm_std` + use crate as cosmwasm_std; + + fn coins_to_string(coins: Vec) -> String { + format!( + "[{}]", + coins + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(", ") + ) + } + + #[derive(Clone, IntoEvent)] + struct TransferEvent { + from: Addr, + receiver: Addr, + #[to_string_fn(coins_to_string)] + amount: Vec, + } + + let transfer_event = TransferEvent { + from: Addr::unchecked("alice"), + receiver: Addr::unchecked("bob"), + amount: coins(42, "link"), + }; + let expected = + Response::::new().add_event(Event::new("transfer_event").add_attributes(vec![ + attr("from", "alice"), + attr("receiver", "bob"), + attr("amount", coins_to_string(coins(42, "link"))), + ])); + let actual = Response::::new().add_event(transfer_event); + assert_eq!(actual, expected); + } + + #[test] + fn using_into_event_add_events() { + use crate as cosmwasm_std; + + fn u32_to_string(n: u32) -> String { + n.to_string() + } + + #[derive(IntoEvent)] + struct Act { + name: String, + #[to_string_fn(u32_to_string)] + amount: u32, + } + + let act1 = Act { + name: "mint".to_string(), + amount: 42, + }; + let act2 = Act { + name: "burn".to_string(), + amount: 21, + }; + let event1 = + Event::new("act").add_attributes(vec![attr("name", "mint"), attr("amount", "42")]); + let event2 = + Event::new("act").add_attributes(vec![attr("name", "burn"), attr("amount", "21")]); + let expected = Response::::new().add_events(vec![event1, event2]); + let actual = Response::::new().add_events(vec![act1, act2]); + assert_eq!(actual, expected); + } } From 908b554c5dc8d5f2bc29d4494b88cbf6b3550df3 Mon Sep 17 00:00:00 2001 From: loloicci Date: Tue, 5 Mar 2024 20:37:30 +0900 Subject: [PATCH 3/4] update contracts' Cargo.lock --- contracts/burner/Cargo.lock | 9 +++++++++ contracts/crypto-verify/Cargo.lock | 9 +++++++++ contracts/cyberpunk/Cargo.lock | 9 +++++++++ contracts/empty/Cargo.lock | 9 +++++++++ contracts/floaty/Cargo.lock | 9 +++++++++ contracts/hackatom/Cargo.lock | 9 +++++++++ contracts/ibc-reflect-send/Cargo.lock | 9 +++++++++ contracts/ibc-reflect/Cargo.lock | 9 +++++++++ contracts/queue/Cargo.lock | 9 +++++++++ contracts/reflect/Cargo.lock | 9 +++++++++ contracts/staking/Cargo.lock | 9 +++++++++ contracts/virus/Cargo.lock | 9 +++++++++ 12 files changed, 108 insertions(+) diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index d9daddad55..fe21a3cf59 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -184,6 +184,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -212,6 +218,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index fa0f39b71b..a737c81bb7 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock index fa91313dd4..d042a7701d 100644 --- a/contracts/cyberpunk/Cargo.lock +++ b/contracts/cyberpunk/Cargo.lock @@ -208,6 +208,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -236,6 +242,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/empty/Cargo.lock b/contracts/empty/Cargo.lock index 7572bac2e5..abd61d9bec 100644 --- a/contracts/empty/Cargo.lock +++ b/contracts/empty/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index ada4e8f5f1..852a9ce583 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index f882c8e7f6..00cd08624e 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index 0af8f97d56..202958c723 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index d4f9a48a1f..c954b06933 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 5e3092c0fa..ec69477e2c 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index 8e658a0203..55a5ed3865 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index 4885b43153..3e45802538 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock index 3e1ce68010..3cf4a64e65 100644 --- a/contracts/virus/Cargo.lock +++ b/contracts/virus/Cargo.lock @@ -173,6 +173,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.3" @@ -201,6 +207,9 @@ dependencies = [ name = "cosmwasm-derive" version = "2.0.0-rc.1" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "syn 1.0.109", ] From 940fb33792ba4829f76ce324eae89df42c4260fc Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 6 Mar 2024 19:05:47 +0900 Subject: [PATCH 4/4] improve code and add tests --- packages/derive/src/into_event.rs | 142 +++++++++++++++++++++++++++--- packages/derive/src/lib.rs | 12 +-- 2 files changed, 137 insertions(+), 17 deletions(-) diff --git a/packages/derive/src/into_event.rs b/packages/derive/src/into_event.rs index f45656ca0a..d17436deb8 100644 --- a/packages/derive/src/into_event.rs +++ b/packages/derive/src/into_event.rs @@ -22,7 +22,7 @@ fn scan_to_string_fn(field: &Field) -> Result> if filtered.is_empty() { Ok(None) } else { - Ok(Some(filtered[0].tokens.clone())) + Ok(Some(filtered[0].parse_args()?)) } } @@ -35,7 +35,7 @@ fn has_use_to_string(field: &Field) -> Result { if filtered.clone().any(|a| !a.tokens.is_empty()) { return Err(Error::new( field.span(), - "[IntoEvent] attribute `use_to_string` has some value. If you intend to specify the cast function to string, use `to_string_fn` instead.", + "[IntoEvent] An attribute `use_to_string` has some value. If you intend to specify the cast function to string, use `to_string_fn` instead.", )); } Ok(filtered.next().is_some()) @@ -88,23 +88,143 @@ fn make_init_from_struct(id: Ident, struct_data: DataStruct) -> Result TokenStream { +fn derive_into_event_impl(input: DeriveInput) -> proc_macro2::TokenStream { match input.data { - syn::Data::Struct(struct_data) => make_init_from_struct(input.ident, struct_data) - .unwrap_or_else(|e| e.to_compile_error()) - .into(), + syn::Data::Struct(struct_data) => { + make_init_from_struct(input.ident, struct_data).unwrap_or_else(|e| e.to_compile_error()) + } syn::Data::Enum(enum_data) => Error::new( enum_data.enum_token.span, "[IntoEvent] `derive(IntoEvent)` cannot be applied to Enum.", ) - .to_compile_error() - .into(), + .to_compile_error(), syn::Data::Union(union_data) => Error::new( union_data.union_token.span, "[IntoEvent] `derive(IntoEvent)` cannot be applied to Union.", ) - .to_compile_error() - .into(), + .to_compile_error(), + } +} + +/// derive `IntoEvent` from a derive input. The input needs to be a struct. +pub fn derive_into_event(input: DeriveInput) -> TokenStream { + derive_into_event_impl(input).into() +} + +#[cfg(test)] +mod tests { + use super::*; + use proc_macro2::TokenStream; + use syn::parse_quote; + + fn expect_compile_error(ts: TokenStream, msg: &str) { + assert!( + ts.to_string().starts_with("compile_error ! {"), + "Code does not raise compile error: `{}`", + ts + ); + + assert!( + ts.to_string().contains(msg), + "Error does not have expected message \"{}\": `{}`", + msg, + ts + ); + } + + #[test] + fn test_doc_example() { + let input: DeriveInput = parse_quote! { + struct StructName { + field_name_1: field_type_1, + #[use_to_string] + field_name_2: field_type_2, + #[to_string_fn(cast_fn_3)] + field_name_3: field_type_3, + } + }; + let result_implement = derive_into_event_impl(input); + let expected: TokenStream = parse_quote! { + impl Into for StructName { + fn into(self) -> cosmwasm_std::Event { + cosmwasm_std::Event::new("struct_name") + .add_attribute(stringify!(field_name_1), self.field_name_1) + .add_attribute(stringify!(field_name_2), self.field_name_2.to_string()) + .add_attribute(stringify!(field_name_3), cast_fn_3 (self.field_name_3)) + } + } + }; + assert_eq!(expected.to_string(), result_implement.to_string()) + } + + #[test] + fn test_error_multiple_to_string_functions() { + let input: DeriveInput = parse_quote! { + struct StructName { + #[to_string_fn(cast_fn_1)] + #[to_string_fn(cast_fn_1)] + field_name_1: field_type_1, + } + }; + let result_implement = derive_into_event_impl(input); + expect_compile_error( + result_implement, + "[IntoEvent] Only one or zero `to_string_fn`", + ); + } + + #[test] + fn test_error_use_to_string_has_value() { + let input: DeriveInput = parse_quote! { + struct StructName { + #[use_to_string(foo)] + field_name_1: field_type_1, + } + }; + let result_implement = derive_into_event_impl(input); + expect_compile_error( + result_implement, + "[IntoEvent] An attribute `use_to_string` has some value", + ); + } + + #[test] + fn test_error_both_two_attributes_is_used() { + let input: DeriveInput = parse_quote! { + struct StructName { + #[use_to_string] + #[to_string_fn(cast_fn_1)] + field_name_1: field_type_1, + } + }; + let result_implement = derive_into_event_impl(input); + expect_compile_error( + result_implement, + "[IntoEvent] Both `use_to_string` and `to_string_fn`", + ); + } + + #[test] + fn test_error_derive_enum() { + let input: DeriveInput = parse_quote! { + enum Enum {} + }; + let result_implement = derive_into_event_impl(input); + expect_compile_error( + result_implement, + "[IntoEvent] `derive(IntoEvent)` cannot be applied to Enum", + ); + } + + #[test] + fn test_error_derive_union() { + let input: DeriveInput = parse_quote! { + union Union {} + }; + let result_implement = derive_into_event_impl(input); + expect_compile_error( + result_implement, + "[IntoEvent] `derive(IntoEvent)` cannot be applied to Union", + ); } } diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index cd8d8c9576..0a67bfe6dc 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -109,12 +109,12 @@ pub fn entry_point(_attr: TokenStream, mut item: TokenStream) -> TokenStream { /// Output AST: /// /// ```no_test -/// impl Into for `StructName` { -/// fn into(self) -> Event { -/// Event::new("struct_name") -/// .add_attribute("field_name_1", self.field_value_1) -/// .add_attribute("field_name_2", self.field_value_2.to_string()) -/// .add_attribute("field_name_3", casting_fn(self.field_value_3)) +/// impl Into for StructName { +/// fn into(self) -> cosmwasm_std::Event { +/// cosmwasm_std::Event::new("struct_name") +/// .add_attribute("field_name_1", self.field_name_1) +/// .add_attribute("field_name_2", self.field_name_2.to_string()) +/// .add_attribute("field_name_3", cast_fn_3(self.field_name_3)) /// } /// } /// ```