1
//! Common macro elements for deriving parsers and encoders
2

            
3
use derive_deftly::{define_derive_deftly, define_derive_deftly_module};
4

            
5
define_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)] __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::manual_non_exhaustive)]
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
    ///     __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, 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
    #[doc = ${concat "Constructor (required fields) for " $tname}]
107
    ///
108
    #[doc = ${concat "See [`" $tname "`]."}]
109
    ///
110
    /// This constructor struct contains precisely the required fields.
111
    #[doc = ${concat "You can make a `" $tname
112
              "` out of it with [`.construct()`](" $CONSTRUCTOR_NAME "::construct),"}]
113
    /// or the `From` impl,
114
    /// and use the result as a basis for further modifications.
115
    ///
116
    /// # Example
117
    ///
118
    /// ```rust,ignore
119
    #[doc = ${concat "let " ${snake_case $tname} " = " $tname "{"}]
120
    #[doc = ${concat ${for fields {
121
        ${if any(fmeta(constructor(default)), not(fmeta(constructor))) {
122
            "    " $fname ": /* optional field value */,\n"
123
        } else {
124
        }}
125
    }}}]
126
    #[doc = ${concat "    .." $CONSTRUCTOR_NAME " {"}]
127
    #[doc = ${concat ${for fields {
128
        ${if not(any(fmeta(constructor(default)), not(fmeta(constructor)))) {
129
            "        " $fname ": /* required field value */,\n"
130
        } else {
131
        }}
132
    }}}]
133
    #[doc = ${concat "    }.construct()"}]
134
    #[doc = ${concat "};"}]
135
    /// ```
136
    #[allow(clippy::exhaustive_structs)]
137
    $tvis struct $CONSTRUCTOR_NAME<$tdefgens> where $twheres { $(
138
        ${when F_REQUIRED}
139

            
140
        ${fattrs doc}
141
        $fdefvis $fname: $ftype,
142
    ) }
143

            
144
    impl<$tgens> $CONSTRUCTOR where $twheres {
145
        #[doc = ${concat "Construct a minimal [`" $tname "`]"}]
146
        ///
147
        #[doc = ${concat "In the returned " $tname ","}]
148
        /// optional fields all get the default values.
149
        $tvis fn construct(self) -> $ttype {
150
            $tname { $(
151
                $fname: ${select1
152
                    F_REQUIRED {
153
                        self.$fname
154
                    }
155
                    F_DEFAULT_TRAIT {
156
                        ::std::default::Default::default()
157
                    }
158
                    F_DEFAULT_EXPR {
159
                        ${fmeta(constructor(default)) as expr}
160
                    }
161
                },
162
            ) }
163
        }
164
    }
165

            
166
    impl<$tgens> From<$CONSTRUCTOR> for $ttype where $twheres {
167
        fn from(constructor: $CONSTRUCTOR) -> $ttype {
168
            constructor.construct()
169
        }
170
    }
171
}
172

            
173
/// Macro to help check that netdoc items in a derive input are in the right order
174
///
175
/// Used only by the `NetdocParseable` derive-deftly macro.
176
#[doc(hidden)]
177
#[macro_export]
178
macro_rules! netdoc_ordering_check {
179
    { } => { compile_error!("netdoc must have an intro item so cannot be empty"); };
180

            
181
    // When we have   K0 P0 K1 P1 ...
182
    //   * Check that P0 and P1 have a consistent ordr
183
    //   * Continue with   K1 P1 ...
184
    // So we check each consecutive pair of fields.
185
    { $k0:ident $f0:ident $k1:ident $f1:ident $($rest:tt)* } => {
186
        $crate::netdoc_ordering_check! { <=? $k0 $k1 $f1 }
187
        $crate::netdoc_ordering_check! { $k1 $f1 $($rest)* }
188
    };
189
    { $k0:ident $f0:ident } => {}; // finished
190

            
191
    // Individual ordering checks for K0 <=? K1
192
    //
193
    // We write out each of the allowed this-kind next-kind combinations:
194
    { <=? intro     $any:ident $f1:ident } => {};
195
    { <=? normal    normal     $f1:ident } => {};
196
    { <=? normal    subdoc     $f1:ident } => {};
197
    { <=? subdoc    subdoc     $f1:ident } => {};
198
    // Not in the allowed list, must be an error:
199
    { <=? $k0:ident $k1:ident  $f1:ident } => {
200
        compile_error!(concat!(
201
            "in netdoc, ", stringify!($k1)," field ", stringify!($f1),
202
            " may not come after ", stringify!($k0),
203
        ));
204
    };
205
}
206

            
207
define_derive_deftly_module! {
208
    /// Common definitions for any netdoc derives
209
    NetdocDeriveAnyCommon beta_deftly:
210

            
211
    // Emit an eprintln with deftly(netdoc(debug)), just so that we don't get surprises
212
    // where someone leaves a (debug) in where it's not implemented, and we later implement it.
213
    ${define EMIT_DEBUG_PLACEHOLDER {
214
        ${if tmeta(netdoc(debug)) {
215
            // This messing about with std::io::stderr() mirrors netdoc_parseable_derive_debug.
216
            // (We could use eprintln! #[test] captures eprintln! but not io::stderr.)
217
            writeln!(
218
                std::io::stderr().lock(),
219
                ${concat "#[deftly(netdoc(debug))] applied to " $tname},
220
            ).expect("write to stderr failed");
221
        }}
222
    }}
223
    ${define DOC_DEBUG_PLACEHOLDER {
224
        /// * **`#[deftly(netdoc(debug))]`**:
225
        ///
226
        ///   Currently implemented only as a placeholde
227
        ///
228
        ///   The generated implementation may in future generate copious debug output
229
        ///   to the program's stderr when it is run.
230
        ///   Do not enable in production!
231
    }}
232
}
233

            
234
define_derive_deftly_module! {
235
    /// Common definitions for derives of structs containing items
236
    ///
237
    /// Used by `NetdocParseable`, `NetdocParseableFields`,
238
    /// `NetdocEncodable` and `NetdocEncodableFields`.
239
    ///
240
    /// Importing template must define these:
241
    ///
242
    ///  * **`F_INTRO`**, **`F_SUBDOC`**, **`F_SIGNATURE`**
243
    ///    conditions for the fundamental field kinds which aren't supported everywhere.
244
    ///
245
    ///    The `F_FLATTEN`, `F_SKIP`, `F_NORMAL` field type conditions are defined here.
246
    ///
247
    /// Importer must also import `NetdocDeriveAnyCommon`.
248
    //
249
    // We have the call sites import the other modules, rather than using them here, because:
250
    //  - This avoids the human reader having to chase breadcrumbs
251
    //    to find out what a particular template is using.
252
    //  - The dependency graph is not a tree, so some things would be included twice
253
    //    and derive-deftly cannot deduplicate them.
254
    NetdocSomeItemsDeriveCommon beta_deftly:
255

            
256
    // Is this field `flatten`?
257
    ${defcond F_FLATTEN fmeta(netdoc(flatten))}
258
    // Is this field `skip`?
259
    ${defcond F_SKIP fmeta(netdoc(skip))}
260
    // Is this field normal (non-structural)?
261
    ${defcond F_NORMAL not(any(F_SIGNATURE, F_INTRO, F_FLATTEN, F_SUBDOC, F_SKIP))}
262

            
263
    // Field keyword as `&str`
264
    ${define F_KEYWORD_STR { ${concat
265
        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) {
266
          ${if F_INTRO {
267
            ${error "#[deftly(netdoc(subdoc))] (flatten) and (skip) not supported for intro items"}
268
          } else {
269
            // Sub-documents and flattened fields have their keywords inside;
270
            // if we ask for the field-based keyword name for one of those then that's a bug.
271
            ${error "internal error, subdoc or skip KeywordRef"}
272
          }}
273
        }}
274
        ${fmeta(netdoc(keyword)) as str,
275
          default ${concat ${kebab_case $fname}}}
276
    }}}
277
    // Field keyword as `&str` for debugging and error reporting
278
    ${define F_KEYWORD_REPORT ${concat
279
        ${if any(F_FLATTEN, F_SUBDOC, F_SKIP) { $fname }
280
             else { $F_KEYWORD_STR }}
281
    }}
282
    // Field keyword as `KeywordRef`
283
    ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
284
}
285

            
286
define_derive_deftly_module! {
287
    /// Common definitions for derives of whole network documents
288
    ///
289
    /// Used by `NetdocParseable` and `NetdocEncodable`.
290
    ///
291
    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
292
    NetdocEntireDeriveCommon beta_deftly:
293

            
294
    // Predicate for the toplevel
295
    ${defcond T_SIGNATURES tmeta(netdoc(signatures))}
296

            
297
    // Predicates for the field kinds
298
    ${defcond F_INTRO all(not(T_SIGNATURES), approx_equal($findex, 0))}
299
    ${defcond F_SUBDOC fmeta(netdoc(subdoc))}
300
    ${defcond F_SIGNATURE T_SIGNATURES} // signatures section documents have only signature fields
301

            
302
    // compile-time check that fields are in the right order in the struct
303
    ${define FIELD_ORDERING_CHECK {
304
        ${if not(T_SIGNATURES) { // signatures structs have only signature fields
305
          netdoc_ordering_check! {
306
            $(
307
                ${when not(F_SKIP)}
308

            
309
                ${select1
310
                  F_INTRO     { intro     }
311
                  F_NORMAL    { normal    }
312
                  F_FLATTEN   { normal    }
313
                  F_SUBDOC    { subdoc    }
314
                }
315
                $fname
316
            )
317
          }
318
        }}
319
    }}
320
}
321

            
322
define_derive_deftly_module! {
323
    /// Common definitions for derives of flattenable network document fields structs
324
    ///
325
    /// Used by `NetdocParseableFields` and `NetdocEncodableFields`.
326
    ///
327
    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
328
    NetdocFieldsDeriveCommon beta_deftly:
329

            
330
    // Predicates for the field kinds, used by NetdocSomeItemsDeriveCommon etc.
331
    ${defcond F_INTRO false}
332
    ${defcond F_SUBDOC false}
333
    ${defcond F_SIGNATURE false}
334

            
335
    ${define DOC_NETDOC_FIELDS_DERIVE_SUPPORTED {
336
        ///  * The input struct can contain only normal non-structural items
337
        ///    (so it's not a sub-document with an intro item).
338
        ///  * The only attributes supported are the field attributes
339
        ///    `#[deftly(netdoc(keyword = STR))]`
340
        ///    `#[deftly(netdoc(default))]`
341
        ///    `#[deftly(netdoc(single_arg))]`
342
        ///    `#[deftly(netdoc(with = "MODULE"))]`
343
        ///    `#[deftly(netdoc(flatten))]`
344
        ///    `#[deftly(netdoc(skip))]`
345
    }}
346
}
347

            
348
define_derive_deftly_module! {
349
    /// Common definitions for derives of network document item value structs
350
    ///
351
    /// Used by `ItemValueParseable` and `ItemValueEncodable`.
352
    ///
353
    /// Importer must also import `NetdocDeriveAnyCommon`.
354
    NetdocItemDeriveCommon beta_deftly:
355

            
356
    ${defcond F_REST fmeta(netdoc(rest))}
357
    ${defcond F_OBJECT fmeta(netdoc(object))}
358
    ${defcond F_SIG_HASH fmeta(netdoc(sig_hash))}
359
    ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SIG_HASH))}
360

            
361
    ${defcond T_IS_SIGNATURE not(approx_equal(${for fields { ${when F_SIG_HASH} 1 }}, {}))}
362
}