Skip to main content

sails_rs/gstd/
macros.rs

1/// Computes the Sails hash for a function signature.
2///
3/// Supports both a low-level form with explicit kind and name expression, and a
4/// shorthand form for `command` and `query` functions.
5///
6/// # Examples
7///
8/// ```rust,ignore
9/// let hash = sails_rs::hash_fn!("command" "transfer", (u32, String) -> ());
10/// let hash = sails_rs::hash_fn!(command transfer(u32, String) -> ());
11/// let hash = sails_rs::hash_fn!(query balance_of(u32) -> u128);
12/// ```
13#[macro_export]
14macro_rules! hash_fn {
15    (@raw $kind:expr, $name:expr, ( $( $ty:ty ),* $(,)? ) -> $reply:ty $(| $throws:ty )?) => {{
16        let mut fn_hash = $crate::keccak_const::Keccak256::new();
17        fn_hash = fn_hash.update($kind.as_bytes()).update($name.as_bytes());
18        $( fn_hash = fn_hash.update(&<$ty as $crate::sails_reflect_hash::ReflectHash>::HASH); )*
19        fn_hash = fn_hash.update(b"res").update(&<$reply as $crate::sails_reflect_hash::ReflectHash>::HASH);
20        $( fn_hash = fn_hash.update(b"throws").update(&<$throws as $crate::sails_reflect_hash::ReflectHash>::HASH); )?
21        fn_hash.finalize()
22    }};
23
24    (
25        $kind:literal $name:expr, ( $( $ty:ty ),* $(,)? ) -> $reply:ty $(| $throws:ty )?
26    ) => {
27        $crate::hash_fn!(@raw $kind, $name, ( $( $ty ),* ) -> $reply $(| $throws )?)
28    };
29
30    (
31        command $name:ident ( $( $ty:ty ),* $(,)? ) -> $reply:ty $(| $throws:ty )?
32    ) => {
33        $crate::hash_fn!(@raw "command", stringify!($name), ( $( $ty ),* ) -> $reply $(| $throws )?)
34    };
35
36    (
37        query $name:ident ( $( $ty:ty ),* $(,)? ) -> $reply:ty $(| $throws:ty )?
38    ) => {
39        $crate::hash_fn!(@raw "query", stringify!($name), ( $( $ty ),* ) -> $reply $(| $throws )?)
40    };
41}
42
43/// Evaluates a program constructor call and stores the resulting program in a
44/// mutable static slot.
45///
46/// The constructor arguments are expected to already be bound in the local
47/// scope. `params_struct = ...` is required and is used to convert `.unwrap()`
48/// failures into a structured panic through
49/// [`ok_or_throws!`].
50///
51/// # Examples
52///
53/// ```rust,ignore
54/// program_ctor!(
55///     PROGRAM = MyProgram::new(p1, p2).await,
56///     params_struct = meta_in_program::__NewParams,
57/// );
58/// program_ctor!(
59///     PROGRAM = MyProgram::new_result(p1, p2).await.unwrap(),
60///     params_struct = meta_in_program::__NewResultParams,
61/// );
62/// ```
63#[macro_export]
64macro_rules! program_ctor {
65    (
66        $prg:ident = $($call:ident)::+ ( $( $arg:ident ),* $(,)? ) .await .unwrap(),
67        params_struct = $params_ty:ty $(,)?
68    ) => {{
69        $crate::gstd::message_loop(async move {
70            $crate::program_ctor!(
71                @store $prg = $crate::ok_or_throws!($($call)::+ ( $( $arg, )* ).await, $params_ty, 0)
72            );
73        });
74    }};
75    (
76        $prg:ident = $($call:ident)::+ ( $( $arg:ident ),* $(,)? ) .await,
77        params_struct = $params_ty:ty $(,)?
78    ) => {{
79        $crate::gstd::message_loop(async move {
80            $crate::program_ctor!(@store $prg = $($call)::+ ( $( $arg, )* ).await);
81        });
82    }};
83    (
84        $prg:ident = $($call:ident)::+ ( $( $arg:ident ),* $(,)? ) .unwrap(),
85        params_struct = $params_ty:ty $(,)?
86    ) => {{
87        $crate::program_ctor!(
88            @store $prg = $crate::ok_or_throws!($($call)::+ ( $( $arg, )* ), $params_ty, 0)
89        );
90    }};
91    (
92        $prg:ident = $($call:ident)::+ ( $( $arg:ident ),* $(,)? ),
93        params_struct = $params_ty:ty $(,)?
94    ) => {{
95        $crate::program_ctor!(@store $prg = $($call)::+ ( $( $arg, )* ));
96    }};
97    (@store $prg:ident = $program:expr) => {{
98        unsafe {
99            $prg = Some($program);
100        }
101    }};
102}
103
104/// Declares an invocation params struct together with its metadata and
105/// [`crate::gstd::InvocationIo`] implementation.
106///
107/// By default the generated struct derives [`crate::Decode`] and
108/// [`crate::TypeInfo`], using `$crate::scale_codec` and `$crate::type_info`
109/// as the derive crate paths. Pass `decode = false` for ABI-only metadata
110/// structs that should not derive [`crate::Decode`].
111///
112/// # Examples
113///
114/// ```rust,ignore
115/// // Default: derives Decode + TypeInfo
116/// sails_rs::invocation_io!(
117///     pub struct __FooParams {
118///         pub(super) a: u32,
119///         pub(super) b: String,
120///     },
121///     entry_id = 0,
122/// );
123///
124/// // ABI-only: derives TypeInfo only (no Decode)
125/// sails_rs::invocation_io!(
126///     pub struct __BarParams {
127///         pub(super) addr: Address,
128///     },
129///     entry_id = 1,
130///     decode = false,
131/// );
132/// ```
133#[macro_export]
134macro_rules! invocation_io {
135    (
136        $struct_vis:vis struct $params_struct:ident {
137            $( $field_vis:vis $field:ident : $ty:ty ),* $(,)?
138        },
139        entry_id = $entry_id:expr $(,)?
140    ) => {
141        $crate::invocation_io!(
142            $struct_vis struct $params_struct {
143                $( $field_vis $field : $ty, )*
144            },
145            interface_id = $crate::meta::InterfaceId::zero(),
146            entry_id = $entry_id,
147        );
148    };
149
150    (
151        $struct_vis:vis struct $params_struct:ident {
152            $( $field_vis:vis $field:ident : $ty:ty ),* $(,)?
153        },
154        interface_id = $interface_id:expr,
155        entry_id = $entry_id:expr $(,)?
156    ) => {
157        $crate::invocation_io! {
158            @with_decode
159            $struct_vis struct $params_struct {
160                $( $field_vis $field : $ty, )*
161            },
162            interface_id = $interface_id,
163            entry_id = $entry_id,
164        }
165    };
166
167    (
168        $struct_vis:vis struct $params_struct:ident {
169            $( $field_vis:vis $field:ident : $ty:ty ),* $(,)?
170        },
171        entry_id = $entry_id:expr,
172        decode = false $(,)?
173    ) => {
174        $crate::invocation_io!(
175            $struct_vis struct $params_struct {
176                $( $field_vis $field : $ty, )*
177            },
178            interface_id = $crate::meta::InterfaceId::zero(),
179            entry_id = $entry_id,
180            decode = false,
181        );
182    };
183
184    (
185        $struct_vis:vis struct $params_struct:ident {
186            $( $field_vis:vis $field:ident : $ty:ty ),* $(,)?
187        },
188        interface_id = $interface_id:expr,
189        entry_id = $entry_id:expr,
190        decode = false $(,)?
191    ) => {
192        $crate::invocation_io! {
193            @without_decode
194            $struct_vis struct $params_struct {
195                $( $field_vis $field : $ty, )*
196            },
197            interface_id = $interface_id,
198            entry_id = $entry_id,
199        }
200    };
201
202    (@with_decode
203        $struct_vis:vis struct $params_struct:ident {
204            $( $field_vis:vis $field:ident : $ty:ty, )*
205        },
206        interface_id = $interface_id:expr,
207        entry_id = $entry_id:expr $(,)?
208    ) => {
209        #[derive($crate::Decode, $crate::TypeInfo)]
210        #[codec(crate = $crate::scale_codec)]
211        #[type_info(crate = $crate::type_info)]
212        $struct_vis struct $params_struct {
213            $( $field_vis $field: $ty, )*
214        }
215
216        $crate::invocation_io! {
217            @impl_common
218            $params_struct,
219            interface_id = $interface_id,
220            entry_id = $entry_id,
221        }
222    };
223
224    (@without_decode
225        $struct_vis:vis struct $params_struct:ident {
226            $( $field_vis:vis $field:ident : $ty:ty, )*
227        },
228        interface_id = $interface_id:expr,
229        entry_id = $entry_id:expr $(,)?
230    ) => {
231        #[derive($crate::TypeInfo)]
232        #[type_info(crate = $crate::type_info)]
233        $struct_vis struct $params_struct {
234            $( $field_vis $field: $ty, )*
235        }
236
237        $crate::invocation_io! {
238            @impl_common
239            $params_struct,
240            interface_id = $interface_id,
241            entry_id = $entry_id,
242        }
243    };
244
245    (@impl_common
246        $params_struct:ident,
247        interface_id = $interface_id:expr,
248        entry_id = $entry_id:expr $(,)?
249    ) => {
250        impl $crate::meta::Identifiable for $params_struct {
251            const INTERFACE_ID: $crate::meta::InterfaceId = $interface_id;
252        }
253
254        impl $crate::meta::MethodMeta for $params_struct {
255            const ENTRY_ID: u16 = $entry_id;
256        }
257
258        impl $crate::gstd::InvocationIo for $params_struct {
259            type Params = Self;
260        }
261    };
262}
263
264/// Dispatches a service exposure, selecting the async or sync handling path at
265/// runtime and replying with the encoded result.
266///
267/// The service exposure value must already be bound in the local scope.
268#[macro_export]
269macro_rules! service_route_dispatch {
270    (
271        $svc:ident : $service_ty:ty,
272        interface_id = $interface_id:expr,
273        entry_id = $entry_id:expr,
274        input = $input:expr $(,)?
275    ) => {{
276        let is_async = <$service_ty as $crate::gstd::services::Service>::Exposure::check_asyncness(
277            $interface_id,
278            $entry_id,
279        )
280        .unwrap_or_else(|| $crate::gstd::unknown_input_panic("Unknown call", &[]));
281
282        if is_async {
283            $crate::gstd::message_loop(async move {
284                $svc.try_handle_async($interface_id, $entry_id, $input, |encoded_result, value| {
285                    $crate::gstd::msg::reply_bytes(encoded_result, value)
286                        .expect("Failed to send output");
287                })
288                .await
289                .unwrap_or_else(|| $crate::gstd::unknown_input_panic("Unknown request", &[]));
290            });
291        } else {
292            $svc.try_handle($interface_id, $entry_id, $input, |encoded_result, value| {
293                $crate::gstd::msg::reply_bytes(encoded_result, value)
294                    .expect("Failed to send output");
295            })
296            .unwrap_or_else(|| $crate::gstd::unknown_input_panic("Unknown request", &[]));
297        }
298    }};
299}
300
301/// Unwraps a `Result` or converts its error into a structured panic payload.
302///
303/// The payload is encoded with the provided invocation params type and route
304/// index, then sent through [`crate::gstd::Syscall::panic`] when it fits within
305/// [`crate::gstd::MAX_PANIC_PAYLOAD_SIZE`].
306#[macro_export]
307macro_rules! ok_or_throws {
308    ($res: expr, $param: ty, $route_idx: expr) => {
309        match $res {
310            Ok(r) => r,
311            Err(e) => {
312                let encoded = $crate::gstd::encode_invocation_payload::<$param, _, _>(
313                    &e,
314                    $route_idx,
315                    |encoded| encoded.to_vec(),
316                );
317                if encoded.len() <= $crate::gstd::MAX_PANIC_PAYLOAD_SIZE {
318                    $crate::gstd::Syscall::panic(&encoded)
319                } else {
320                    ::core::panic!("Error payload is too large to panic")
321                }
322            }
323        }
324    };
325}
326
327#[cfg(test)]
328mod tests {
329    use crate::prelude::*;
330
331    #[allow(dead_code)]
332    struct MyProgram {
333        p1: u32,
334        p2: String,
335    }
336
337    static mut PROGRAM: Option<MyProgram> = None;
338
339    impl MyProgram {
340        pub async fn new(p1: u32, p2: String) -> Self {
341            Self { p1, p2 }
342        }
343
344        pub async fn new_result(p1: u32, p2: String) -> Result<Self, String> {
345            Ok(Self { p1, p2 })
346        }
347    }
348
349    mod meta_in_program {
350        invocation_io!(pub struct __NewParams {}, entry_id = 0,);
351        invocation_io!(pub struct __NewResultParams {}, entry_id = 0,);
352    }
353
354    #[test]
355    fn program_ctor_async() {
356        let p1 = 42_u32;
357        let p2 = String::from("payload");
358
359        let _compile = || {
360            program_ctor!(
361                PROGRAM = MyProgram::new(p1, p2).await,
362                params_struct = meta_in_program::__NewParams,
363            );
364        };
365    }
366
367    #[test]
368    fn program_ctor_async_unwrap_result() {
369        let p1 = 42_u32;
370        let p2 = String::from("payload");
371
372        let _compile = || {
373            program_ctor!(
374                PROGRAM = MyProgram::new_result(p1, p2).await.unwrap(),
375                params_struct = meta_in_program::__NewResultParams,
376            );
377        };
378    }
379
380    #[test]
381    fn invocation_io_macro_compiles() {
382        invocation_io!(
383            pub struct __FooParams {
384                pub(super) a: u32,
385                pub(super) b: String,
386            },
387            interface_id = crate::meta::InterfaceId::zero(),
388            entry_id = 7,
389        );
390
391        let _params: <__FooParams as crate::gstd::InvocationIo>::Params = __FooParams {
392            a: 1,
393            b: String::from("ok"),
394        };
395        let _ = (_params.a, &_params.b);
396
397        assert_eq!(<__FooParams as crate::meta::MethodMeta>::ENTRY_ID, 7);
398    }
399
400    #[test]
401    fn invocation_io_macro_defaults_interface_id_to_zero() {
402        invocation_io!(
403            pub struct __BarParams {
404                pub(super) a: u32,
405            },
406            entry_id = 3,
407        );
408
409        let params = __BarParams { a: 1 };
410        let _ = params.a;
411
412        assert_eq!(
413            <__BarParams as crate::meta::Identifiable>::INTERFACE_ID,
414            crate::meta::InterfaceId::zero()
415        );
416        assert_eq!(<__BarParams as crate::meta::MethodMeta>::ENTRY_ID, 3);
417    }
418
419    #[test]
420    fn service_route_dispatch_macro_compiles() {
421        struct DummyService;
422        struct DummyExposure;
423
424        impl crate::gstd::services::Service for DummyService {
425            type Exposure = DummyExposure;
426
427            fn expose(self, _route_idx: u8) -> Self::Exposure {
428                DummyExposure
429            }
430        }
431
432        impl crate::gstd::services::Exposure for DummyExposure {
433            fn interface_id() -> crate::meta::InterfaceId {
434                crate::meta::InterfaceId::zero()
435            }
436
437            fn route_idx(&self) -> u8 {
438                0
439            }
440
441            fn check_asyncness(
442                _interface_id: crate::meta::InterfaceId,
443                _entry_id: u16,
444            ) -> Option<bool> {
445                Some(false)
446            }
447        }
448
449        impl DummyExposure {
450            async fn try_handle_async(
451                self,
452                _interface_id: crate::meta::InterfaceId,
453                _entry_id: u16,
454                _input: &[u8],
455                _result_handler: impl FnOnce(&[u8], u128),
456            ) -> Option<()> {
457                Some(())
458            }
459
460            fn try_handle(
461                self,
462                _interface_id: crate::meta::InterfaceId,
463                _entry_id: u16,
464                _input: &[u8],
465                _result_handler: impl FnOnce(&[u8], u128),
466            ) -> Option<()> {
467                Some(())
468            }
469        }
470
471        let svc = DummyExposure;
472        let interface_id = crate::meta::InterfaceId::zero();
473        let entry_id = 0u16;
474        let input: &[u8] = &[];
475
476        let _compile = || {
477            service_route_dispatch!(
478                svc: DummyService,
479                interface_id = interface_id,
480                entry_id = entry_id,
481                input = input,
482            );
483        };
484    }
485}