sylvia_derive/contract/communication/
wrapper_msg.rs

1use crate::crate_module;
2use crate::fold::StripGenerics;
3use crate::parser::{ContractErrorAttr, Custom, MsgType};
4use crate::types::interfaces::Interfaces;
5use crate::utils::emit_bracketed_generics;
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::fold::Fold;
9use syn::spanned::Spanned;
10use syn::{Ident, ItemImpl, Type};
11
12/// Glue message is the message composing Exec/Query/Sudo messages from several traits and a
13/// contract.
14///
15/// It's required for the contract to receive all possible message variants in the entry points.
16/// More info [here](https://cosmwasm-docs.vercel.app/sylvia/macros/generated-types/message-types).
17#[derive(Debug)]
18pub struct GlueMessage<'a> {
19    source: &'a ItemImpl,
20    contract: &'a Type,
21    msg_ty: MsgType,
22    error: &'a ContractErrorAttr,
23    custom: &'a Custom,
24    interfaces: &'a Interfaces,
25}
26
27impl<'a> GlueMessage<'a> {
28    pub fn new(
29        source: &'a ItemImpl,
30        msg_ty: MsgType,
31        error: &'a ContractErrorAttr,
32        custom: &'a Custom,
33        interfaces: &'a Interfaces,
34    ) -> Self {
35        GlueMessage {
36            source,
37            contract: &source.self_ty,
38            msg_ty,
39            error,
40            custom,
41            interfaces,
42        }
43    }
44
45    pub fn emit(&self) -> TokenStream {
46        let sylvia = crate_module();
47        let Self {
48            source,
49            contract,
50            msg_ty,
51            error,
52            custom,
53            interfaces,
54            ..
55        } = self;
56
57        let generics: Vec<_> = source.generics.params.iter().collect();
58        let full_where_clause = &source.generics.where_clause;
59        let bracketed_wrapper_generics = emit_bracketed_generics(&generics);
60
61        let contract_enum_name = msg_ty.emit_msg_wrapper_name();
62        let enum_accessor = msg_ty.as_accessor_name();
63        let contract_name = StripGenerics.fold_type((*contract).clone());
64
65        let variants = interfaces.emit_glue_message_variants(msg_ty, contract);
66        let types = interfaces.emit_glue_message_types(msg_ty, contract);
67
68        let ep_name = msg_ty.emit_ep_name();
69        let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), contract.span());
70        let contract_variant = quote! { #contract_name ( <#contract as #sylvia ::types::ContractApi> :: #enum_accessor ) };
71        let mut messages_call = interfaces.emit_messages_call(msg_ty);
72        messages_call.push(quote! { &#messages_fn_name() });
73
74        let variants_cnt = messages_call.len();
75
76        let dispatch_arms = interfaces.emit_dispatch_arms(msg_ty);
77
78        let dispatch_arm =
79            quote! {#contract_enum_name :: #contract_name (msg) => msg.dispatch(contract, ctx)};
80
81        let interfaces_deserialization_attempts = interfaces.emit_deserialization_attempts(msg_ty);
82
83        let contract_deserialization_attempt = quote! {
84            let msgs = &#messages_fn_name();
85            if msgs.into_iter().any(|msg| msg == &recv_msg_name) {
86                match val.deserialize_into() {
87                    Ok(msg) => return Ok(Self:: #contract_name (msg)),
88                    Err(err) => return Err(D::Error::custom(err)).map(Self:: #contract_name )
89                };
90            }
91        };
92
93        let ctx_type = msg_ty.emit_ctx_type(&custom.query_or_default());
94        let ret_type = msg_ty.emit_result_type(&custom.msg_or_default(), &error.error);
95
96        let mut response_schemas_calls = interfaces.emit_response_schemas_calls(msg_ty, contract);
97        response_schemas_calls
98            .push(quote! {<#contract as #sylvia ::types::ContractApi> :: #enum_accessor ::response_schemas_impl()});
99
100        let response_schemas = match msg_ty {
101            MsgType::Query => {
102                quote! {
103                    #[cfg(not(target_arch = "wasm32"))]
104                    impl #bracketed_wrapper_generics #sylvia ::cw_schema::QueryResponses for #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
105                        fn response_schemas_impl() -> std::collections::BTreeMap<String, #sylvia ::schemars::schema::RootSchema> {
106                            let responses = [#(#response_schemas_calls),*];
107                            responses.into_iter().flatten().collect()
108                        }
109                    }
110                }
111            }
112            _ => {
113                quote! {}
114            }
115        };
116
117        let modules_names = interfaces.variants_modules();
118        let variants_names = interfaces.variants_names();
119        let serde = quote! { #sylvia:: serde }.to_string();
120
121        quote! {
122            #[allow(clippy::derive_partial_eq_without_eq)]
123            #[derive(#sylvia ::serde::Serialize, Clone, Debug, PartialEq)]
124            #[serde(rename_all="snake_case", untagged)]
125            #[serde(crate = #serde )]
126            pub enum #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
127                #(#variants,)*
128                #contract_variant
129            }
130
131            // `schemars` v0.8.16 requires every generic type to implement JsonSchema in
132            // order to use derive JsonSchema macro. The goal of that trait bound is to
133            // generate schema_name. Currently there's no way to provide such a name in an
134            // attribute, so Sylvia needs to implement this trait manually:
135            //
136            impl #bracketed_wrapper_generics #sylvia ::schemars::JsonSchema
137                for #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
138
139                fn schema_name() -> std::string::String {
140                    {
141                        let res = format!(
142                                "{0}",
143                                std::any::type_name::<Self>()
144                        );
145                        res
146                    }
147                }
148
149                fn json_schema(
150                    gen: &mut #sylvia ::schemars::gen::SchemaGenerator,
151                ) -> #sylvia ::schemars::schema::Schema {
152                    #sylvia ::schemars::schema::Schema::Object( #sylvia ::schemars::schema::SchemaObject {
153                        subschemas: Some(
154                            Box::new( #sylvia ::schemars::schema::SubschemaValidation {
155                                any_of: Some(
156                                    <[_]>::into_vec(
157                                        Box::new([
158                                            #(gen.subschema_for::<#types>(),)*
159                                            gen.subschema_for::< <#contract as #sylvia ::types::ContractApi> :: #enum_accessor >(),
160                                        ]),
161                                    ),
162                                ),
163                                ..Default::default()
164                            }),
165                        ),
166                        ..Default::default()
167                    })
168                }
169            }
170
171            impl #bracketed_wrapper_generics #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
172                pub fn dispatch (
173                    self,
174                    contract: &#contract,
175                    ctx: #ctx_type,
176                ) -> #ret_type #full_where_clause {
177                    const _: () = {
178                        let msgs: [&[&str]; #variants_cnt] = [#(#messages_call),*];
179                        #sylvia ::utils::assert_no_intersection(msgs);
180                    };
181
182                    match self {
183                        #(#dispatch_arms,)*
184                        #dispatch_arm
185                    }
186                }
187            }
188
189            #response_schemas
190
191            impl<'sv_de, #(#generics,)* > #sylvia ::serde::Deserialize<'sv_de> for #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
192                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193                    where D: #sylvia ::serde::Deserializer<'sv_de>,
194                {
195                    use #sylvia ::serde::de::Error;
196
197                    let val = #sylvia ::serde_value::Value::deserialize(deserializer)?;
198                    let map = match &val {
199                        #sylvia ::serde_value::Value::Map(map) => map,
200                        _ => return Err(D::Error::custom("Wrong message format!"))
201                    };
202                    if map.len() != 1 {
203                        return Err(D::Error::custom(format!("Expected exactly one message. Received {}", map.len())))
204                    }
205
206                    // Due to earlier size check of map this unwrap is safe
207                    let recv_msg_name = map.into_iter().next().unwrap();
208
209                    if let #sylvia ::serde_value::Value::String(recv_msg_name) = &recv_msg_name .0 {
210                        #(#interfaces_deserialization_attempts)*
211                        #contract_deserialization_attempt
212                    }
213
214                    let msgs: [&[&str]; #variants_cnt] = [#(#messages_call),*];
215                    let mut err_msg = msgs.into_iter().flatten().fold(
216                        // It might be better to forward the error or serialization, but we just
217                        // deserialized it from JSON, not reason to expect error here.
218                        format!(
219                            "Unsupported message received: {}. Messages supported by this contract: ",
220                            #sylvia ::serde_json::to_string(&val).unwrap_or_else(|_| String::new())
221                        ),
222                        |mut acc, message| acc + message + ", ",
223                    );
224                    err_msg.truncate(err_msg.len() - 2);
225                    Err(D::Error::custom(err_msg))
226                }
227            }
228
229            impl #bracketed_wrapper_generics From<<#contract as #sylvia ::types::ContractApi>:: #enum_accessor>
230                for #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
231                fn from(a: <#contract as #sylvia ::types::ContractApi>:: #enum_accessor ) -> Self {
232                    Self:: #contract_name (a)
233                }
234            }
235
236            #(
237            impl #bracketed_wrapper_generics From<<#contract as #modules_names ::sv::InterfaceMessagesApi>:: #enum_accessor>
238                for #contract_enum_name #bracketed_wrapper_generics #full_where_clause {
239                fn from(a: <#contract as #modules_names ::sv::InterfaceMessagesApi>:: #enum_accessor ) -> Self {
240                    Self:: #variants_names (a)
241                }
242            }
243            )*
244        }
245    }
246}