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:literal =>
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, clippy::empty_structs_with_brackets, 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            #[allow(clippy::empty_structs_with_brackets)]
111            $( #[$request_type_attr] )*
112            [<$type_name Request>] $(($restricted $(, $empty)?))? {
113                $(
114                    $( #[$request_field_attr] )*
115                    $request_field: $request_field_type
116                    $(as $request_field_type_as)?
117                    $(= $request_field_type_default, $request_field_type_default_string)?,
118                )*
119            }
120        }
121
122        $crate::macros::define_response! {
123            #[allow(dead_code, missing_docs, clippy::empty_structs_with_brackets, reason = "inside a macro")]
124            #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125            #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
126            #[doc = $crate::macros::define_request_and_response_doc!(
127                "request" => [<$type_name Request>],
128                $monero_daemon_rpc_doc_link,
129                $monero_code_commit,
130                $monero_code_filename,
131                $monero_code_filename_extension,
132                $monero_code_line_start,
133                $monero_code_line_end,
134            )]
135            ///
136            $( #[$type_attr] )*
137            ///
138            $( #[$response_type_attr] )*
139            $response_base_type => [<$type_name Response>] {
140                $(
141                    $( #[$response_field_attr] )*
142                    $response_field: $response_field_type
143                    $(as $response_field_type_as)?
144                    $(= $response_field_type_default, $response_field_type_default_string)?,
145                )*
146            }
147        }
148    }};
149}
150pub(crate) use define_request_and_response;
151
152//---------------------------------------------------------------------------------------------------- impl_rpc_call
153/// Implement [`crate::RpcCall`] and [`crate::RpcCallValue`] on request types.
154///
155/// Input for this is:
156/// `$REQUEST_TYPE restricted empty`
157/// where `restricted` and `empty` are the idents themselves.
158/// The implementation for [`crate::RpcCall`] will change
159/// depending if they exist or not.
160macro_rules! impl_rpc_call {
161    // Restricted and empty RPC calls.
162    ($t:ident, restricted, empty) => {
163        impl $crate::RpcCall for $t {
164            const IS_RESTRICTED: bool = true;
165            const IS_EMPTY: bool = true;
166        }
167
168        impl From<()> for $t {
169            fn from(_: ()) -> Self {
170                Self {}
171            }
172        }
173
174        impl From<$t> for () {
175            fn from(_: $t) -> Self {}
176        }
177    };
178
179    // Empty RPC calls.
180    ($t:ident, empty) => {
181        impl $crate::RpcCall for $t {
182            const IS_RESTRICTED: bool = false;
183            const IS_EMPTY: bool = true;
184        }
185
186        impl From<()> for $t {
187            fn from(_: ()) -> Self {
188                Self {}
189            }
190        }
191
192        impl From<$t> for () {
193            fn from(_: $t) -> Self {}
194        }
195    };
196
197    // Restricted RPC calls.
198    ($t:ident, restricted) => {
199        impl $crate::RpcCall for $t {
200            const IS_RESTRICTED: bool = true;
201            const IS_EMPTY: bool = false;
202        }
203    };
204
205    // Not restrict or empty RPC calls.
206    ($t:ident) => {
207        impl $crate::RpcCall for $t {
208            const IS_RESTRICTED: bool = false;
209            const IS_EMPTY: bool = false;
210        }
211    };
212}
213pub(crate) use impl_rpc_call;
214
215//---------------------------------------------------------------------------------------------------- define_request
216/// Define a request type.
217///
218/// This is only used in [`define_request_and_response`], see it for docs.
219macro_rules! define_request {
220    //------------------------------------------------------------------------------
221    // This branch will generate a type alias to `()` if only given `{}` as input.
222    (
223        // Any doc comments, derives, etc.
224        $( #[$attr:meta] )*
225        // The response type.
226        $t:ident $(($restricted:ident $(, $empty:ident)?))? {
227            // And any fields.
228            $(
229                $( #[$field_attr:meta] )* // field attributes
230                // field_name: FieldType
231                $field:ident: $field_type:ty
232                $(as $field_as:ty)?
233                $(= $field_default:expr_2021, $field_default_string:literal)?,
234                // The $field_default is an optional extra token that represents
235                // a default value to pass to [`cuprate_epee_encoding::epee_object`],
236                // see it for usage.
237            )*
238        }
239    ) => {
240        #[allow(dead_code, missing_docs, clippy::empty_structs_with_brackets, reason = "inside a macro")]
241        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
242        #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
243        $( #[$attr] )*
244        pub struct $t {
245            $(
246                $( #[$field_attr] )*
247                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
248                pub $field: $field_type,
249            )*
250        }
251
252        $crate::macros::impl_rpc_call!($t $(, $restricted $(, $empty)?)?);
253
254        #[cfg(feature = "epee")]
255        ::cuprate_epee_encoding::epee_object! {
256            $t,
257            $(
258                $field: $field_type
259                $(as $field_as)?
260                $(= $field_default)?,
261            )*
262        }
263    };
264}
265pub(crate) use define_request;
266
267//---------------------------------------------------------------------------------------------------- define_response
268/// Define a response type.
269///
270/// This is used in [`define_request_and_response`], see it for docs.
271macro_rules! define_response {
272    //------------------------------------------------------------------------------
273    // This version of the macro expects the literal ident
274    // `Response` => $response_type_name.
275    //
276    // It will create a `struct` that _doesn't_ use a base from [`crate::base`],
277    // for example, [`crate::json::BannedResponse`] doesn't use a base, so it
278    // uses this branch.
279    (
280        // Any doc comments, derives, etc.
281        $( #[$attr:meta] )*
282        // The response type.
283        Response => $t:ident {
284            // And any fields.
285            // See [`define_request`] for docs, this does the same thing.
286            $(
287                $( #[$field_attr:meta] )*
288                $field:ident: $field_type:ty
289                $(as $field_as:ty)?
290                $(= $field_default:expr_2021, $field_default_string:literal)?,
291            )*
292        }
293    ) => {
294        $( #[$attr] )*
295        #[cfg_attr(feature = "serde", serde(default))] // TODO: link epee field not serializing oddity
296        pub struct $t {
297            $(
298                $( #[$field_attr] )*
299                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
300                pub $field: $field_type,
301            )*
302        }
303
304        #[cfg(feature = "epee")]
305        ::cuprate_epee_encoding::epee_object! {
306            $t,
307            $(
308                $field: $field_type
309                $(as $field_as)?
310                $(= $field_default)?,
311            )*
312        }
313    };
314
315    //------------------------------------------------------------------------------
316    // This version of the macro expects a `Request` base type from [`crate::bases`].
317    (
318        // Any doc comments, derives, etc.
319        $( #[$attr:meta] )*
320        // The response base type => actual name of the struct
321        $base:ty => $t:ident {
322            // And any fields.
323            // See [`define_request`] for docs, this does the same thing.
324            $(
325                $( #[$field_attr:meta] )*
326                $field:ident: $field_type:ty
327                $(as $field_as:ty)?
328                $(= $field_default:expr_2021, $field_default_string:literal)?,
329            )*
330        }
331    ) => {
332        $( #[$attr] )*
333        #[cfg_attr(feature = "serde", serde(default))] // TODO: link epee field not serializing oddity
334        pub struct $t {
335            #[cfg_attr(feature = "serde", serde(flatten))]
336            pub base: $base,
337
338            $(
339                $( #[$field_attr] )*
340                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
341                pub $field: $field_type,
342            )*
343        }
344
345        #[cfg(feature = "epee")]
346        ::cuprate_epee_encoding::epee_object! {
347            $t,
348            $(
349                $field: $field_type
350                $(as $field_as)?
351                $(= $field_default)?,
352            )*
353            !flatten: base: $base,
354        }
355    };
356}
357pub(crate) use define_response;
358
359//---------------------------------------------------------------------------------------------------- define_request_and_response_doc
360/// Generate documentation for the types generated
361/// by the [`define_request_and_response`] macro.
362///
363/// See it for more info on inputs.
364macro_rules! define_request_and_response_doc {
365    (
366        // This labels the last `[request]` or `[response]`
367        // hyperlink in documentation. Input is either:
368        // - "request"
369        // - "response"
370        //
371        // Remember this is linking to the _other_ type,
372        // so if defining a `Request` type, input should
373        // be "response".
374        $request_or_response:literal => $request_or_response_type:ident,
375
376        $monero_daemon_rpc_doc_link:ident,
377        $monero_code_commit:literal,
378        $monero_code_filename:ident,
379        $monero_code_filename_extension:ident,
380        $monero_code_line_start:literal,
381        $monero_code_line_end:literal,
382    ) => {
383        concat!(
384            "",
385            "[Definition](",
386            "https://github.com/monero-project/monero/blob/",
387            $monero_code_commit,
388            "/src/rpc/",
389            stringify!($monero_code_filename),
390            ".",
391            stringify!($monero_code_filename_extension),
392            "#L",
393            stringify!($monero_code_line_start),
394            "-L",
395            stringify!($monero_code_line_end),
396            "), [documentation](",
397            "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html",
398            "#",
399            stringify!($monero_daemon_rpc_doc_link),
400            "), [",
401            $request_or_response,
402            "](",
403            stringify!($request_or_response_type),
404            ")."
405        )
406    };
407}
408pub(crate) use define_request_and_response_doc;
409
410//---------------------------------------------------------------------------------------------------- Macro
411/// Output a string link to `monerod` source code.
412macro_rules! monero_definition_link {
413    (
414        $commit:literal, // Git commit hash
415        $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h`
416        $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0`
417    ) => {
418        concat!(
419            "[Definition](https://github.com/monero-project/monero/blob/",
420            $commit,
421            "/src/",
422            $file_path,
423            "#L",
424            stringify!($start),
425            $(
426                "-L",
427                stringify!($end),
428            )?
429            ")."
430        )
431    };
432}
433pub(crate) use monero_definition_link;