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;