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;