cuprate_rpc_types/
macros.rs

1//! Macros.
2
3//---------------------------------------------------------------------------------------------------- define_request_and_response
4/// A template for generating the RPC request and response `struct`s.
5///
6/// These `struct`s automatically implement:
7/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash`
8/// - `serde::{Serialize, Deserialize}`
9/// - `cuprate_epee_encoding::EpeeObject`
10///
11/// It's best to see the output of this macro via the documentation
12/// of the generated structs via `cargo doc`s to see which parts
13/// generate which docs.
14///
15/// See the [`crate::json`] module for example usage.
16///
17/// # Macro internals
18/// This macro uses:
19/// - [`define_request`]
20/// - [`define_response`]
21/// - [`define_request_and_response_doc`]
22///
23/// # `define_request`
24/// This macro has 2 branches. If the caller provides
25/// `Request {}`, i.e. no fields, it will generate:
26/// ```
27/// pub type Request = ();
28/// ```
29/// If they _did_ specify fields, it will generate:
30/// ```
31/// pub struct Request {/* fields */}
32/// ```
33/// This is because having a bunch of types that are all empty structs
34/// means they are not compatible and it makes it cumbersome for end-users.
35/// Really, they semantically are empty types, so `()` is used.
36///
37/// # `define_response`
38/// This macro has 2 branches. If the caller provides `Response`
39/// it will generate a normal struct with no additional fields.
40///
41/// If the caller provides a base type from [`crate::base`], it will
42/// flatten that into the request type automatically.
43///
44/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}`
45/// would trigger the different branches.
46macro_rules! define_request_and_response {
47    (
48        // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint.
49        $monero_daemon_rpc_doc_link:ident,
50
51        // The commit hash and  `$file.$extension` in which this type is defined in
52        // the Monero codebase in the `rpc/` directory, followed by the specific lines.
53        $monero_code_commit:ident =>
54        $monero_code_filename:ident.
55        $monero_code_filename_extension:ident =>
56        $monero_code_line_start:literal..=
57        $monero_code_line_end:literal,
58
59        // The base `struct` name.
60        // Attributes added here will apply to _both_
61        // request and response types.
62        $( #[$type_attr:meta] )*
63        // After the type name, 2 optional idents are allowed:
64        //
65        // - `restricted`
66        // - `empty`
67        //
68        // These have to be within `()` and will affect the
69        // [`crate::RpcCall`] implementation on the request type.
70        $type_name:ident $(($restricted:ident $(, $empty:ident)?))?,
71
72        // The request type (and any doc comments, derives, etc).
73        $( #[$request_type_attr:meta] )*
74        Request {
75            // And any fields.
76            $(
77                $( #[$request_field_attr:meta] )* // Field attribute.
78                $request_field:ident: $request_field_type:ty // field_name: field type
79                $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization
80                $(= $request_field_type_default:expr_2021, $request_field_type_default_string:literal)?, // (optional) default value
81            )*
82        },
83
84        // The response type (and any doc comments, derives, etc).
85        $( #[$response_type_attr:meta] )*
86        $response_base_type:ty {
87            // And any fields.
88            $(
89                $( #[$response_field_attr:meta] )*
90                $response_field:ident: $response_field_type:ty
91                $(as $response_field_type_as:ty)?
92                $(= $response_field_type_default:expr_2021, $response_field_type_default_string:literal)?,
93            )*
94        }
95    ) => { paste::paste! {
96        $crate::macros::define_request! {
97            #[allow(dead_code, missing_docs, reason = "inside a macro")]
98            #[doc = $crate::macros::define_request_and_response_doc!(
99                "response" => [<$type_name Response>],
100                $monero_daemon_rpc_doc_link,
101                $monero_code_commit,
102                $monero_code_filename,
103                $monero_code_filename_extension,
104                $monero_code_line_start,
105                $monero_code_line_end,
106            )]
107            ///
108            $( #[$type_attr] )*
109            ///
110            $( #[$request_type_attr] )*
111            [<$type_name Request>] $(($restricted $(, $empty)?))? {
112                $(
113                    $( #[$request_field_attr] )*
114                    $request_field: $request_field_type
115                    $(as $request_field_type_as)?
116                    $(= $request_field_type_default, $request_field_type_default_string)?,
117                )*
118            }
119        }
120
121        $crate::macros::define_response! {
122            #[allow(dead_code, missing_docs, reason = "inside a macro")]
123            #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124            #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
125            #[doc = $crate::macros::define_request_and_response_doc!(
126                "request" => [<$type_name Request>],
127                $monero_daemon_rpc_doc_link,
128                $monero_code_commit,
129                $monero_code_filename,
130                $monero_code_filename_extension,
131                $monero_code_line_start,
132                $monero_code_line_end,
133            )]
134            ///
135            $( #[$type_attr] )*
136            ///
137            $( #[$response_type_attr] )*
138            $response_base_type => [<$type_name Response>] {
139                $(
140                    $( #[$response_field_attr] )*
141                    $response_field: $response_field_type
142                    $(as $response_field_type_as)?
143                    $(= $response_field_type_default, $response_field_type_default_string)?,
144                )*
145            }
146        }
147    }};
148}
149pub(crate) use define_request_and_response;
150
151//---------------------------------------------------------------------------------------------------- impl_rpc_call
152/// Implement [`crate::RpcCall`] and [`crate::RpcCallValue`] on request types.
153///
154/// Input for this is:
155/// `$REQUEST_TYPE restricted empty`
156/// where `restricted` and `empty` are the idents themselves.
157/// The implementation for [`crate::RpcCall`] will change
158/// depending if they exist or not.
159macro_rules! impl_rpc_call {
160    // Restricted and empty RPC calls.
161    ($t:ident, restricted, empty) => {
162        impl $crate::RpcCall for $t {
163            const IS_RESTRICTED: bool = true;
164            const IS_EMPTY: bool = true;
165        }
166
167        impl From<()> for $t {
168            fn from(_: ()) -> Self {
169                Self {}
170            }
171        }
172
173        impl From<$t> for () {
174            fn from(_: $t) -> Self {}
175        }
176    };
177
178    // Empty RPC calls.
179    ($t:ident, empty) => {
180        impl $crate::RpcCall for $t {
181            const IS_RESTRICTED: bool = false;
182            const IS_EMPTY: bool = true;
183        }
184
185        impl From<()> for $t {
186            fn from(_: ()) -> Self {
187                Self {}
188            }
189        }
190
191        impl From<$t> for () {
192            fn from(_: $t) -> Self {}
193        }
194    };
195
196    // Restricted RPC calls.
197    ($t:ident, restricted) => {
198        impl $crate::RpcCall for $t {
199            const IS_RESTRICTED: bool = true;
200            const IS_EMPTY: bool = false;
201        }
202    };
203
204    // Not restrict or empty RPC calls.
205    ($t:ident) => {
206        impl $crate::RpcCall for $t {
207            const IS_RESTRICTED: bool = false;
208            const IS_EMPTY: bool = false;
209        }
210    };
211}
212pub(crate) use impl_rpc_call;
213
214//---------------------------------------------------------------------------------------------------- define_request
215/// Define a request type.
216///
217/// This is only used in [`define_request_and_response`], see it for docs.
218macro_rules! define_request {
219    //------------------------------------------------------------------------------
220    // This branch will generate a type alias to `()` if only given `{}` as input.
221    (
222        // Any doc comments, derives, etc.
223        $( #[$attr:meta] )*
224        // The response type.
225        $t:ident $(($restricted:ident $(, $empty:ident)?))? {
226            // And any fields.
227            $(
228                $( #[$field_attr:meta] )* // field attributes
229                // field_name: FieldType
230                $field:ident: $field_type:ty
231                $(as $field_as:ty)?
232                $(= $field_default:expr_2021, $field_default_string:literal)?,
233                // The $field_default is an optional extra token that represents
234                // a default value to pass to [`cuprate_epee_encoding::epee_object`],
235                // see it for usage.
236            )*
237        }
238    ) => {
239        #[allow(dead_code, missing_docs, reason = "inside a macro")]
240        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
241        #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
242        $( #[$attr] )*
243        pub struct $t {
244            $(
245                $( #[$field_attr] )*
246                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
247                pub $field: $field_type,
248            )*
249        }
250
251        $crate::macros::impl_rpc_call!($t $(, $restricted $(, $empty)?)?);
252
253        #[cfg(feature = "epee")]
254        ::cuprate_epee_encoding::epee_object! {
255            $t,
256            $(
257                $field: $field_type
258                $(as $field_as)?
259                $(= $field_default)?,
260            )*
261        }
262    };
263}
264pub(crate) use define_request;
265
266//---------------------------------------------------------------------------------------------------- define_response
267/// Define a response type.
268///
269/// This is used in [`define_request_and_response`], see it for docs.
270macro_rules! define_response {
271    //------------------------------------------------------------------------------
272    // This version of the macro expects the literal ident
273    // `Response` => $response_type_name.
274    //
275    // It will create a `struct` that _doesn't_ use a base from [`crate::base`],
276    // for example, [`crate::json::BannedResponse`] doesn't use a base, so it
277    // uses this branch.
278    (
279        // Any doc comments, derives, etc.
280        $( #[$attr:meta] )*
281        // The response type.
282        Response => $t:ident {
283            // And any fields.
284            // See [`define_request`] for docs, this does the same thing.
285            $(
286                $( #[$field_attr:meta] )*
287                $field:ident: $field_type:ty
288                $(as $field_as:ty)?
289                $(= $field_default:expr_2021, $field_default_string:literal)?,
290            )*
291        }
292    ) => {
293        $( #[$attr] )*
294        pub struct $t {
295            $(
296                $( #[$field_attr] )*
297                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
298                pub $field: $field_type,
299            )*
300        }
301
302        #[cfg(feature = "epee")]
303        ::cuprate_epee_encoding::epee_object! {
304            $t,
305            $(
306                $field: $field_type
307                $(as $field_as)?
308                $(= $field_default)?,
309            )*
310        }
311    };
312
313    //------------------------------------------------------------------------------
314    // This version of the macro expects a `Request` base type from [`crate::bases`].
315    (
316        // Any doc comments, derives, etc.
317        $( #[$attr:meta] )*
318        // The response base type => actual name of the struct
319        $base:ty => $t:ident {
320            // And any fields.
321            // See [`define_request`] for docs, this does the same thing.
322            $(
323                $( #[$field_attr:meta] )*
324                $field:ident: $field_type:ty
325                $(as $field_as:ty)?
326                $(= $field_default:expr_2021, $field_default_string:literal)?,
327            )*
328        }
329    ) => {
330        $( #[$attr] )*
331        pub struct $t {
332            #[cfg_attr(feature = "serde", serde(flatten))]
333            pub base: $base,
334
335            $(
336                $( #[$field_attr] )*
337                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
338                pub $field: $field_type,
339            )*
340        }
341
342        #[cfg(feature = "epee")]
343        ::cuprate_epee_encoding::epee_object! {
344            $t,
345            $(
346                $field: $field_type
347                $(as $field_as)?
348                $(= $field_default)?,
349            )*
350            !flatten: base: $base,
351        }
352    };
353}
354pub(crate) use define_response;
355
356//---------------------------------------------------------------------------------------------------- define_request_and_response_doc
357/// Generate documentation for the types generated
358/// by the [`define_request_and_response`] macro.
359///
360/// See it for more info on inputs.
361macro_rules! define_request_and_response_doc {
362    (
363        // This labels the last `[request]` or `[response]`
364        // hyperlink in documentation. Input is either:
365        // - "request"
366        // - "response"
367        //
368        // Remember this is linking to the _other_ type,
369        // so if defining a `Request` type, input should
370        // be "response".
371        $request_or_response:literal => $request_or_response_type:ident,
372
373        $monero_daemon_rpc_doc_link:ident,
374        $monero_code_commit:ident,
375        $monero_code_filename:ident,
376        $monero_code_filename_extension:ident,
377        $monero_code_line_start:literal,
378        $monero_code_line_end:literal,
379    ) => {
380        concat!(
381            "",
382            "[Definition](",
383            "https://github.com/monero-project/monero/blob/",
384            stringify!($monero_code_commit),
385            "/src/rpc/",
386            stringify!($monero_code_filename),
387            ".",
388            stringify!($monero_code_filename_extension),
389            "#L",
390            stringify!($monero_code_line_start),
391            "-L",
392            stringify!($monero_code_line_end),
393            "), [documentation](",
394            "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html",
395            "#",
396            stringify!($monero_daemon_rpc_doc_link),
397            "), [",
398            $request_or_response,
399            "](",
400            stringify!($request_or_response_type),
401            ")."
402        )
403    };
404}
405pub(crate) use define_request_and_response_doc;
406
407//---------------------------------------------------------------------------------------------------- Macro
408/// Output a string link to `monerod` source code.
409macro_rules! monero_definition_link {
410    (
411        $commit:ident, // Git commit hash
412        $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h`
413        $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0`
414    ) => {
415        concat!(
416            "[Definition](https://github.com/monero-project/monero/blob/",
417            stringify!($commit),
418            "/src/",
419            $file_path,
420            "#L",
421            stringify!($start),
422            $(
423                "-L",
424                stringify!($end),
425            )?
426            ")."
427        )
428    };
429}
430pub(crate) use monero_definition_link;