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                $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}