tor_netdoc/derive_common.rs
1//! Common macro elements for deriving parsers and encoders
2
3use derive_deftly::{define_derive_deftly, define_derive_deftly_module};
4
5define_derive_deftly! {
6 /// Defines a constructor struct and method
7 //
8 // TODO maybe move this out of tor-netdoc, to a lower-level dependency
9 ///
10 /// "Constructor" is a more lightweight alternative to the builder pattern.
11 ///
12 /// # Comparison to builders
13 ///
14 /// * Suitable for transparent, rather than opaque, structs.
15 /// * Missing fields during construction are detected at compile-time.
16 /// * Construction is infallible at runtime.
17 /// * Making a previously-required field optional is an API break.
18 ///
19 /// # Input
20 ///
21 /// * `struct Thing`. (enums and unions are not supported.)
22 ///
23 /// * Each field must impl `Default` or be annotated `#[deftly(constructor)]`
24 ///
25 /// * `Thing` must contain `#[doc(hidden)] pub __non_exhaustive: ()`
26 /// rather than being `#[non_exhaustive]`.
27 /// (Because struct literal syntax is not available otherwise.)
28 ///
29 /// # Generated items
30 ///
31 /// * **`pub struct ThingConstructor`**:
32 /// contains all the required (non-optional) fields from `Thing`.
33 /// `ThingConstructor` is `exhaustive`.
34 ///
35 /// * **`fn ThingConstructor::construct(self) -> Thing`**:
36 /// fills in all the default values.
37 ///
38 /// * `impl From<ThingConstructor> for Thing`
39 ///
40 /// # Attributes
41 ///
42 /// ## Field attributes
43 ///
44 /// * **`#[deftly(constructor)]`**:
45 /// Include this field in `ThingConstructor`.
46 /// The caller must provide a value.
47 ///
48 /// * **`#[deftly(constructor(default = EXPR))]`**:
49 /// Instead of `Default::default()`, the default value is EXPR.
50 /// EXPR cannot refer to anything in `ThingConstructor`.
51 // If we want that we would need to invent a feature for it.
52 ///
53 /// # Example
54 ///
55 /// ```
56 /// use derive_deftly::Deftly;
57 /// use tor_netdoc::derive_deftly_template_Constructor;
58 ///
59 /// #[derive(Deftly, PartialEq, Debug)]
60 /// #[derive_deftly(Constructor)]
61 /// #[allow(clippy::exhaustive_structs)]
62 /// pub struct Thing {
63 /// /// Required field
64 /// #[deftly(constructor)]
65 /// pub required: i32,
66 ///
67 /// /// Optional field
68 /// pub optional: Option<i32>,
69 ///
70 /// /// Optional field with fixed default
71 /// #[deftly(constructor(default = 7))]
72 /// pub defaulted: i32,
73 ///
74 /// #[doc(hidden)]
75 /// pub __non_exhaustive: (),
76 /// }
77 ///
78 /// let thing = Thing {
79 /// optional: Some(23),
80 /// ..ThingConstructor {
81 /// required: 12,
82 /// }.construct()
83 /// };
84 ///
85 /// assert_eq!(
86 /// thing,
87 /// Thing {
88 /// required: 12,
89 /// optional: Some(23),
90 /// defaulted: 7,
91 /// __non_exhaustive: (),
92 /// }
93 /// );
94 /// ```
95 ///
96 /// # Note
97 export Constructor for struct, meta_quoted rigorous, beta_deftly:
98
99 ${define CONSTRUCTOR_NAME $<$tname Constructor>}
100 ${define CONSTRUCTOR $<$ttype Constructor>}
101
102 ${defcond F_DEFAULT_EXPR fmeta(constructor(default))}
103 ${defcond F_DEFAULT_TRAIT not(fmeta(constructor))}
104 ${defcond F_REQUIRED not(any(F_DEFAULT_EXPR, F_DEFAULT_TRAIT))}
105
106 ${for fields {
107 ${loop_exactly_1 "need a `__non_exhaustive` field (instead of `#[non_exhaustive]`"}
108 ${when all(
109 approx_equal($fname, __non_exhaustive),
110 approx_equal({}, ${tattrs non_exhaustive}),
111 )}
112 }}
113
114 $/// Constructor (required fields) for `$tname`
115 $///
116 $/// See [`$tname`].
117 $///
118 $/// This constructor struct contains precisely the required fields.
119 $/// You can make a `$tname` out of it with [`.construct()`]($CONSTRUCTOR_NAME::construct),
120 $/// or the `From` impl,
121 $/// and use the result as a basis for further modifications.
122 $///
123 $/// # Example
124 $///
125 $/// ```rust,ignore
126 $/// let ${snake_case $tname} = $tname {
127 ${for fields { ${when any(fmeta(constructor(default)), not(fmeta(constructor)))}
128 $/// $fname: /* optional field value */,
129 }}
130 $/// ..$CONSTRUCTOR_NAME {
131 ${for fields { ${when not(any(fmeta(constructor(default)), not(fmeta(constructor))))}
132 $/// $fname: /* required field value */,
133 }}
134 $/// }.construct()
135 $/// };
136 $/// ```
137 #[allow(clippy::exhaustive_structs)]
138 $tvis struct $CONSTRUCTOR_NAME<$tdefgens> where $twheres { $(
139 ${when F_REQUIRED}
140
141 ${fattrs doc}
142 $fdefvis $fname: $ftype,
143 ) }
144
145 impl<$tgens> $CONSTRUCTOR where $twheres {
146 $/// Construct a minimal `$tname`
147 $///
148 $/// In the returned [`$tname`],
149 $/// optional fields all get the default values.
150 $tvis fn construct(self) -> $ttype {
151 $tname { $(
152 $fname: ${select1
153 F_REQUIRED {
154 self.$fname
155 }
156 F_DEFAULT_TRAIT {
157 <$ftype as ::std::default::Default>::default()
158 }
159 F_DEFAULT_EXPR {
160 ${fmeta(constructor(default)) as expr}
161 }
162 },
163 ) }
164 }
165 }
166
167 impl<$tgens> From<$CONSTRUCTOR> for $ttype where $twheres {
168 fn from(constructor: $CONSTRUCTOR) -> $ttype {
169 constructor.construct()
170 }
171 }
172}
173
174define_derive_deftly! {
175 /// Derive all parsing and encoding for a fixed, constant, string
176 ///
177 /// Usually, use the more-cooked [`define_constant_string!`] macro instead.
178 ///
179 /// # Input
180 ///
181 /// Typically, a unit struct.
182 /// (Can be applied to any ZST struct that implements `Default`.)
183 ///
184 /// # Required attribute
185 ///
186 /// * `#[deftly(constant_string = EXPR))]` where `EXPR` is a `&str` expression.
187 ///
188 /// # Generated items
189 ///
190 /// Implementations of [`FromStr`, `Display`, and `NormalItemArgument`].
191 /// Therefore also [`ItemArgument`](crate::encode::ItemArgument)
192 /// and [`ItemArgumentParseable`](crate::parse2::ItemArgumentParseable).
193 ///
194 /// # Example
195 ///
196 /// ```
197 /// use derive_deftly::Deftly;
198 /// use tor_netdoc::derive_deftly_template_ConstantString;
199 ///
200 /// #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
201 /// #[derive_deftly(ConstantString)]
202 /// #[deftly(constant_string = "sha3-256")]
203 /// #[allow(clippy::exhaustive_structs)]
204 /// pub struct SharedRandV1AlgName;
205 /// ```
206 // TODO DIRMIRROR / TODO DIRAUTH use ConstantString for routerdesc::OverloadGeneralVersion?
207 // TODO DIRMIRROR / TODO DIRAUTH use ConstantString for routerdesc::AuthCertVersion?
208 export ConstantString for struct, beta_deftly, meta_quoted retain:
209
210 // Bind `constant`. Ensures the type is as expected.
211 ${define LET_CONSTANT {
212 let constant: &str = ${tmeta(constant_string) as expr};
213 }}
214
215 ${define FMT { ::std::fmt} }
216 ${define ERR { $crate::ExpectedConstantString } }
217
218 impl<$tgens> $FMT::Display for $ttype where $twheres {
219 fn fmt(&self, f: &mut $FMT::Formatter) -> $FMT::Result {
220 $LET_CONSTANT
221 $FMT::Display::fmt(constant, f)
222 }
223 }
224
225 impl<$tgens> ::std::str::FromStr for $ttype where $twheres {
226 type Err = $ERR;
227
228 fn from_str(s: &str) -> ::std::result::Result<Self, $ERR> {
229 $LET_CONSTANT
230 if s == constant {
231 Ok(::std::default::Default::default())
232 } else {
233 Err($ERR {
234 got: s.to_string(),
235 expected: constant,
236 })
237 }
238 }
239 }
240
241 impl<$tgens> $crate::NormalItemArgument for $ttype where $twheres {}
242}
243
244/// Define a ZST struct for a fixed, constant, argument string in a netdoc item
245///
246/// Convenience macro to define a unit struct, derive many traits,
247/// and derive
248/// [`ConstantString`](derive_deftly_template_ConstantString).
249///
250/// # Example
251///
252/// ```
253/// use tor_netdoc::define_constant_string;
254/// use tor_netdoc::derive_deftly_template_ConstantString; // sadly, needed for Reasons
255///
256/// define_constant_string! {
257/// /// The shared random algorithm name for the V1 shared random protocol
258/// ///
259/// /// This is a constant string, since the version defines the hash algorithm.
260/// SharedRandV1AlgName = "sha3-256";
261/// }
262///
263/// assert_eq!("sha3-256".parse::<SharedRandV1AlgName>(), Ok(SharedRandV1AlgName));
264/// assert_eq!(SharedRandV1AlgName.to_string(), "sha3-256");
265/// ```
266///
267/// # Input
268///
269/// ```rust,ignore
270/// define_constant_string! {
271/// #[ATTRIBUTES...]
272/// TYPE_NAME = STRING_LITERAL;
273/// }
274/// ```
275///
276/// # Generated items
277///
278/// ```rust,ignore
279/// #[ATTRIBUTES...]
280/// pub struct TYPE_NAME;
281/// ```
282///
283/// Implementations of `Default`, `Debug`, `Clone`, `Copy`,
284/// `Eq`, `PartialEq`, `Ord`, `PartialOrd`, `Hash`.
285///
286/// Implementations of [`FromStr`, `Display`, and `NormalItemArgument`].
287/// Therefore also [`ItemArgument`](crate::encode::ItemArgument)
288/// and [`ItemArgumentParseable`](crate::parse2::ItemArgumentParseable).
289//
290// This macro exists mostly to encapsulate this astonishing list of traits to derive!
291#[macro_export]
292macro_rules! define_constant_string {
293 {
294 $( #[ $($attr:tt)* ] )*
295 $name:ident = $string:expr;
296 } => {
297 $( #[ $($attr)* ] )*
298 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
299 #[derive($crate::derive_deftly::Deftly)]
300 #[derive_deftly(ConstantString)]
301 #[deftly(constant_string = ($string))]
302 #[allow(clippy::exhaustive_structs)]
303 pub struct $name;
304 };
305}
306
307/// Macro to help check that netdoc items in a derive input are in the right order
308///
309/// Used only by the `NetdocParseable` derive-deftly macro.
310#[doc(hidden)]
311#[macro_export]
312macro_rules! netdoc_ordering_check {
313 { } => { compile_error!("netdoc must have an intro item so cannot be empty"); };
314
315 // When we have K0 P0 K1 P1 ...
316 // * Check that P0 and P1 have a consistent ordr
317 // * Continue with K1 P1 ...
318 // So we check each consecutive pair of fields.
319 { $k0:ident $f0:ident $k1:ident $f1:ident $($rest:tt)* } => {
320 $crate::netdoc_ordering_check! { <=? $k0 $k1 $f1 }
321 $crate::netdoc_ordering_check! { $k1 $f1 $($rest)* }
322 };
323 { $k0:ident $f0:ident } => {}; // finished
324
325 // Individual ordering checks for K0 <=? K1
326 //
327 // We write out each of the allowed this-kind next-kind combinations:
328 { <=? intro $any:ident $f1:ident } => {};
329 { <=? normal normal $f1:ident } => {};
330 { <=? normal subdoc $f1:ident } => {};
331 { <=? subdoc subdoc $f1:ident } => {};
332 // Not in the allowed list, must be an error:
333 { <=? $k0:ident $k1:ident $f1:ident } => {
334 compile_error!(concat!(
335 "in netdoc, ", stringify!($k1)," field ", stringify!($f1),
336 " may not come after ", stringify!($k0),
337 ));
338 };
339}
340
341define_derive_deftly_module! {
342 /// Common definitions for any netdoc derives
343 NetdocDeriveAnyCommon beta_deftly:
344
345 // Emit an eprintln with deftly(netdoc(debug)), just so that we don't get surprises
346 // where someone leaves a (debug) in where it's not implemented, and we later implement it.
347 ${define EMIT_DEBUG_PLACEHOLDER {
348 ${if tmeta(netdoc(debug)) {
349 use std::io::Write as _;
350
351 // This messing about with std::io::stderr() mirrors netdoc_parseable_derive_debug.
352 // (We could use eprintln! #[test] captures eprintln! but not io::stderr.)
353 writeln!(
354 std::io::stderr().lock(),
355 ${concat "#[deftly(netdoc(debug))] applied to " $tname},
356 ).expect("write to stderr failed");
357 }}
358 }}
359 ${define DOC_DEBUG_PLACEHOLDER {
360 /// * **`#[deftly(netdoc(debug))]`**:
361 ///
362 /// Currently implemented only as a placeholder
363 ///
364 /// The generated implementation may in future generate copious debug output
365 /// to the program's stderr when it is run.
366 /// Do not enable in production!
367 }}
368}
369
370define_derive_deftly_module! {
371 /// Common definitions for derives of structs containing items
372 ///
373 /// Used by `NetdocParseable`, `NetdocParseableFields`,
374 /// `NetdocEncodable` and `NetdocEncodableFields`.
375 ///
376 /// Importing template must define these:
377 ///
378 /// * **`F_INTRO`**, **`F_SUBDOC`**, **`F_SIGNATURE`**
379 /// conditions for the fundamental field kinds which aren't supported everywhere.
380 ///
381 /// The `F_FLATTEN`, `F_SKIP`, `F_NORMAL` field type conditions are defined here.
382 ///
383 /// Importer must also import `NetdocDeriveAnyCommon`.
384 //
385 // We have the call sites import the other modules, rather than using them here, because:
386 // - This avoids the human reader having to chase breadcrumbs
387 // to find out what a particular template is using.
388 // - The dependency graph is not a tree, so some things would be included twice
389 // and derive-deftly cannot deduplicate them.
390 NetdocSomeItemsDeriveCommon beta_deftly:
391
392 // Is this field `flatten`?
393 ${defcond F_FLATTEN fmeta(netdoc(flatten))}
394 // Is this field `skip`?
395 ${defcond F_SKIP fmeta(netdoc(skip))}
396 // Is this field normal (non-structural)?
397 ${defcond F_NORMAL not(any(F_SIGNATURE, F_INTRO, F_FLATTEN, F_SUBDOC, F_SKIP))}
398
399 // Field keyword as `&str`
400 ${define F_KEYWORD_STR { ${concat
401 ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) {
402 ${if F_INTRO {
403 ${error "#[deftly(netdoc(subdoc))] (flatten) and (skip) not supported for intro items"}
404 } else {
405 // Sub-documents and flattened fields have their keywords inside;
406 // if we ask for the field-based keyword name for one of those then that's a bug.
407 ${error "internal error, subdoc or skip KeywordRef"}
408 }}
409 }}
410 ${fmeta(netdoc(keyword)) as str,
411 default ${concat ${kebab_case $fname}}}
412 }}}
413 // Field keyword as `&str` for debugging and error reporting
414 ${define F_KEYWORD_REPORT ${concat
415 ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) { $fname }
416 else { $F_KEYWORD_STR }}
417 }}
418 // Field keyword as `KeywordRef`
419 ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
420}
421
422define_derive_deftly_module! {
423 /// Common definitions for derives of whole network documents
424 ///
425 /// Used by `NetdocParseable` and `NetdocEncodable`.
426 ///
427 /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
428 NetdocEntireDeriveCommon beta_deftly:
429
430 // Predicate for the toplevel
431 ${defcond T_SIGNATURES false}
432
433 // Predicates for the field kinds
434 ${defcond F_INTRO approx_equal($findex, 0)}
435 ${defcond F_SUBDOC fmeta(netdoc(subdoc))}
436 ${defcond F_SIGNATURE T_SIGNATURES} // signatures section documents have only signature fields
437
438 // compile-time check that fields are in the right order in the struct
439 ${define FIELD_ORDERING_CHECK {
440 ${if not(T_SIGNATURES) { // signatures structs have only signature fields
441 netdoc_ordering_check! {
442 $(
443 ${when not(F_SKIP)}
444
445 ${select1
446 F_INTRO { intro }
447 F_NORMAL { normal }
448 F_FLATTEN { normal }
449 F_SUBDOC { subdoc }
450 }
451 $fname
452 )
453 }
454 }}
455 }}
456}
457
458define_derive_deftly_module! {
459 /// Common definitions for derives of flattenable network document fields structs
460 ///
461 /// Used by `NetdocParseableFields` and `NetdocEncodableFields`.
462 ///
463 /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
464 NetdocFieldsDeriveCommon beta_deftly:
465
466 // Predicates for the field kinds, used by NetdocSomeItemsDeriveCommon etc.
467 ${defcond F_INTRO false}
468 ${defcond F_SUBDOC false}
469 ${defcond F_SIGNATURE false}
470
471 ${define DOC_NETDOC_FIELDS_DERIVE_SUPPORTED {
472 /// * The input struct can contain only normal non-structural items
473 /// (so it's not a sub-document with an intro item).
474 /// * The only attributes supported are the field attributes
475 /// `#[deftly(netdoc(keyword = STR))]`
476 /// `#[deftly(netdoc(default))]`
477 /// `#[deftly(netdoc(single_arg))]`
478 /// `#[deftly(netdoc(with = MODULE))]`
479 /// `#[deftly(netdoc(flatten))]`
480 /// `#[deftly(netdoc(skip))]`
481 }}
482}
483
484define_derive_deftly_module! {
485 /// Common definitions for derives of network document item value structs
486 ///
487 /// Used by `ItemValueParseable` and `ItemValueEncodable`.
488 ///
489 /// Importer must also import `NetdocDeriveAnyCommon`.
490 NetdocItemDeriveCommon beta_deftly:
491
492 ${defcond F_REST fmeta(netdoc(rest))}
493 ${defcond F_OBJECT fmeta(netdoc(object))}
494 ${defcond F_SKIP fmeta(netdoc(skip))}
495 ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SKIP))}
496
497 ${defcond T_IS_SIGNATURE tmeta(netdoc(signature))}
498}