Skip to main content

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                #[allow(deprecated)]
153                $fname: ${select1
154                    F_REQUIRED {
155                        self.$fname
156                    }
157                    F_DEFAULT_TRAIT {
158                        <$ftype as ::std::default::Default>::default()
159                    }
160                    F_DEFAULT_EXPR {
161                        ${fmeta(constructor(default)) as expr}
162                    }
163                },
164            ) }
165        }
166    }
167
168    impl<$tgens> From<$CONSTRUCTOR> for $ttype where $twheres {
169        fn from(constructor: $CONSTRUCTOR) -> $ttype {
170            constructor.construct()
171        }
172    }
173}
174
175define_derive_deftly! {
176    /// Derive all parsing and encoding for a fixed, constant, string
177    ///
178    /// Usually, use the more-cooked [`define_constant_string!`] macro instead.
179    ///
180    /// # Input
181    ///
182    /// Typically, a unit struct.
183    /// (Can be applied to any ZST struct that implements `Default`.)
184    ///
185    /// # Required attribute
186    ///
187    ///  * `#[deftly(constant_string = EXPR))]` where `EXPR` is a `&str` expression.
188    ///
189    /// # Generated items
190    ///
191    /// Implementations of [`FromStr`, `Display`, and `NormalItemArgument`].
192    /// Therefore also [`ItemArgument`](crate::encode::ItemArgument)
193    /// and [`ItemArgumentParseable`](crate::parse2::ItemArgumentParseable).
194    ///
195    /// # Example
196    ///
197    /// ```
198    /// use derive_deftly::Deftly;
199    /// use tor_netdoc::derive_deftly_template_ConstantString;
200    ///
201    /// #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
202    /// #[derive_deftly(ConstantString)]
203    /// #[deftly(constant_string = "sha3-256")]
204    /// #[allow(clippy::exhaustive_structs)]
205    /// pub struct SharedRandV1AlgName;
206    /// ```
207    // TODO DIRMIRROR / TODO DIRAUTH use ConstantString for routerdesc::OverloadGeneralVersion?
208    // TODO DIRMIRROR / TODO DIRAUTH use ConstantString for routerdesc::AuthCertVersion?
209    export ConstantString for struct, beta_deftly, meta_quoted retain:
210
211    // Bind `constant`.  Ensures the type is as expected.
212    ${define LET_CONSTANT {
213        let constant: &str = ${tmeta(constant_string) as expr};
214    }}
215
216    ${define FMT { ::std::fmt} }
217    ${define ERR { $crate::ExpectedConstantString } }
218
219    impl<$tgens> $FMT::Display for $ttype where $twheres {
220        fn fmt(&self, f: &mut $FMT::Formatter) -> $FMT::Result {
221            $LET_CONSTANT
222            $FMT::Display::fmt(constant, f)
223        }
224    }
225
226    impl<$tgens> ::std::str::FromStr for $ttype where $twheres {
227        type Err = $ERR;
228
229        fn from_str(s: &str) -> ::std::result::Result<Self, $ERR> {
230            $LET_CONSTANT
231            if s == constant {
232                Ok(::std::default::Default::default())
233            } else {
234                Err($ERR {
235                    got: s.to_string(),
236                    expected: constant,
237                })
238            }
239        }
240    }
241
242    impl<$tgens> $crate::NormalItemArgument for $ttype where $twheres {}
243}
244
245/// Define a ZST struct for a fixed, constant, argument string in a netdoc item
246///
247/// Convenience macro to define a unit struct, derive many traits,
248/// and derive
249/// [`ConstantString`](derive_deftly_template_ConstantString).
250///
251/// # Example
252///
253/// ```
254/// use tor_netdoc::define_constant_string;
255/// use tor_netdoc::derive_deftly_template_ConstantString; // sadly, needed for Reasons
256///
257/// define_constant_string! {
258///     /// The shared random algorithm name for the V1 shared random protocol
259///     ///
260///     /// This is a constant string, since the version defines the hash algorithm.
261///     SharedRandV1AlgName = "sha3-256";
262/// }
263///
264/// assert_eq!("sha3-256".parse::<SharedRandV1AlgName>(), Ok(SharedRandV1AlgName));
265/// assert_eq!(SharedRandV1AlgName.to_string(), "sha3-256");
266/// ```
267///
268/// # Input
269///
270/// ```rust,ignore
271/// define_constant_string! {
272///     #[ATTRIBUTES...]
273///     TYPE_NAME = STRING_LITERAL;
274/// }
275/// ```
276///
277/// # Generated items
278///
279/// ```rust,ignore
280/// #[ATTRIBUTES...]
281/// pub struct TYPE_NAME;
282/// ```
283///
284/// Implementations of `Default`, `Debug`, `Clone`, `Copy`,
285/// `Eq`, `PartialEq`, `Ord`, `PartialOrd`, `Hash`.
286///
287/// Implementations of [`FromStr`, `Display`, and `NormalItemArgument`].
288/// Therefore also [`ItemArgument`](crate::encode::ItemArgument)
289/// and [`ItemArgumentParseable`](crate::parse2::ItemArgumentParseable).
290//
291// This macro exists mostly to encapsulate this astonishing list of traits to derive!
292#[macro_export]
293macro_rules! define_constant_string {
294    {
295        $( #[ $($attr:tt)* ] )*
296        $name:ident = $string:expr;
297    } => {
298        $( #[ $($attr)* ] )*
299        #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
300        #[derive($crate::derive_deftly::Deftly)]
301        #[derive_deftly(ConstantString)]
302        #[deftly(constant_string = ($string))]
303        #[allow(clippy::exhaustive_structs)]
304        pub struct $name;
305    };
306}
307
308/// Macro to help check that netdoc items in a derive input are in the right order
309///
310/// Used only by the `NetdocParseable` derive-deftly macro.
311#[doc(hidden)]
312#[macro_export]
313macro_rules! netdoc_ordering_check {
314    { } => { compile_error!("netdoc must have an intro item so cannot be empty"); };
315
316    // When we have   K0 P0 K1 P1 ...
317    //   * Check that P0 and P1 have a consistent ordr
318    //   * Continue with   K1 P1 ...
319    // So we check each consecutive pair of fields.
320    { $k0:ident $f0:ident $k1:ident $f1:ident $($rest:tt)* } => {
321        $crate::netdoc_ordering_check! { <=? $k0 $k1 $f1 }
322        $crate::netdoc_ordering_check! { $k1 $f1 $($rest)* }
323    };
324    { $k0:ident $f0:ident } => {}; // finished
325
326    // Individual ordering checks for K0 <=? K1
327    //
328    // We write out each of the allowed this-kind next-kind combinations:
329    { <=? intro     $any:ident $f1:ident } => {};
330    { <=? normal    normal     $f1:ident } => {};
331    { <=? normal    subdoc     $f1:ident } => {};
332    { <=? subdoc    subdoc     $f1:ident } => {};
333    // Not in the allowed list, must be an error:
334    { <=? $k0:ident $k1:ident  $f1:ident } => {
335        compile_error!(concat!(
336            "in netdoc, ", stringify!($k1)," field ", stringify!($f1),
337            " may not come after ", stringify!($k0),
338        ));
339    };
340}
341
342define_derive_deftly_module! {
343    /// Common definitions for any netdoc derives
344    NetdocDeriveAnyCommon beta_deftly:
345
346    // Emit an eprintln with deftly(netdoc(debug)), just so that we don't get surprises
347    // where someone leaves a (debug) in where it's not implemented, and we later implement it.
348    ${define EMIT_DEBUG_PLACEHOLDER {
349        ${if tmeta(netdoc(debug)) {
350            use std::io::Write as _;
351
352            // This messing about with std::io::stderr() mirrors netdoc_parseable_derive_debug.
353            // (We could use eprintln! #[test] captures eprintln! but not io::stderr.)
354            writeln!(
355                std::io::stderr().lock(),
356                ${concat "#[deftly(netdoc(debug))] applied to " $tname},
357            ).expect("write to stderr failed");
358        }}
359    }}
360    ${define DOC_DEBUG_PLACEHOLDER {
361        /// * **`#[deftly(netdoc(debug))]`**:
362        ///
363        ///   Currently implemented only as a placeholder
364        ///
365        ///   The generated implementation may in future generate copious debug output
366        ///   to the program's stderr when it is run.
367        ///   Do not enable in production!
368    }}
369}
370
371define_derive_deftly_module! {
372    /// Common definitions for derives of structs containing items
373    ///
374    /// Used by `NetdocParseable`, `NetdocParseableFields`,
375    /// `NetdocEncodable` and `NetdocEncodableFields`.
376    ///
377    /// Importing template must define these:
378    ///
379    ///  * **`F_INTRO`**, **`F_SUBDOC`**, **`F_SIGNATURE`**
380    ///    conditions for the fundamental field kinds which aren't supported everywhere.
381    ///
382    ///    The `F_FLATTEN`, `F_SKIP`, `F_NORMAL` field type conditions are defined here.
383    ///
384    /// Importer must also import `NetdocDeriveAnyCommon`.
385    //
386    // We have the call sites import the other modules, rather than using them here, because:
387    //  - This avoids the human reader having to chase breadcrumbs
388    //    to find out what a particular template is using.
389    //  - The dependency graph is not a tree, so some things would be included twice
390    //    and derive-deftly cannot deduplicate them.
391    NetdocSomeItemsDeriveCommon beta_deftly:
392
393    // Is this field `flatten`?
394    ${defcond F_FLATTEN fmeta(netdoc(flatten))}
395    // Is this field `skip`?
396    ${defcond F_SKIP fmeta(netdoc(skip))}
397    // Is this field normal (non-structural)?
398    ${defcond F_NORMAL not(any(F_SIGNATURE, F_INTRO, F_FLATTEN, F_SUBDOC, F_SKIP))}
399
400    // Field keyword as `&str`
401    ${define F_KEYWORD_STR { ${concat
402        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) {
403          ${if F_INTRO {
404            ${error "#[deftly(netdoc(subdoc))] (flatten) and (skip) not supported for intro items"}
405          } else {
406            // Sub-documents and flattened fields have their keywords inside;
407            // if we ask for the field-based keyword name for one of those then that's a bug.
408            ${error "internal error, subdoc or skip KeywordRef"}
409          }}
410        }}
411        ${fmeta(netdoc(keyword)) as str,
412          default ${concat ${kebab_case $fname}}}
413    }}}
414    // Field keyword as `&str` for debugging and error reporting
415    ${define F_KEYWORD_REPORT ${concat
416        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) { $fname }
417             else { $F_KEYWORD_STR }}
418    }}
419    // Field keyword as `KeywordRef`
420    ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
421}
422
423define_derive_deftly_module! {
424    /// Common definitions for derives of whole network documents
425    ///
426    /// Used by `NetdocParseable` and `NetdocEncodable`.
427    ///
428    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
429    NetdocEntireDeriveCommon beta_deftly:
430
431    // Predicate for the toplevel
432    ${defcond T_SIGNATURES false}
433
434    // Predicates for the field kinds
435    ${defcond F_INTRO approx_equal($findex, 0)}
436    ${defcond F_SUBDOC fmeta(netdoc(subdoc))}
437    ${defcond F_SIGNATURE T_SIGNATURES} // signatures section documents have only signature fields
438
439    // compile-time check that fields are in the right order in the struct
440    ${define FIELD_ORDERING_CHECK {
441        ${if not(T_SIGNATURES) { // signatures structs have only signature fields
442          netdoc_ordering_check! {
443            $(
444                ${when not(F_SKIP)}
445
446                ${select1
447                  F_INTRO     { intro     }
448                  F_NORMAL    { normal    }
449                  F_FLATTEN   { normal    }
450                  F_SUBDOC    { subdoc    }
451                }
452                $fname
453            )
454          }
455        }}
456    }}
457}
458
459define_derive_deftly_module! {
460    /// Common definitions for derives of flattenable network document fields structs
461    ///
462    /// Used by `NetdocParseableFields` and `NetdocEncodableFields`.
463    ///
464    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
465    NetdocFieldsDeriveCommon beta_deftly:
466
467    // Predicates for the field kinds, used by NetdocSomeItemsDeriveCommon etc.
468    ${defcond F_INTRO false}
469    ${defcond F_SUBDOC false}
470    ${defcond F_SIGNATURE false}
471
472    ${define DOC_NETDOC_FIELDS_DERIVE_SUPPORTED {
473        ///  * The input struct can contain only normal non-structural items
474        ///    (so it's not a sub-document with an intro item).
475        ///  * The only attributes supported are the field attributes
476        ///    `#[deftly(netdoc(keyword = STR))]`
477        ///    `#[deftly(netdoc(default))]`
478        ///    `#[deftly(netdoc(single_arg))]`
479        ///    `#[deftly(netdoc(with = MODULE))]`
480        ///    `#[deftly(netdoc(flatten))]`
481        ///    `#[deftly(netdoc(skip))]`
482    }}
483}
484
485define_derive_deftly_module! {
486    /// Common definitions for derives of network document item value structs
487    ///
488    /// Used by `ItemValueParseable` and `ItemValueEncodable`.
489    ///
490    /// Importer must also import `NetdocDeriveAnyCommon`.
491    NetdocItemDeriveCommon beta_deftly:
492
493    ${defcond F_REST fmeta(netdoc(rest))}
494    ${defcond F_OBJECT fmeta(netdoc(object))}
495    ${defcond F_SKIP fmeta(netdoc(skip))}
496    ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SKIP))}
497
498    ${defcond T_IS_SIGNATURE tmeta(netdoc(signature))}
499}