sylvia_derive/
lib.rs

1//! This crate defines [Sylvia](https://docs.rs/sylvia/latest/sylvia) procedural macros.
2//!
3//! Please refer to the [Sylvia-book](https://cosmwasm.github.io/sylvia-book/index.html) on how to use these macros.
4
5use crate::parser::EntryPointArgs;
6use contract::ContractInput;
7use entry_points::EntryPointInput;
8use fold::StripInput;
9use interface::InterfaceInput;
10use proc_macro::TokenStream;
11use proc_macro2::TokenStream as TokenStream2;
12use proc_macro_error::proc_macro_error;
13use quote::quote;
14use syn::fold::Fold;
15use syn::spanned::Spanned;
16use syn::{parse2, parse_quote, ItemImpl, ItemTrait, Path};
17
18mod contract;
19mod entry_points;
20mod fold;
21mod interface;
22mod parser;
23mod types;
24mod utils;
25
26#[cfg(not(test))]
27pub(crate) fn crate_module() -> Path {
28    use proc_macro_crate::{crate_name, FoundCrate};
29
30    match crate_name("sylvia").expect("sylvia is not found in Cargo.toml") {
31        FoundCrate::Itself => parse_quote!(sylvia),
32        FoundCrate::Name(name) => {
33            let ident = syn::Ident::new(&name, proc_macro2::Span::mixed_site());
34            parse_quote!(#ident)
35        }
36    }
37}
38
39#[cfg(test)]
40pub(crate) fn crate_module() -> Path {
41    parse_quote!(sylvia)
42}
43
44/// Procedural macro generating messages from a contract trait.
45/// Generates `sudo`, `exec` and `query` enum messages to be later used in contract implementation.
46///
47/// ## Example usage
48///
49/// ```rust
50/// # use sylvia::cw_schema::cw_serde;
51/// # use sylvia::cw_std::{Response, StdError};
52/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
53/// #
54/// # #[cw_serde]
55/// # pub struct AdminQueryResponse;
56/// #
57/// ##[sylvia::interface]
58/// pub trait SvInterface {
59///    type Error: From<StdError>;
60///
61///    #[sv::msg(exec)]
62///    fn update_admin(&self, ctx: ExecCtx, admin: Option<String>) -> Result<Response, Self::Error>;
63///
64///    #[sv::msg(query)]
65///    fn admin(&self, ctx: QueryCtx) -> Result<AdminQueryResponse, Self::Error>;
66///
67///    #[sv::msg(sudo)]
68///    fn remove_admin(&self, ctx: SudoCtx, #[serde(default)] admin: String) -> Result<Response, Self::Error>;
69/// }
70/// # fn main() {}
71/// ```
72///
73/// This would generate output like:
74///
75/// ```rust
76/// # use sylvia::cw_schema::cw_serde;
77/// # use sylvia::cw_std::{Response, StdError, DepsMut, Env, MessageInfo};
78/// # use sylvia::ctx::ExecCtx;
79/// #
80/// # pub trait SvInterface {
81/// #    type Error: From<StdError>;
82/// # }
83/// #
84/// pub mod sv {
85/// #   use super::*;
86/// #
87///     #[derive(
88///         sylvia::serde::Serialize,
89///         sylvia::serde::Deserialize,
90///         Clone,
91///         Debug,
92///         PartialEq,
93///         sylvia::schemars::JsonSchema,
94///     )]
95///     #[serde(rename_all = "snake_case")]
96///     pub enum ExecMsg {
97///         UpdateAdmin { admin: Option<String> },
98///     }
99///
100///     impl ExecMsg {
101///         pub fn dispatch<C: SvInterface>(contract: &C, ctx: (DepsMut, Env, MessageInfo))
102///             -> Result<Response, C::Error>
103///         {
104///             // Some dispatching implementation
105/// #           Ok(Response::new())
106///         }
107///     }
108/// }
109/// # fn main() {}
110/// ```
111///
112/// Similarly for `Query` and `Sudo` enum messages.
113///
114/// ## Associated types
115///
116/// Generics are not supported by the `interface` macro and won't be. Instead, you can define
117/// associated types on an interface to allow users implementing it to customize the behavior to their liking.
118///
119/// Some names are however parsed and used in special contexts. Those are:
120/// * `Error` - error type returned by interface methods. This one is required to be declared.
121/// * `ExecC` - custom message type used in messages. Has to implement `cosmwasm_std::CustomMsg`.
122/// * `QueryC` - custom query type used in messages. Has to implement `cosmwasm_std::CustomQuery`.
123///
124/// ```rust
125/// # use sylvia::cw_schema::cw_serde;
126/// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
127/// # use sylvia::ctx::ExecCtx;
128/// #
129/// ##[sylvia::interface]
130/// pub trait SvInterface {
131///    type Error: From<StdError>;
132///    type ExecC: CustomMsg;
133///    type QueryC: CustomQuery;
134///
135///    #[sv::msg(exec)]
136///    fn update_admin(&self, ctx: ExecCtx<Self::QueryC>, admin: Option<String>) -> Result<Response<Self::ExecC>, Self::Error>;
137/// }
138/// # fn main() {}
139/// ```
140///
141/// Although it's not required to define `ExecC` and `QueryC` types, it's recommended to do so to allow
142/// the users of the interface to customize the behavior to their liking.
143///
144/// If however you want to restrict the interface to use specific custom message and query types,
145/// you can do so using `#[sv::custom(msg=..., query=...)]` attribute explained below.
146///
147/// ## Attributes
148///
149/// `Interface` macro supports multiple attributes to customize the behavior of generated messages.
150///
151/// ### `sv::msg(...)`
152///
153/// Messages structures are generated basing on interface trait method attributed with
154/// `#[sv::msg(msg_type)]`. Msg attribute takes as its first argument type of message it is
155/// supposed to handle:
156///   * `exec` - execute message variant
157///   * `query` - query message variant
158///   * `sudo` - sudo message variant
159///
160/// In the case of a query, it is possible to pass a second argument which is its `ResponseType`.
161/// This is required in case of aliased results wrapping their `ResponseType` to properly
162/// implement [QueryResponses](https://docs.rs/cosmwasm-schema/latest/cosmwasm_schema/trait.QueryResponses.html).
163///
164/// ```rust
165/// # use sylvia::cw_schema::cw_serde;
166/// # use sylvia::cw_std::{Response, StdError};
167/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
168/// #
169/// # #[cw_serde]
170/// # pub struct AdminQueryResponse;
171/// #
172/// pub type AdminResult<ErrorT> = Result<AdminQueryResponse, ErrorT>;
173///
174/// ##[sylvia::interface]
175/// pub trait SvInterface {
176///    type Error: From<StdError>;
177///
178///    #[sv::msg(exec)]
179///    fn update_admin(&self, ctx: ExecCtx, admin: Option<String>) -> Result<Response, Self::Error>;
180///
181///    #[sv::msg(query, resp=AdminQueryResponse)]
182///    fn admin(&self, ctx: QueryCtx) -> AdminResult<Self::Error>;
183///
184///    #[sv::msg(sudo)]
185///    fn remove_admin(&self, ctx: SudoCtx, admin: String) -> Result<Response, Self::Error>;
186/// }
187/// # fn main() {}
188/// ```
189///
190/// ### `sv::custom(msg=..., query=...)`
191///
192/// Allows restricting interface to use specific
193/// custom message and query types. If used with `ExecC` and `QueryC` associated
194/// types `sv::custom(...)` attribute has priority in defining custom types.
195///
196/// ```rust
197/// # use sylvia::cw_schema::cw_serde;
198/// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
199/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
200/// #
201/// ##[cw_serde]
202/// pub enum SvCustomMsg {}
203///
204/// ##[cw_serde]
205/// pub enum SvCustomQuery {}
206///
207/// impl CustomMsg for SvCustomMsg {}
208/// impl CustomQuery for SvCustomQuery {}
209///
210/// ##[sylvia::interface]
211/// ##[sv::custom(msg=SvCustomMsg, query=SvCustomQuery)]
212/// pub trait SvInterface {
213///    type Error: From<StdError>;
214///    type ExecC: CustomMsg;
215///    type QueryC: CustomQuery;
216///
217///    #[sv::msg(exec)]
218///    fn update_admin(&self, ctx: ExecCtx<SvCustomQuery>, admin: Option<String>) -> Result<Response<SvCustomMsg>, Self::Error>;
219/// }
220/// # fn main() {}
221/// ```
222///
223/// ### `sv::msg_attr(msg_type, {...})`
224///
225/// This attribute can be used for the whole `trait Interface {}` block and
226/// for the following message types: `exec`, `query` and `sudo`.
227/// The `{...}` part will be forwarded as an attribute `#[{...}]` to the
228/// given message type (enum or struct).
229///
230/// ### `sv::attr({...})`
231///
232/// Forwards variant's attribute to the specific enum's field in the
233/// generated message type. It can be used along with `sv::msg(...)`
234/// and only for message types variants that resolves in an enum field,
235/// i.e. `exec`, `query` and `sudo`.
236#[proc_macro_error]
237#[proc_macro_attribute]
238pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
239    interface_impl(attr.into(), item.into()).into()
240}
241
242fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
243    fn inner(item: TokenStream2) -> syn::Result<TokenStream2> {
244        let input: ItemTrait = parse2(item)?;
245
246        let expanded = InterfaceInput::new(&input).process();
247        let input = StripInput.fold_item_trait(input);
248
249        Ok(quote! {
250            #input
251
252            #expanded
253        })
254    }
255
256    inner(item).unwrap_or_else(syn::Error::into_compile_error)
257}
258
259/// Procedural macro generating messages from contract impl block.
260/// Generates `instantiate`, `migrate`, `reply`, `sudo`, `exec` and `query`
261/// enum messages to be later used in contract implementation.
262///
263/// ## Example usage
264/// ```rust
265/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
266/// # use sylvia::ctx::ReplyCtx;
267/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
268/// # use cw_storage_plus::Item;
269/// # use thiserror::Error;
270/// #
271/// # #[derive(Error, Debug, PartialEq)]
272/// # pub enum ContractError {
273/// #     #[error("{0}")]
274/// #     Std(#[from] StdError),
275/// # }
276/// # pub struct ContractData;
277/// #
278/// #
279/// pub struct SvContract {
280///     data: Item<ContractData>,
281/// }
282///
283/// ##[sylvia::contract]
284/// ##[sv::error(ContractError)]
285/// ##[sv::features(replies)]
286/// impl SvContract {
287///     pub const fn new() -> Self {
288///         Self {
289///             data: Item::new("data"),
290///         }
291///     }
292///
293///     #[sv::msg(instantiate)]
294///     fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
295/// #        Ok(Response::new())
296///     }
297///
298///     #[sv::msg(exec)]
299///     fn execute(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
300/// #        Ok(Response::new())
301///     }
302///
303///     #[sv::msg(query)]
304///     fn query(&self, ctx: QueryCtx) -> Result<Response, ContractError> {
305/// #        Ok(Response::new())
306///     }
307///
308///     #[sv::msg(migrate)]
309///     fn migrate(&self, ctx: MigrateCtx) -> Result<Response, ContractError> {
310/// #        Ok(Response::new())
311///     }
312///
313///     #[sv::msg(reply)]
314///     fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> Result<Response, ContractError> {
315/// #        Ok(Response::new())
316///     }
317///
318///     #[sv::msg(sudo)]
319///     fn sudo(&self, ctx: SudoCtx) -> Result<Response, ContractError> {
320/// #        Ok(Response::new())
321///     }
322/// }
323/// # fn main() {}
324/// ```
325///
326/// This would generate output like:
327///
328/// ```rust
329/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
330/// # use sylvia::ctx::ReplyCtx;
331/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
332/// # use cw_storage_plus::Item;
333/// # use thiserror::Error;
334/// #
335/// # #[derive(Error, Debug, PartialEq)]
336/// # pub enum ContractError {
337/// #     #[error("{0}")]
338/// #     Std(#[from] StdError),
339/// # }
340/// # pub struct ContractData;
341/// #
342/// # pub struct SvContract {
343/// #    data: Item<ContractData>,
344/// # }
345/// #
346/// # impl SvContract {
347/// #     pub const fn new() -> Self {
348/// #        Self {
349/// #            data: Item::new("data"),
350/// #        }
351/// #    }
352/// #
353/// #    fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
354/// #        Ok(Response::new())
355/// #    }
356/// #
357/// #    fn execute(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
358/// #        Ok(Response::new())
359/// #    }
360/// #
361/// #    fn query(&self, ctx: QueryCtx) -> Result<Response, ContractError> {
362/// #        Ok(Response::new())
363/// #    }
364/// #
365/// #    fn migrate(&self, ctx: MigrateCtx) -> Result<Response, ContractError> {
366/// #        Ok(Response::new())
367/// #    }
368/// #
369/// #    fn reply(&self, ctx: ReplyCtx) -> Result<Response, ContractError> {
370/// #        Ok(Response::new())
371/// #    }
372/// #
373/// #    fn sudo(&self, ctx: SudoCtx) -> Result<Response, ContractError> {
374/// #        Ok(Response::new())
375/// #    }
376/// # }
377/// #
378/// pub mod sv {
379/// #   use super::*;
380/// #
381///     #[allow(clippy::derive_partial_eq_without_eq)]
382///     #[derive(
383///         sylvia::serde::Serialize,
384///         sylvia::serde::Deserialize,
385///         Clone,
386///         Debug,
387///         PartialEq,
388///         sylvia::schemars::JsonSchema,
389///     )]
390///     #[serde(rename_all = "snake_case")]
391///     pub struct InstantiateMsg {
392///         pub admin: Option<String>,
393///     }
394///     impl InstantiateMsg {
395///         pub fn new(admin: Option<String>) -> Self {
396///             Self { admin }
397///         }
398///         pub fn dispatch(
399///             self,
400///             contract: &SvContract,
401///             ctx: (
402///                 sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
403///                 sylvia::cw_std::Env,
404///                 sylvia::cw_std::MessageInfo,
405///             ),
406///         ) -> Result<Response, ContractError> {
407///             let Self { admin } = self;
408///             contract
409///                 .instantiate(Into::into(ctx), admin)
410///                 .map_err(Into::into)
411///         }
412///     }
413/// }
414/// # fn main() {}
415/// ```
416///
417/// And appropriate messages for `exec`, `query`, `migrate`, `reply` and `sudo` variants.
418///
419/// ## Attributes
420///
421/// `Contract` macro supports multiple attributes to customize the behavior of generated messages.
422///
423/// ### `sv::msg(...)`
424///
425/// Message structures are generated based on specific implemented methods attributed with
426/// `#[sv::msg(msg_type)]`. Msg attribute takes as its first argument type of message it is
427/// supposed to handle:
428/// * `instantiate` - instantiation message handler. There should be always exactly one
429/// * `exec` - execute message variant
430/// * `query` - query message variant
431/// * `migrate` - migrate message variant
432/// * `reply` - reply message variant
433/// * `sudo` - sudo message variant
434///
435/// In the case of a query, it is possible to pass a second argument which is its `ResponseType`.
436/// This is required in case of aliased results wrapping their `ResponseType` to properly
437/// implement `QueryResponses`.
438///
439/// ```rust
440/// # use sylvia::ctx::{InstantiateCtx, QueryCtx};
441/// # use sylvia::cw_std::{Response, StdError};
442/// # use sylvia::cw_schema::cw_serde;
443/// # use cw_storage_plus::Item;
444/// # use thiserror::Error;
445/// #
446/// # #[derive(Error, Debug, PartialEq)]
447/// # pub enum ContractError {
448/// #     #[error("{0}")]
449/// #     Std(#[from] StdError),
450/// # }
451/// # pub struct ContractData;
452/// #
453/// # #[cw_serde]
454/// # pub struct QueryResponse;
455/// #
456/// pub struct SvContract {
457///     data: Item<ContractData>,
458/// }
459///
460/// ##[sylvia::contract]
461/// ##[sv::error(ContractError)]
462/// impl SvContract {
463///     pub const fn new() -> Self {
464///         Self {
465///             data: Item::new("data"),
466///         }
467///     }
468///
469///     #[sv::msg(instantiate)]
470///     fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
471///         Ok(Response::new())
472///     }
473///
474///     #[sv::msg(query, resp=QueryResponse)]
475///     fn query(&self, ctx: QueryCtx) -> Result<QueryResponse, ContractError> {
476///         // Some query implementation
477/// #       Ok(QueryResponse)
478///     }
479/// }
480/// # fn main() {}
481/// ```
482///
483/// ### `sv::custom(msg=..., query=...)`
484///
485/// Allows restricting interface to use specific
486/// custom message and query types. If used with `ExecC` and `QueryC` associated
487/// types `sv::custom(...)` attribute has priority in defining custom types.
488///
489/// ### `sv::error(error_type)`
490///
491/// Allows specifing custom error type for the contract. Default is `cosmwasm_std::StdError`.
492///
493/// ### `sv::override_entry_point(entry_point_type=<path_to_entry_point(msg_path)>`
494///
495/// Allows overriding default entry point for specific message type.
496/// Used in case there is a need to provide some custom functionality inside the entry point.
497/// Specified entry point will be used in generated `multitest` helpers
498///
499/// ```rust
500/// # use sylvia::ctx::InstantiateCtx;
501/// # use sylvia::cw_std::{Response, StdResult, MessageInfo, DepsMut, Env, entry_point};
502/// # use sylvia::cw_schema::cw_serde;
503/// #
504/// pub struct SvContract;
505///
506/// #[cw_serde]
507/// pub enum CustomExecMsg {}
508///
509/// ##[entry_point]
510/// pub fn execute(
511///     deps: DepsMut,
512///     env: Env,
513///     info: MessageInfo,
514///     msg: CustomExecMsg,
515/// ) -> StdResult<Response> {
516/// #   Ok(Response::new())
517/// }
518///
519///
520/// ##[sylvia::contract]
521/// ##[sv::override_entry_point(exec=execute(CustomExecMsg))]
522/// impl SvContract {
523/// #    pub const fn new() -> Self {
524/// #        Self
525/// #    }
526/// #
527/// #    #[sv::msg(instantiate)]
528/// #    fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> StdResult<Response> {
529/// #        Ok(Response::new())
530/// #    }
531/// }
532/// # fn main() {}
533/// ```
534/// ### `sv::messages(path_to_interface)`
535///
536/// Used to declare interfaces implemented on the contract.
537/// Required for the contract to be able to handle an interface message.
538///
539/// ```rust
540/// # use sylvia::ctx::InstantiateCtx;
541/// # use sylvia::cw_std::{Response, StdError, StdResult};
542/// # use cw_storage_plus::Item;
543/// #
544/// pub mod sv_interface {
545/// #   use sylvia::cw_std::StdError;
546/// #
547///     ##[sylvia::interface]
548///     pub trait SvInterface {
549///         type Error: From<StdError>;
550///     }
551/// }
552///
553/// impl sv_interface::SvInterface for SvContract {
554///     type Error = StdError;
555/// }
556///
557/// pub struct SvContract;
558///
559/// ##[sylvia::contract]
560/// ##[sv::messages(sv_interface)]
561/// impl SvContract {
562/// #     pub const fn new() -> Self {
563/// #         Self
564/// #     }
565/// #
566/// #     #[sv::msg(instantiate)]
567/// #     fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
568/// #         Ok(Response::new())
569/// #     }
570/// }
571/// # fn main() {}
572/// ```
573///
574/// In case an interface has different name than a module in which its defined
575/// you have to pass the name of an interface as below:
576///
577/// ```rust
578/// # use sylvia::ctx::InstantiateCtx;
579/// # use sylvia::cw_std::{Response, StdError, StdResult};
580/// # use cw_storage_plus::Item;
581/// #
582/// pub mod interface {
583/// #   use sylvia::cw_std::StdError;
584/// #
585///     ##[sylvia::interface]
586///     pub trait SvInterface {
587///         type Error: From<StdError>;
588///     }
589/// }
590///
591/// impl interface::SvInterface for SvContract {
592///     type Error = StdError;
593/// }
594///
595/// pub struct SvContract;
596///
597/// ##[sylvia::contract]
598/// ##[sv::messages(interface as SvInterface)]
599/// impl SvContract {
600/// #     pub const fn new() -> Self {
601/// #         Self
602/// #     }
603/// #
604/// #     #[sv::msg(instantiate)]
605/// #     fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
606/// #         Ok(Response::new())
607/// #     }
608/// }
609/// # fn main() {}
610/// ```
611///
612/// ### `sv::msg_attr(msg_type, {...})`
613///
614/// This attribute can be used for the whole `impl Contract {}` block and
615/// for every message type: `exec`, `query`, `sudo`, `instantiate`,
616/// `migrate` and `reply`. The `{...}` part will be forwarded as an
617/// attribute `#[{...}]` to the given message type (enum or struct).
618///
619/// ### `sv::attr({...})`
620///
621/// Forwards variant's attribute to the specific enum's field in the
622/// generated message type. It can be used along with `sv::msg(...)`
623/// and only for message types variants that resolves in an enum field,
624/// i.e. `exec`, `query` and `sudo`.
625///
626/// ### `sv::features(...)`
627///
628/// Enables additional features for the contract. Allows user to use features that
629/// are considered breaking before the major release.
630///
631/// Currently supported features are:
632/// * `replies` - enables better dispatching of `reply` as well as its auto deserialization.
633///     With this feature enabled, user can use additional parameters in the
634///     `sv::msg` attribute like so:
635///     `#[sv::msg(reply, handlers=[scenario1, scenario2], reply_on=Success)]`.
636///
637///     Based on this parameters reply ids will be generated and associated with
638///     proper scenario specified by the `reply_on` parameter.
639///
640///     User can also specify custom `data` and `payload` types that will be auto
641///     deserialized from the `cosmwasm_std::Binary` type.
642///
643/// ### `sv::payload(raw)`
644///
645/// Requires contract to be marked with the `sv::features(replies)`.
646///
647/// Used next to the reply method argument. It disables auto deserialization
648/// of the payload argument.
649///
650/// ### `sv::data(...)`
651///
652/// Requires contract to be marked with the `sv::features(replies)`.
653///
654/// Used next to the reply method argument. Based on the passed parameters
655/// it enables different behavior:
656///
657/// * `#[sv::data(raw)]` - Returns error if the data is `None`, extracts and forwards
658///     `Binary` if it's `Some`.
659///
660/// * `#[sv::data(raw, opt)]` - Forwards `data` as `Option<Binary>`.
661///
662/// * `#[sv::data(opt)]` - Expects type of `data` in the method signature to be
663///     `data: Option<T> where T: Deserialize`.
664///     Tries to deserialize `data` to type defined in the method signature
665///     and forwards it wrapped in the `Option`.
666///     Requires `data` to be JSON serialized.
667///
668/// If `data` is:
669/// | `Some(valid)` | `Some(invalid)` | `None` |
670/// |---|---|---|
671/// | forwards `Some(valid)` | early returns an error specifying what went wrong with `serde` error attached | `None` - forwards `None` |
672///
673/// * `#[sv::data]` - Expects data in the method signature to be `data: T where T: Deserialize`.
674///     Tries to deserialize data to type defined in the method signature
675///     and forwards it or returns early an error.
676///     Requires `data` to be JSON serialized.
677///
678/// If `data` is:
679/// | `Some(valid)` | `Some(invalid)` | `None` |
680/// |---|---|---|
681/// | forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
682///
683/// * `#[sv::data(instantiate)]` - special case for reply to `WasmMsg::instantiate` and
684///     `WasmMsg::instantiate2`. Tries to deserialize data using
685///     [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
686///
687/// If `data` is:
688/// | `Some(valid)` | `Some(invalid)` | `None` |
689/// |---|---|---|
690/// | extracts and forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
691///
692/// * `#[sv::data(instantiate, opt)]` - special case for reply to `WasmMsg::instantiate` and
693///     `WasmMsg::instantiate2`. tries to deserialize data using
694///     [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
695///
696/// if `data` is:
697/// | `Some(valid)` | `Some(invalid)` | `None` |
698/// |---|---|---|
699/// | forwards `Some(valid)` | early returns error specifying what went wrong with `serde` error attached | Forwards `None` |
700///
701/// * Missing `#[sv::data(...)]` - In case `sv::data` is not found Sylvia won't forward the `data` argument
702///     so the `data` should be omited in the method signature.
703#[proc_macro_error]
704#[proc_macro_attribute]
705pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
706    contract_impl(attr.into(), item.into()).into()
707}
708
709fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
710    fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
711        let input: ItemImpl = parse2(item)?;
712        let expanded = if attr.is_empty() {
713            ContractInput::new(&input).process()
714        } else {
715            quote! {}
716        };
717        let input = StripInput.fold_item_impl(input);
718
719        Ok(quote! {
720            #[allow(clippy::new_without_default)]
721            #input
722
723            #expanded
724        })
725    }
726
727    inner(attr, item).unwrap_or_else(syn::Error::into_compile_error)
728}
729
730/// Procedural macro generating cosmwasm entry points from contract impl block.
731/// By default generates `execute`, `instantiate`, `sudo`, `query` entry points.
732///
733/// ## Example usage
734/// ```rust
735/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
736/// # use sylvia::ctx::ReplyCtx;
737/// # use sylvia::cw_std::{Binary, Reply, Response, StdResult, SubMsgResult};
738/// #
739/// pub struct SvContract;
740///
741/// ##[sylvia::entry_points]
742/// ##[sylvia::contract]
743/// ##[sv::features(replies)]
744/// impl SvContract {
745///     pub const fn new() -> Self {
746///         Self
747///     }
748///
749///     #[sv::msg(instantiate)]
750///     fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> StdResult<Response> {
751/// #        Ok(Response::new())
752///     }
753/// #
754/// #    #[sv::msg(exec)]
755/// #    fn execute(&self, ctx: ExecCtx) -> StdResult<Response> {
756/// #        Ok(Response::new())
757/// #    }
758/// #
759/// #    #[sv::msg(query)]
760/// #    fn query(&self, ctx: QueryCtx) -> StdResult<Response> {
761/// #        Ok(Response::new())
762/// #    }
763/// #
764/// #    #[sv::msg(migrate)]
765/// #    fn migrate(&self, ctx: MigrateCtx) -> StdResult<Response> {
766/// #        Ok(Response::new())
767/// #    }
768/// #
769/// #    #[sv::msg(reply)]
770/// #    fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> StdResult<Response> {
771/// #        Ok(Response::new())
772/// #    }
773/// #
774/// #    #[sv::msg(sudo)]
775/// #    fn sudo(&self, ctx: SudoCtx) -> StdResult<Response> {
776/// #        Ok(Response::new())
777/// #    }
778/// }
779/// # fn main() {}
780/// ```
781///
782/// ## Generics
783///
784/// `Cosmwasm` entry point has to be implemented with concrete types.
785/// In case your contract uses some generic types you have to specify concrete types
786/// used in their place in the `entry_points` macro attribute `generics`.
787///
788/// ```rust
789/// # use sylvia::ctx::InstantiateCtx;
790/// # use sylvia::cw_std::{Response, StdResult};
791/// # use sylvia::cw_schema::cw_serde;
792/// # use cw_storage_plus::Item;
793/// # use std::marker::PhantomData;
794/// #
795/// # #[cw_serde]
796/// # pub struct SvCustomMsg;
797/// # impl sylvia::cw_std::CustomMsg for SvCustomMsg {}
798/// #
799/// pub struct SvContract<InstantiateT, DataT> {
800///     data: Item<DataT>,
801///     _phantom: PhantomData<InstantiateT>,
802/// }
803///
804/// ##[sylvia::entry_points(generics<SvCustomMsg, SvCustomMsg>)]
805/// ##[sylvia::contract]
806/// impl<InstantiateT, DataT> SvContract<InstantiateT, DataT>
807///     where InstantiateT: sylvia::types::CustomMsg + 'static,
808///         DataT: 'static
809/// {
810///     pub const fn new() -> Self {
811///         Self {
812///             data: Item::new("data"),
813///             _phantom: PhantomData,
814///         }
815///     }
816///
817///     #[sv::msg(instantiate)]
818///     fn instantiate(&self, ctx: InstantiateCtx, instantiate_data: InstantiateT) -> StdResult<Response> {
819/// #        Ok(Response::new())
820///     }
821/// }
822/// # fn main() {}
823/// ```
824///
825#[proc_macro_error]
826#[proc_macro_attribute]
827pub fn entry_points(attr: TokenStream, item: TokenStream) -> TokenStream {
828    entry_points_impl(attr.into(), item.into()).into()
829}
830
831fn entry_points_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
832    fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
833        let input: ItemImpl = parse2(item)?;
834        let args = EntryPointArgs::new(&attr)?;
835        let expanded = EntryPointInput::new(&input, args, attr.span()).process();
836
837        Ok(quote! {
838            #input
839
840            #expanded
841        })
842    }
843
844    inner(attr, item).unwrap_or_else(syn::Error::into_compile_error)
845}
846
847#[cfg(test)]
848mod test {
849    use std::{env, fs};
850
851    use sylvia_runtime_macros::emulate_attribute_expansion_fallible;
852
853    use crate::{contract_impl, interface_impl};
854
855    // Test expanding macros in sylvia crate tests, to calculate generating code coverage
856    #[test]
857    fn sylvia_test_cov() {
858        let mut path = env::current_dir().unwrap();
859        path.push("..");
860        path.push("sylvia");
861        path.push("tests");
862
863        for entry in fs::read_dir(path).unwrap() {
864            let entry = entry.unwrap();
865
866            if entry.file_type().unwrap().is_file() {
867                let file = fs::File::open(entry.path()).unwrap();
868                emulate_attribute_expansion_fallible(file, "interface", interface_impl).unwrap();
869
870                let file = fs::File::open(entry.path()).unwrap();
871                emulate_attribute_expansion_fallible(file, "contract", contract_impl).unwrap();
872            }
873        }
874    }
875}