Skip to main content

sails_macros/
lib.rs

1//! Procedural macros for the `Sails` framework.
2
3use proc_macro::TokenStream;
4use proc_macro_error::proc_macro_error;
5
6/// Generates code for turning a Rust impl block into a Sails service
7/// based on a set of public methods of the block. See
8/// [documentation](https://github.com/gear-tech/sails?tab=readme-ov-file#application)
9/// for details.
10///
11/// The macro can be customized with the following arguments:
12/// - `crate` - specifies path to the `sails-rs` crate allowing the latter
13///             to be imported with a different name, for example, when the
14///             `sails-rs` create is re-exported from another crate.
15/// - `events` - specifies a Rust enum type denoting events that the service can emit.
16///              See [documentation](https://github.com/gear-tech/sails?tab=readme-ov-file#events)
17///              for details.
18/// - `extends` - specifies a list of other services the service extends using the mixin pattern.
19///               See [documentation](https://github.com/gear-tech/sails?tab=readme-ov-file#service-extending-mixins)
20///               for details.
21///
22/// # Examples
23///
24/// ```rust
25/// mod my_service {
26///     use sails_rs::{export, service, prelude::*};
27///
28///     #[event]
29///     #[derive(parity_scale_codec::Encode, type_info::TypeInfo, ReflectHash)]
30///     #[reflect_hash(crate = sails_rs)]
31///     pub enum MyServiceEvents {
32///         SomethingDone,
33///     }
34///
35///     pub struct MyService;
36///
37///     #[service(events = MyServiceEvents)]
38///     impl MyService {
39///         #[export]
40///         pub fn do_something(&mut self) -> u32 {
41///             self.emit_event(MyServiceEvents::SomethingDone).unwrap();
42///             0
43///         }
44///
45///         #[export]
46///         pub fn get_something(&self) -> u32 {
47///             0
48///         }
49///     }
50/// }
51/// ```
52#[proc_macro_error]
53#[proc_macro_attribute]
54pub fn service(args: TokenStream, impl_tokens: TokenStream) -> TokenStream {
55    sails_macros_core::gservice(args.into(), impl_tokens.into()).into()
56}
57
58/// Generates code for turning a Rust impl block into a Sails program
59/// based on a set of public methods of the block. See
60/// [documentation](https://github.com/gear-tech/sails?tab=readme-ov-file#application)
61/// for details.
62///
63/// The macro can be customized with the following arguments:
64/// - `crate` - specifies path to the `sails-rs` crate allowing the latter
65///             to be imported with a different name, for example, when the
66///             `sails-rs` create is re-exported from another crate.
67/// - `handle_signal` - specifies a path to a function that will be called
68///                     after standard signal handling provided by the `gstd` crate.
69/// - `payable` - specifies that the program can accept transfers of value.
70///
71/// The macro also accepts a `handle_reply` attribute that can be used to specify a function
72/// that will handle replies. This function should be defined within the program and accepts `&self`.
73/// The function will be called automatically when a reply is received.
74///
75/// # Examples
76///
77/// ```rust
78/// mod my_program {
79///     use sails_rs::program;
80///
81///     pub struct MyProgram;
82///
83///     #[program(payable)]
84///     impl MyProgram {
85///         pub fn default() -> Self {
86///             Self
87///         }
88///
89///         pub fn from_seed(_seed: u32) -> Self {
90///             Self
91///         }
92///
93///         #[handle_reply]
94///         fn inspect_reply(&self) {
95///             // Handle reply here
96///         }
97///     }
98/// }
99/// ```
100#[proc_macro_error]
101#[proc_macro_attribute]
102pub fn program(args: TokenStream, impl_tokens: TokenStream) -> TokenStream {
103    sails_macros_core::gprogram(args.into(), impl_tokens.into()).into()
104}
105
106/// Customizes how a service/program method is exposed based on specified arguments.
107///
108/// The attribute accepts two optional arguments:
109/// - `route` - Defines  a custom route for the method.
110///    By default, every exposed service method is accessible via a route derived from its name,
111///    converted to PascalCase. This argument allows you to override the default route with a
112///    string of your choice.
113/// - `unwrap_result` - Indicates that the method's `Result<T, E>` return value should be unwrapped.
114///   If specified, the method will panic if the result is an `Err`.
115///
116/// # Examples
117///
118/// The following example demonstrates the use of the `export` attribute applied to the `do_something` method.
119/// - The `route` argument customizes the route to "Something" (convertered to PascalCase).
120/// - The `unwrap_result` argument ensures that the method's result is unwrapped, causing it to panic
121///   with the message "Something went wrong" if the result is an `Err`.
122///
123/// ```rust
124/// mod my_service {
125///    use sails_rs::{export, service};
126///
127///    struct MyService;
128///
129///    #[service]
130///    impl MyService {
131///        #[export(route = "something", unwrap_result)]
132///        pub fn do_something(&mut self) -> Result<u32, String> {
133///            Err("Something went wrong".to_string())
134///        }
135///    }
136/// }
137/// ```
138#[proc_macro_error]
139#[proc_macro_attribute]
140pub fn export(args: TokenStream, impl_item_fn_tokens: TokenStream) -> TokenStream {
141    sails_macros_core::export(args.into(), impl_item_fn_tokens.into()).into()
142}
143
144/// Defines event for using within Gear and Ethereum ecosystem.
145///
146/// Trait `SailsEvent` provides a uniform interface to encode an event into a tuple
147/// of variant name and data payload.
148///
149/// Trait `EthEvent` provides a uniform interface to convert an event into the topics and data payload
150/// that are used to emit logs in the Ethereum Virtual Machine (EVM). The logs generated by the EVM
151/// consist of:
152///
153/// - **Topics:** An array of 32-byte values. The first topic is always the keccak256 hash of the event
154///   signature, while the remaining topics correspond to indexed fields. For dynamic types (as determined
155///   by `<T as alloy_sol_types::SolType>::IS_DYNAMIC`), the ABI-encoded value is hashed before being stored.
156///   For static types, the ABI-encoded value is left-padded with zeros to 32 bytes.
157/// - **Data:** A byte array containing the ABI-encoded non-indexed fields of the event, encoded as a tuple.
158///
159/// This is intended to be used with the `#[sails_rs::event]` procedural macro, which automatically
160/// implements the trait for your enum-based event definitions.
161///
162///
163/// # Arguments
164///
165/// - `scale` — implement only `SailsEvent` (SCALE/Gear transport). The Rust client generator
166///   and JS client generator will include this event; the Solidity generator will exclude it.
167/// - `ethabi` — implement only `EthEvent` (Ethereum ABI transport, requires `ethexe` feature).
168///   The Solidity generator will include this event; the Rust and JS client generators will exclude it.
169/// - `scale, ethabi` — implement both traits explicitly (same as the default when both flags are omitted).
170/// - `crate = <path>` — override the path to the `sails-rs` crate (defaults to `sails_rs`).
171///
172/// When only one transport flag is given, every variant in the IDL receives a `@codec: scale` or
173/// `@codec: ethabi` annotation so downstream generators can filter accordingly.
174///
175/// ## Ethabi-only events and `#[sails_type]`
176///
177/// `#[sails_type]` always derives `Encode`, `Decode`, `TypeInfo`, and `ReflectHash`. If your
178/// ethabi-only event enum contains fields that are ABI-compatible but not SCALE-compatible (e.g.
179/// `alloy_primitives::Address`), do **not** combine it with `#[sails_type]`. Instead, derive
180/// `TypeInfo` and `ReflectHash` manually:
181///
182/// ```rust,ignore
183/// #[sails_rs::event(ethabi)]
184/// #[derive(sails_rs::type_info::TypeInfo, sails_rs::ReflectHash)]
185/// #[type_info(crate = sails_rs::type_info)]
186/// #[reflect_hash(crate = sails_rs)]
187/// pub enum Events {
188///     Something(sails_rs::alloy_primitives::Address),
189/// }
190/// ```
191///
192/// # Examples
193///
194/// Given an event definition:
195///
196/// ```rust,ignore
197/// #[sails_rs::event]
198/// #[derive(sails_rs::Encode, sails_rs::TypeInfo)]
199/// #[codec(crate = sails_rs::scale_codec)]
200/// #[type_info(crate = sails_rs::type_info)]
201/// pub enum Events {
202///     MyEvent {
203///         #[indexed]
204///         sender: uint128,
205///         amount: uint128,
206///         note: String,
207///     },
208/// }
209/// ```
210///
211/// Calling the methods:
212///
213/// ```rust,ignore
214/// let event = Events::MyEvent {
215///     sender: 123,
216///     amount: 1000,
217///     note: "Hello, Ethereum".to_owned(),
218/// };
219///
220/// let topics = event.topics();
221/// let data = event.data();
222/// ```
223///
224/// The first topic will be the hash of the event signature (e.g. `"MyEvent(uint128,uint128,String)"`),
225/// and additional topics and the data payload will be computed based on the field attributes.
226///
227/// # Methods
228///
229/// - `topics()`: Returns a vector of 32-byte topics (`alloy_primitives::B256`) for the event.
230/// - `data()`: Returns the ABI-encoded data payload (a `Vec<u8>`) for the non-indexed fields.
231#[proc_macro_error]
232#[proc_macro_attribute]
233pub fn event(args: TokenStream, input: TokenStream) -> TokenStream {
234    sails_macros_core::event(args.into(), input.into()).into()
235}
236
237/// Derives the canonical Sails type bundle: `Encode`, `Decode`, `TypeInfo`,
238/// and `ReflectHash`, together with their `crate =` helper attributes routed
239/// to the `sails_rs` re-exports.
240///
241/// # Arguments
242///
243/// - `crate = <path>` — override the path to the `sails-rs` crate (defaults to
244///   `sails_rs`). Useful when `sails-rs` is re-exported from a parent crate.
245/// - `no_reflect_hash` — omit `ReflectHash` from the derive list and drop the
246///   `reflect_hash` helper attribute. Exists specifically for the IDL v1
247///   client generator, which predates `ReflectHash`.
248///
249/// # Examples
250///
251/// ```rust,ignore
252/// use sails_rs::sails_type;
253///
254/// #[sails_type]
255/// #[derive(PartialEq, Clone, Debug)]
256/// pub struct MyType {
257///     pub a: u32,
258///     pub b: String,
259/// }
260///
261/// #[sails_type(crate = my_alias)]
262/// pub enum MyEnum { A, B }
263///
264/// #[sails_type(no_reflect_hash)]
265/// pub struct LegacyType { pub a: u32 }
266/// ```
267///
268/// # Ordering with `#[event]`
269///
270/// When applied to an event enum, `#[event]` **must** appear _before_ `#[sails_type]`:
271///
272/// ```rust,ignore
273/// #[sails_rs::event]  // correct: sorts variants first
274/// #[sails_rs::sails_type]
275/// pub enum MyEvents { Transferred, Approved }
276/// ```
277///
278/// Placing `#[sails_type]` first is a compile error. `#[event]` must sort variants
279/// alphabetically before `ReflectHash` (added by `#[sails_type]`) runs its derive;
280/// the reversed order would produce a hash inconsistent with the IDL.
281#[proc_macro_error]
282#[proc_macro_attribute]
283pub fn sails_type(args: TokenStream, item: TokenStream) -> TokenStream {
284    sails_macros_core::sails_type(args.into(), item.into()).into()
285}