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;