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}