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` should 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    $/// Constructor (required fields) for `$tname`
107    $///
108    $/// See [`$tname`].
109    $///
110    $/// This constructor struct contains precisely the required fields.
111    $/// You can make a `$tname` out of it with [`.construct()`]($CONSTRUCTOR_NAME::construct),
112    $/// or the `From` impl,
113    $/// and use the result as a basis for further modifications.
114    $///
115    $/// # Example
116    $///
117    $/// ```rust,ignore
118    $/// let ${snake_case $tname} = $tname {
119  ${for fields { ${when any(fmeta(constructor(default)), not(fmeta(constructor)))}
120    $///     $fname: /* optional field value */,
121  }}
122    $///     ..$CONSTRUCTOR_NAME {
123  ${for fields { ${when not(any(fmeta(constructor(default)), not(fmeta(constructor))))}
124    $///         $fname: /* required field value */,
125  }}
126    $///     }.construct()
127    $/// };
128    $/// ```
129    #[allow(clippy::exhaustive_structs)]
130    $tvis struct $CONSTRUCTOR_NAME<$tdefgens> where $twheres { $(
131        ${when F_REQUIRED}
132
133        ${fattrs doc}
134        $fdefvis $fname: $ftype,
135    ) }
136
137    impl<$tgens> $CONSTRUCTOR where $twheres {
138        $/// Construct a minimal `$tname`
139        $///
140        $/// In the returned [`$tname`],
141        $/// optional fields all get the default values.
142        $tvis fn construct(self) -> $ttype {
143            $tname { $(
144                $fname: ${select1
145                    F_REQUIRED {
146                        self.$fname
147                    }
148                    F_DEFAULT_TRAIT {
149                        <$ftype as ::std::default::Default>::default()
150                    }
151                    F_DEFAULT_EXPR {
152                        ${fmeta(constructor(default)) as expr}
153                    }
154                },
155            ) }
156        }
157    }
158
159    impl<$tgens> From<$CONSTRUCTOR> for $ttype where $twheres {
160        fn from(constructor: $CONSTRUCTOR) -> $ttype {
161            constructor.construct()
162        }
163    }
164}
165
166/// Macro to help check that netdoc items in a derive input are in the right order
167///
168/// Used only by the `NetdocParseable` derive-deftly macro.
169#[doc(hidden)]
170#[macro_export]
171macro_rules! netdoc_ordering_check {
172    { } => { compile_error!("netdoc must have an intro item so cannot be empty"); };
173
174    // When we have   K0 P0 K1 P1 ...
175    //   * Check that P0 and P1 have a consistent ordr
176    //   * Continue with   K1 P1 ...
177    // So we check each consecutive pair of fields.
178    { $k0:ident $f0:ident $k1:ident $f1:ident $($rest:tt)* } => {
179        $crate::netdoc_ordering_check! { <=? $k0 $k1 $f1 }
180        $crate::netdoc_ordering_check! { $k1 $f1 $($rest)* }
181    };
182    { $k0:ident $f0:ident } => {}; // finished
183
184    // Individual ordering checks for K0 <=? K1
185    //
186    // We write out each of the allowed this-kind next-kind combinations:
187    { <=? intro     $any:ident $f1:ident } => {};
188    { <=? normal    normal     $f1:ident } => {};
189    { <=? normal    subdoc     $f1:ident } => {};
190    { <=? subdoc    subdoc     $f1:ident } => {};
191    // Not in the allowed list, must be an error:
192    { <=? $k0:ident $k1:ident  $f1:ident } => {
193        compile_error!(concat!(
194            "in netdoc, ", stringify!($k1)," field ", stringify!($f1),
195            " may not come after ", stringify!($k0),
196        ));
197    };
198}
199
200define_derive_deftly_module! {
201    /// Common definitions for any netdoc derives
202    NetdocDeriveAnyCommon beta_deftly:
203
204    // Emit an eprintln with deftly(netdoc(debug)), just so that we don't get surprises
205    // where someone leaves a (debug) in where it's not implemented, and we later implement it.
206    ${define EMIT_DEBUG_PLACEHOLDER {
207        ${if tmeta(netdoc(debug)) {
208            use std::io::Write as _;
209
210            // This messing about with std::io::stderr() mirrors netdoc_parseable_derive_debug.
211            // (We could use eprintln! #[test] captures eprintln! but not io::stderr.)
212            writeln!(
213                std::io::stderr().lock(),
214                ${concat "#[deftly(netdoc(debug))] applied to " $tname},
215            ).expect("write to stderr failed");
216        }}
217    }}
218    ${define DOC_DEBUG_PLACEHOLDER {
219        /// * **`#[deftly(netdoc(debug))]`**:
220        ///
221        ///   Currently implemented only as a placeholder
222        ///
223        ///   The generated implementation may in future generate copious debug output
224        ///   to the program's stderr when it is run.
225        ///   Do not enable in production!
226    }}
227}
228
229define_derive_deftly_module! {
230    /// Common definitions for derives of structs containing items
231    ///
232    /// Used by `NetdocParseable`, `NetdocParseableFields`,
233    /// `NetdocEncodable` and `NetdocEncodableFields`.
234    ///
235    /// Importing template must define these:
236    ///
237    ///  * **`F_INTRO`**, **`F_SUBDOC`**, **`F_SIGNATURE`**
238    ///    conditions for the fundamental field kinds which aren't supported everywhere.
239    ///
240    ///    The `F_FLATTEN`, `F_SKIP`, `F_NORMAL` field type conditions are defined here.
241    ///
242    /// Importer must also import `NetdocDeriveAnyCommon`.
243    //
244    // We have the call sites import the other modules, rather than using them here, because:
245    //  - This avoids the human reader having to chase breadcrumbs
246    //    to find out what a particular template is using.
247    //  - The dependency graph is not a tree, so some things would be included twice
248    //    and derive-deftly cannot deduplicate them.
249    NetdocSomeItemsDeriveCommon beta_deftly:
250
251    // Is this field `flatten`?
252    ${defcond F_FLATTEN fmeta(netdoc(flatten))}
253    // Is this field `skip`?
254    ${defcond F_SKIP fmeta(netdoc(skip))}
255    // Is this field normal (non-structural)?
256    ${defcond F_NORMAL not(any(F_SIGNATURE, F_INTRO, F_FLATTEN, F_SUBDOC, F_SKIP))}
257
258    // Field keyword as `&str`
259    ${define F_KEYWORD_STR { ${concat
260        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) {
261          ${if F_INTRO {
262            ${error "#[deftly(netdoc(subdoc))] (flatten) and (skip) not supported for intro items"}
263          } else {
264            // Sub-documents and flattened fields have their keywords inside;
265            // if we ask for the field-based keyword name for one of those then that's a bug.
266            ${error "internal error, subdoc or skip KeywordRef"}
267          }}
268        }}
269        ${fmeta(netdoc(keyword)) as str,
270          default ${concat ${kebab_case $fname}}}
271    }}}
272    // Field keyword as `&str` for debugging and error reporting
273    ${define F_KEYWORD_REPORT ${concat
274        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) { $fname }
275             else { $F_KEYWORD_STR }}
276    }}
277    // Field keyword as `KeywordRef`
278    ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
279}
280
281define_derive_deftly_module! {
282    /// Common definitions for derives of whole network documents
283    ///
284    /// Used by `NetdocParseable` and `NetdocEncodable`.
285    ///
286    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
287    NetdocEntireDeriveCommon beta_deftly:
288
289    // Predicate for the toplevel
290    ${defcond T_SIGNATURES false}
291
292    // Predicates for the field kinds
293    ${defcond F_INTRO approx_equal($findex, 0)}
294    ${defcond F_SUBDOC fmeta(netdoc(subdoc))}
295    ${defcond F_SIGNATURE T_SIGNATURES} // signatures section documents have only signature fields
296
297    // compile-time check that fields are in the right order in the struct
298    ${define FIELD_ORDERING_CHECK {
299        ${if not(T_SIGNATURES) { // signatures structs have only signature fields
300          netdoc_ordering_check! {
301            $(
302                ${when not(F_SKIP)}
303
304                ${select1
305                  F_INTRO     { intro     }
306                  F_NORMAL    { normal    }
307                  F_FLATTEN   { normal    }
308                  F_SUBDOC    { subdoc    }
309                }
310                $fname
311            )
312          }
313        }}
314    }}
315}
316
317define_derive_deftly_module! {
318    /// Common definitions for derives of flattenable network document fields structs
319    ///
320    /// Used by `NetdocParseableFields` and `NetdocEncodableFields`.
321    ///
322    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
323    NetdocFieldsDeriveCommon beta_deftly:
324
325    // Predicates for the field kinds, used by NetdocSomeItemsDeriveCommon etc.
326    ${defcond F_INTRO false}
327    ${defcond F_SUBDOC false}
328    ${defcond F_SIGNATURE false}
329
330    ${define DOC_NETDOC_FIELDS_DERIVE_SUPPORTED {
331        ///  * The input struct can contain only normal non-structural items
332        ///    (so it's not a sub-document with an intro item).
333        ///  * The only attributes supported are the field attributes
334        ///    `#[deftly(netdoc(keyword = STR))]`
335        ///    `#[deftly(netdoc(default))]`
336        ///    `#[deftly(netdoc(single_arg))]`
337        ///    `#[deftly(netdoc(with = MODULE))]`
338        ///    `#[deftly(netdoc(flatten))]`
339        ///    `#[deftly(netdoc(skip))]`
340    }}
341}
342
343define_derive_deftly_module! {
344    /// Common definitions for derives of network document item value structs
345    ///
346    /// Used by `ItemValueParseable` and `ItemValueEncodable`.
347    ///
348    /// Importer must also import `NetdocDeriveAnyCommon`.
349    NetdocItemDeriveCommon beta_deftly:
350
351    ${defcond F_REST fmeta(netdoc(rest))}
352    ${defcond F_OBJECT fmeta(netdoc(object))}
353    ${defcond F_SKIP fmeta(netdoc(skip))}
354    ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SKIP))}
355
356    ${defcond T_IS_SIGNATURE tmeta(netdoc(signature))}
357}