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, 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        #[cfg_attr(feature = "serde", serde(default))] // TODO: link epee field not serializing oddity
295        pub struct $t {
296            $(
297                $( #[$field_attr] )*
298                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
299                pub $field: $field_type,
300            )*
301        }
302
303        #[cfg(feature = "epee")]
304        ::cuprate_epee_encoding::epee_object! {
305            $t,
306            $(
307                $field: $field_type
308                $(as $field_as)?
309                $(= $field_default)?,
310            )*
311        }
312    };
313
314    //------------------------------------------------------------------------------
315    // This version of the macro expects a `Request` base type from [`crate::bases`].
316    (
317        // Any doc comments, derives, etc.
318        $( #[$attr:meta] )*
319        // The response base type => actual name of the struct
320        $base:ty => $t:ident {
321            // And any fields.
322            // See [`define_request`] for docs, this does the same thing.
323            $(
324                $( #[$field_attr:meta] )*
325                $field:ident: $field_type:ty
326                $(as $field_as:ty)?
327                $(= $field_default:expr_2021, $field_default_string:literal)?,
328            )*
329        }
330    ) => {
331        $( #[$attr] )*
332        #[cfg_attr(feature = "serde", serde(default))] // TODO: link epee field not serializing oddity
333        pub struct $t {
334            #[cfg_attr(feature = "serde", serde(flatten))]
335            pub base: $base,
336
337            $(
338                $( #[$field_attr] )*
339                $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])?
340                pub $field: $field_type,
341            )*
342        }
343
344        #[cfg(feature = "epee")]
345        ::cuprate_epee_encoding::epee_object! {
346            $t,
347            $(
348                $field: $field_type
349                $(as $field_as)?
350                $(= $field_default)?,
351            )*
352            !flatten: base: $base,
353        }
354    };
355}
356pub(crate) use define_response;
357
358//---------------------------------------------------------------------------------------------------- define_request_and_response_doc
359/// Generate documentation for the types generated
360/// by the [`define_request_and_response`] macro.
361///
362/// See it for more info on inputs.
363macro_rules! define_request_and_response_doc {
364    (
365        // This labels the last `[request]` or `[response]`
366        // hyperlink in documentation. Input is either:
367        // - "request"
368        // - "response"
369        //
370        // Remember this is linking to the _other_ type,
371        // so if defining a `Request` type, input should
372        // be "response".
373        $request_or_response:literal => $request_or_response_type:ident,
374
375        $monero_daemon_rpc_doc_link:ident,
376        $monero_code_commit:literal,
377        $monero_code_filename:ident,
378        $monero_code_filename_extension:ident,
379        $monero_code_line_start:literal,
380        $monero_code_line_end:literal,
381    ) => {
382        concat!(
383            "",
384            "[Definition](",
385            "https://github.com/monero-project/monero/blob/",
386            $monero_code_commit,
387            "/src/rpc/",
388            stringify!($monero_code_filename),
389            ".",
390            stringify!($monero_code_filename_extension),
391            "#L",
392            stringify!($monero_code_line_start),
393            "-L",
394            stringify!($monero_code_line_end),
395            "), [documentation](",
396            "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html",
397            "#",
398            stringify!($monero_daemon_rpc_doc_link),
399            "), [",
400            $request_or_response,
401            "](",
402            stringify!($request_or_response_type),
403            ")."
404        )
405    };
406}
407pub(crate) use define_request_and_response_doc;
408
409//---------------------------------------------------------------------------------------------------- Macro
410/// Output a string link to `monerod` source code.
411macro_rules! monero_definition_link {
412    (
413        $commit:literal, // Git commit hash
414        $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h`
415        $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0`
416    ) => {
417        concat!(
418            "[Definition](https://github.com/monero-project/monero/blob/",
419            $commit,
420            "/src/",
421            $file_path,
422            "#L",
423            stringify!($start),
424            $(
425                "-L",
426                stringify!($end),
427            )?
428            ")."
429        )
430    };
431}
432pub(crate) use monero_definition_link;