tor_netdoc/parse2/derive.rs
1//! Deriving `NetdocParseable`
2
3use super::*;
4
5/// Macro to help check that netdoc items in a derive input are in the right order
6///
7/// Used only by the `NetdocParseable` derive-deftly macro.
8#[doc(hidden)]
9#[macro_export]
10macro_rules! netdoc_ordering_check {
11 { } => { compile_error!("netdoc must have an intro item so cannot be empty"); };
12
13 // When we have K0 P0 K1 P1 ...
14 // * Check that P0 and P1 have a consistent ordr
15 // * Continue with K1 P1 ...
16 // So we check each consecutive pair of fields.
17 { $k0:ident $f0:ident $k1:ident $f1:ident $($rest:tt)* } => {
18 $crate::netdoc_ordering_check! { <=? $k0 $k1 $f1 }
19 $crate::netdoc_ordering_check! { $k1 $f1 $($rest)* }
20 };
21 { $k0:ident $f0:ident } => {}; // finished
22
23 // Individual ordering checks for K0 <=? K1
24 //
25 // We write out each of the allowed this-kind next-kind combinations:
26 { <=? intro $any:ident $f1:ident } => {};
27 { <=? normal normal $f1:ident } => {};
28 { <=? normal subdoc $f1:ident } => {};
29 { <=? subdoc subdoc $f1:ident } => {};
30 // Not in the allowed list, must be an error:
31 { <=? $k0:ident $k1:ident $f1:ident } => {
32 compile_error!(concat!(
33 "in netdoc, ", stringify!($k1)," field ", stringify!($f1),
34 " may not come after ", stringify!($k0),
35 ));
36 };
37}
38
39/// Helper to implemnet `dtrace!` inside `NetdocParseable` derive-deftly macro.
40#[doc(hidden)]
41pub fn netdoc_parseable_derive_debug(ttype: &str, msg: &str, vals: &[&dyn Debug]) {
42 let mut out = std::io::stderr().lock();
43 (|| {
44 write!(out, "netdoc {ttype} parse: {msg}")?;
45 for val in vals {
46 write!(out, ", {val:?}")?;
47 }
48 writeln!(out)
49 })()
50 .expect("write to String failed");
51}
52
53define_derive_deftly_module! {
54 /// Common definitions for `NetdocParseable` and `NetdocParseableFields`
55 ///
56 /// Importing template must define these:
57 ///
58 /// * **`F_INTRO`**, **`F_SUBDOC`**, **`F_SIGNATURE`**
59 /// conditions for the fundamental field kinds which aren't supported everywhere.
60 ///
61 /// The `F_FLATTEN` and `F_NORMAL` field type conditions are defined here.
62 ///
63 /// * **`THIS_ITEM`**: consumes the next item and evaluates to it as an `UnparsedItem`.
64 /// See the definition in `NetdocParseable`.
65 ///
66 /// * **`F_ACCUMULATE_VAR`** the variable or field into which to accumulate
67 /// normal items for this field. Must be of type `&mut $F_ACCUMULATE_TYPE`.
68 NetdocParseableCommon beta_deftly:
69
70 // Convenience alias for our prelude
71 ${define P { $crate::parse2::internal_prelude }}
72
73 // Defines the `dtrace` macro.
74 ${define DEFINE_DTRACE {
75 #[allow(unused_macros)]
76 macro_rules! dtrace { { $$msg:literal $$(, $$val:expr )* $$(,)? } => {
77 ${if tmeta(netdoc(debug)) {
78 netdoc_parseable_derive_debug(
79 ${concat $ttype},
80 $$msg,
81 &[ $$( &&$$val as _, )* ],
82 )
83 }}
84 }}
85 }}
86
87 // Is this field `flatten`?
88 ${defcond F_FLATTEN fmeta(netdoc(flatten))}
89 // Is this field normal (non-structural)?
90 ${defcond F_NORMAL not(any(F_SIGNATURE, F_INTRO, F_FLATTEN, F_SUBDOC))}
91
92 // Field keyword as `&str`
93 ${define F_KEYWORD_STR { ${concat
94 ${if any(F_FLATTEN, F_SUBDOC) {
95 ${if F_INTRO {
96 ${error "#[deftly(netdoc(subdoc))] and (flatten) not supported for intro items"}
97 } else {
98 // Sub-documents and flattened fields have their keywords inside;
99 // if we ask for the field-based keyword name for one of those then that's a bug.
100 ${error "internal error, subdoc KeywordRef"}
101 }}
102 }}
103 ${fmeta(netdoc(keyword)) as str,
104 default ${concat ${kebab_case $fname}}}
105 }}}
106 // Field keyword as `&str` for debugging and error reporting
107 ${define F_KEYWORD_REPORT {
108 ${if F_SUBDOC { ${concat $fname} }
109 else { $F_KEYWORD_STR }}
110 }}
111 // Field keyword as `KeywordRef`
112 ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
113
114 // The effective field type for parsing.
115 //
116 // Handles #[deftly(netdoc(default))], in which case we parse as if the field was Option,
117 // and substitute in the default at the end.
118 //
119 ${define F_EFFECTIVE_TYPE {
120 ${if all(fmeta(netdoc(default))) {
121 Option::<$ftype>
122 } else {
123 $ftype
124 }}
125 }}
126
127 // Provide `$<selector_ $fname>` for every (suitable) field.
128 ${define ITEM_SET_SELECTORS {
129 $(
130 ${when not(any(F_FLATTEN))}
131
132 // See `mod multiplicity`.
133 ${if not(all(F_INTRO, fmeta(netdoc(with)))) {
134 // If the intro it has `with`, we don't check its trait impl, and this ends up unused
135 let $<selector_ $fname> = MultiplicitySelector::<$F_EFFECTIVE_TYPE>::default();
136 }}
137 )
138 }}
139 // The item set selector for this field.
140 // We must provide this, rather than expanding $<selector_ $fname> at the use sites,
141 // because the identifier `selector_` has different macro_rules hygiene here vs there!
142 // TODO derive-deftly#130
143 ${define F_SELECTOR $<selector_ $fname>}
144
145 // Check that every field type implements the necessary trait.
146 ${define CHECK_FIELD_TYPES_PARSEABLE {
147 $(
148 ${when not(any(F_FLATTEN))}
149
150 // Expands to `selector_FIELD.check_SOMETHING();`
151 //
152 // If the relevant trait isn't implemented, rustc reports the error by
153 // pointing at the `check-something` call. We re-span that identifier
154 // to point to the field name, so that's where the error is reported.
155 //
156 // Without this, we just get a report that `item` doesn't implement the required
157 // trait - but `item` is a local variable here, so the error points into the macro
158 ${if not(all(any(F_INTRO, F_NORMAL), fmeta(netdoc(with)))) {
159 $<selector_ $fname> . ${paste_spanned $fname ${select1
160 any(F_INTRO, F_NORMAL){
161 // For the intro item, this is not completely precise, because the
162 // it will allow Option<> and Vec<> which aren't allowed there.
163 ${if
164 fmeta(netdoc(single_arg)) { check_item_argument_parseable }
165 else { check_item_value_parseable }
166 }
167 }
168 F_SIGNATURE { check_signature_item_parseable }
169 F_SUBDOC { check_subdoc_parseable }
170 }} ();
171 }}
172 )
173 }}
174
175 // Convert the UnparsedItem (in `item` to the value (to accumulate).
176 // Expands to an expression.
177 ${define ITEM_VALUE_FROM_UNPARSED {
178 ${if fmeta(netdoc(with)) {
179 ${fmeta(netdoc(with)) as path}
180 ::${paste_spanned $fname from_unparsed}
181 (item)?
182 } else if fmeta(netdoc(single_arg)) { {
183 let item = ItemValueParseable::from_unparsed(item)?;
184 let (item,) = item;
185 item
186 } } else {
187 ItemValueParseable::from_unparsed(item)?
188 }}
189 }}
190
191 // Type into which we accumulate value(s) of this field
192 ${define F_ACCUMULATE_TYPE {
193 ${if F_FLATTEN {
194 <$ftype as NetdocParseableFields>::Accumulator
195 } else {
196 Option::<$F_EFFECTIVE_TYPE>
197 }
198 }}}
199
200 // Accumulates `item` (which must be `ItemSetMethods::Each`) into `$F_ACCUMULATE_VAR`
201 ${define ACCUMULATE_ITEM_VALUE { {
202 $F_SELECTOR.${paste_spanned $fname accumulate}($F_ACCUMULATE_VAR, item)?;
203 } }}
204
205 // Handle a nonstructural field, parsing and accumulating its value
206 //
207 // Looks at `kw` for the keyword.
208 //
209 // Expands to a series of `if ... { ... } else`.
210 // The use site must provide (maybe further arms) and a fallback block!
211 //
212 // If the item is the intro item for this document, evaluates `break` -
213 // so if `f_INTRO` is not trivially false, must be expanded within a field loop.
214 ${define NONSTRUCTURAL_ACCUMULATE_ELSE {
215 ${for fields {
216 ${when not(any(F_FLATTEN, F_SUBDOC))}
217
218 if kw == $F_KEYWORD {
219 ${select1
220 F_NORMAL {
221 let item = $THIS_ITEM;
222 dtrace!("is normal", item);
223 let item = $ITEM_VALUE_FROM_UNPARSED;
224 $ACCUMULATE_ITEM_VALUE
225 }
226 F_SIGNATURE {
227 let hash_inputs = input
228 .peek_signature_hash_inputs(signed_doc_body)?
229 .expect("not eof, we peeked kw");
230
231 let item = $THIS_ITEM;
232 dtrace!("is signature", item);
233 let item =
234 SignatureItemParseable::from_unparsed_and_body(item, &hash_inputs)?;
235 $ACCUMULATE_ITEM_VALUE
236 }
237 F_INTRO {
238 dtrace!("is intro", kw);
239 break;
240 } // start of next similar document
241 }
242 } else
243 }}
244 ${for fields {
245 ${when F_FLATTEN}
246
247 if $ftype::is_item_keyword(kw) {
248 dtrace!(${concat "is flatten in " $fname}, kw);
249 let item = $THIS_ITEM;
250 <$ftype as NetdocParseableFields>::accumulate_item($F_ACCUMULATE_VAR, item)?;
251 } else
252 }}
253 }}
254
255 // Completes a document
256 //
257 // The fields accumulated so far must be in `$fpatname` (as a value, not a ref,
258 // and therefore not in $F_ACCUMULATE_VAR).
259 //
260 // Expands to code which resolves the fields, and ends with `Ok(document value)`.
261 ${define FINISH_RESOLVE {
262 ${for fields {
263 ${select1
264 F_INTRO {}
265 any(F_NORMAL, F_SIGNATURE) {
266 let $fpatname = $F_SELECTOR.finish($fpatname, $F_KEYWORD_REPORT)?;
267 }
268 F_FLATTEN {
269 let $fpatname = <$ftype as NetdocParseableFields>::finish($fpatname)?;
270 }
271 F_SUBDOC {
272 let $fpatname = $F_SELECTOR.finish_subdoc($fpatname)?;
273 }
274 }
275 }}
276 $(
277 ${when not(F_INTRO)}
278 ${if fmeta(netdoc(default)) {
279 let $fpatname = Option::unwrap_or_default($fpatname);
280 }}
281 )
282 Ok($vpat)
283 }}
284}
285
286define_derive_deftly! {
287 use NetdocParseableCommon;
288
289 /// Derive [`NetdocParseable`] for a document (or sub-document)
290 ///
291 /// ### Expected input structure
292 ///
293 /// Should be applied named-field struct, where each field is
294 /// an Item which may appear in the document,
295 /// or a sub-document.
296 ///
297 /// The first field will be the document's intro Item.
298 /// The expected Keyword for each Item will be kebab-case of the field name.
299 ///
300 /// ### Field type
301 ///
302 /// Each field must be
303 /// * `impl `[`ItemValueParseable`] for an "exactly once" field,
304 /// * `Vec<T: ItemValueParseable>` for "zero or more", or
305 /// * `Option<T: ItemValueParseable>` for "zero or one".
306 ///
307 /// We don't directly support "at least once":
308 /// the parsed network document doesn't imply the invariant
309 /// that at least one such item was present.
310 // We could invent a `NonemptyVec` or something for this.
311 ///
312 /// (This is implemented via types in the [`multiplicity`] module,
313 /// specifically [`ItemSetSelector`].)
314 ///
315 /// ### Signed documents
316 ///
317 /// To handle signed documents define two structures:
318 ///
319 /// * `Foo`, containing only the content, not the signatures.
320 /// Derive `NetdocParseable` and [`NetdocSigned`](derive_deftly_template_NetdocSigned).
321 /// * `FooSignatures`, containing only the signatures.
322 /// Derive `NetdocParseable` with `#[deftly(netdoc(signatures))]`.
323 ///
324 /// Don't mix signature items with non-signature items in the same struct.
325 /// (This wouldn't compile, because the field type would implement the wrong trait.)
326 ///
327 /// ### Top-level attributes:
328 ///
329 /// * **`#[deftly(netdoc(doctype_for_error = "EXPRESSION"))]`**:
330 ///
331 /// Specifies the value to be returned from
332 /// [`NetdocParseable::doctype_for_error`].
333 ///
334 /// Note, must be an expression, so for a literal, nested `""` are needed.
335 ///
336 /// The default is the intro item keyword.
337 ///
338 /// * **`#[deftly(netdoc(signatures))]`**:
339 ///
340 /// This type is the signatures section of another document.
341 /// Signature sections have no separate intro keyword:
342 /// every field is structural and they are recognised in any order.
343 ///
344 /// Fields must implement [`SignatureItemParseable`],
345 /// rather than [`ItemValueParseable`],
346 ///
347 /// This signatures sub-document will typically be included in a
348 /// `FooSigned` struct derived with
349 /// [`NetdocSigned`](derive_deftly_template_NetdocSigned),
350 /// rather than included anywhere manually.
351 ///
352 /// * **`#[deftly(netdoc(debug))]`**:
353 ///
354 /// The generated implementation will generate copious debug output
355 /// to the program's stderr when it is run.
356 /// Do not enable in production!
357 ///
358 /// ### Field-level attributes:
359 ///
360 /// * **`#[deftly(netdoc(keyword = STR))]`**:
361 ///
362 /// Use `STR` as the Keyword for this Item.
363 ///
364 /// * **`#[deftly(netdoc(single_arg))]`**:
365 ///
366 /// The field type implements `ItemArgumentParseable`,
367 /// instead of `ItemValueParseable`,
368 /// and is parsed as if `(FIELD_TYPE,)` had been written.
369 ///
370 /// * **`#[deftly(netdoc(with = "MODULE"))]`**:
371 ///
372 /// Instead of `ItemValueParseable`, the item is parsed with `MODULE::from_unparsed`,
373 /// which must have the same signature as [`ItemValueParseable::from_unparsed`].
374 ///
375 /// (Not supported for sub-documents, signature items, or field collections.)
376 ///
377 /// * **`#[deftly(netdoc(default))]`**:
378 ///
379 /// This field is optional ("at most once");
380 /// if not present, `FIELD_TYPE::default()` will be used.
381 ///
382 /// This is an alternative to declaring the field type as `Option`
383 /// With `netdoc(default)`, the field value doesn't need unwrapping.
384 /// With `Option` it is possible to see if the field was provided.
385 ///
386 /// * **`#[deftly(netdoc(flatten))]`**:
387 ///
388 /// This field is a struct containing further individual normal fields.
389 /// The Items for those individual fields can appear in *this*
390 /// outer document in any order, interspersed with other normal fields.
391 ///
392 /// The field type must implement [`NetdocParseableFields`].
393 ///
394 /// * **`#[deftly(netdoc(subdoc))]`**:
395 ///
396 /// This field is a sub-document.
397 /// The value type `T` must implment [`NetdocParseable`]
398 /// *instead of* `ItemValueParseable`.
399 ///
400 /// The field name is not used for parsging;
401 /// the sub-document's intro keyword is used instead.
402 ///
403 /// Sub-documents are expected to appear after all normal items,
404 /// in the order presented in the struct definition.
405 ///
406 /// # Example
407 ///
408 /// ```
409 /// use derive_deftly::Deftly;
410 /// use tor_netdoc::derive_deftly_template_NetdocParseable;
411 /// use tor_netdoc::derive_deftly_template_NetdocSigned;
412 /// use tor_netdoc::derive_deftly_template_ItemValueParseable;
413 /// use tor_netdoc::parse2::{parse_netdoc, ParseInput, VerifyFailed};
414 /// use tor_netdoc::parse2::{SignatureItemParseable, SignatureHashInputs};
415 ///
416 /// #[derive(Deftly, Debug, Clone)]
417 /// #[derive_deftly(NetdocParseable, NetdocSigned)]
418 /// pub struct NdThing {
419 /// pub thing_start: (),
420 /// pub value: (String,),
421 /// }
422 ///
423 /// #[derive(Deftly, Debug, Clone)]
424 /// #[derive_deftly(NetdocParseable)]
425 /// #[deftly(netdoc(signatures))]
426 /// pub struct NdThingSignatures {
427 /// pub signature: FoolishSignature,
428 /// }
429 ///
430 /// #[derive(Deftly, Debug, Clone)]
431 /// #[derive_deftly(ItemValueParseable)]
432 /// pub struct FoolishSignature {
433 /// pub doc_len: usize,
434 ///
435 /// #[deftly(netdoc(sig_hash = "use_length_as_foolish_hash"))]
436 /// pub doc_len_actual_pretending_to_be_hash: usize,
437 /// }
438 ///
439 /// fn use_length_as_foolish_hash(body: &SignatureHashInputs) -> usize {
440 /// body.body().body().len()
441 /// }
442 ///
443 /// let doc_text =
444 /// r#"thing-start
445 /// value something
446 /// signature 28
447 /// "#;
448 ///
449 /// impl NdThingSigned {
450 /// pub fn verify_foolish_timeless(self) -> Result<NdThing, VerifyFailed> {
451 /// let sig = &self.signatures.signature;
452 /// if sig.doc_len != sig.doc_len_actual_pretending_to_be_hash {
453 /// return Err(VerifyFailed::VerifyFailed);
454 /// }
455 /// Ok(self.body)
456 /// }
457 /// }
458 ///
459 /// let input = ParseInput::new(&doc_text, "<input>");
460 /// let doc: NdThingSigned = parse_netdoc(&input).unwrap();
461 /// let doc = doc.verify_foolish_timeless().unwrap();
462 /// assert_eq!(doc.value.0, "something");
463 /// ```
464 export NetdocParseable for struct, expect items, beta_deftly:
465
466 // Predicate for the toplevel
467 ${defcond T_SIGNATURES tmeta(netdoc(signatures))}
468
469 // Predicates for the field kinds
470 ${defcond F_INTRO all(not(T_SIGNATURES), approx_equal($findex, 0))}
471 ${defcond F_SUBDOC fmeta(netdoc(subdoc))}
472 ${defcond F_SIGNATURE T_SIGNATURES} // signatures section documents have only signature fields
473
474 ${define F_ACCUMULATE_VAR { (&mut $fpatname) }}
475
476 impl<$tgens> $P::NetdocParseable for $ttype {
477 fn doctype_for_error() -> &'static str {
478 ${tmeta(netdoc(doctype_for_error)) as expr,
479 default ${concat ${for fields { ${when F_INTRO} $F_KEYWORD_STR }}}}
480 }
481
482 fn is_intro_item_keyword(kw: $P::KeywordRef<'_>) -> bool {
483 use $P::*;
484
485 ${for fields {
486 ${when any(F_SIGNATURE, F_INTRO)}
487 kw == $F_KEYWORD
488 }}
489 }
490
491 //##### main parsing function #####
492
493 fn from_items<'s>(
494 input: &mut $P::ItemStream<'s>,
495 outer_stop: $P::stop_at!(),
496 ) -> $P::Result<$ttype, $P::ErrorProblem> {
497 use $P::*;
498 $DEFINE_DTRACE
499
500 //----- compile-time check that fields are in the right order in the struct -----
501
502 ${if not(T_SIGNATURES) { // signatures structs have only signature fields
503 netdoc_ordering_check! {
504 $(
505 ${select1
506 F_INTRO { intro }
507 F_NORMAL { normal }
508 F_FLATTEN { normal }
509 F_SUBDOC { subdoc }
510 }
511 $fname
512 )
513 }
514 }}
515
516 //----- prepare item set selectors for every field -----
517 $ITEM_SET_SELECTORS
518 $CHECK_FIELD_TYPES_PARSEABLE
519
520 // Is this an intro item keyword ?
521 //
522 // Expands to an appropriate `is_intro_item_keyword` method invocation,
523 // but *without arguments*. So, something a bit like an expression of type
524 // fn(KeywordRef) -> bool
525 ${define F_SUBDOC_IS_INTRO_ITEM_KEYWORD {
526 ${if not(F_SUBDOC) { ${error "internal-error: subdoc kw, but not subdoc field"} }}
527 $F_SELECTOR.is_intro_item_keyword
528 }}
529
530 //----- Helper fragments for parsing individual pieces of the document -----
531
532 // Peeks a keyword, and returns it but only if it's part of this (sub)doc.
533 // Return `None` if it was in outer_stop
534 let peek_keyword = |input: &mut ItemStream<'s>| -> Result<Option<KeywordRef<'s>>, EP> {
535 let Some(kw) = input.peek_keyword()? else {
536 dtrace!("stopping, because EOF");
537 return Ok(None)
538 };
539 if outer_stop.stop_at(kw) {
540 dtrace!("stopping, because peeked", kw);
541 return Ok(None)
542 }
543 Ok(Some(kw))
544 };
545
546 // Returns the actual item as an UnparsedItem, committing to consuming it.
547 // Can panic if called without previous `peek_keyword`.
548 ${define THIS_ITEM {
549 input.next_item()?.expect("peeked")
550 }}
551
552 //----- keyword classification closures -----
553
554 // Is this a keyword for one of our sub-documents?
555 let is_subdoc_kw = ${for fields {
556 ${when F_SUBDOC}
557 StopAt(|kw: KeywordRef<'_>| $F_SUBDOC_IS_INTRO_ITEM_KEYWORD(kw)) |
558 }}
559 StopAt(false)
560 ;
561 // Is this a keyword for one of our parents or sub-documents?
562 let inner_stop = outer_stop | is_subdoc_kw;
563
564 //========== actual parsing ==========
565
566 // For each parsing loop/section, where we aren't looking for precisely one thing,
567 // we should explicitly decide what to do with each of:
568 // - F_INTRO - intro item for this document (maybe next instance in parent)
569 // - F_NORMAL - normal items
570 // - subdocuments, is_subdoc_kw and F_SUBDOC
571 // - F_SIGNATURE
572 // - our parent's structural keywords, outer_stop
573 // 5 cases in all.
574
575 // Note the body of the document (before the signatures)
576 ${if T_SIGNATURES {
577 let signed_doc_body = input.body_sofar_for_signature();
578 }}
579
580 //----- Parse the intro item, and introduce bindings for the other items. -----
581 dtrace!("looking for intro item");
582
583 $( ${select1 F_INTRO {
584
585 let item = input.next_item()?.ok_or(EP::EmptyDocument)?;
586 dtrace!("intro", item);
587 if !Self::is_intro_item_keyword(item.keyword()) {
588 Err(EP::WrongDocumentType)?;
589 }
590 let $fpatname: $ftype = $ITEM_VALUE_FROM_UNPARSED;
591
592 } else {
593
594 let mut $fpatname = $F_ACCUMULATE_TYPE::default();
595
596 }})
597
598 //----- Parse the normal items -----
599 dtrace!("looking for normal items");
600
601 while let Some(kw) = peek_keyword(input)? {
602 dtrace!("for normal, peeked", kw);
603 if inner_stop.stop_at(kw) {
604 dtrace!("is inner stop", kw);
605 break;
606 };
607
608 $NONSTRUCTURAL_ACCUMULATE_ELSE
609 {
610 dtrace!("is unknown (in normal)");
611 let _: UnparsedItem = $THIS_ITEM;
612 }
613 }
614
615 //----- Parse the subdocs, in order -----
616 dtrace!("looking for subdocs");
617
618 ${for fields {
619 ${when F_SUBDOC}
620 dtrace!("looking for subdoc", $F_KEYWORD_REPORT);
621
622 loop {
623 let Some(kw) = peek_keyword(input)? else { break };
624 dtrace!("for subdoc, peek", kw);
625
626 if !$F_SUBDOC_IS_INTRO_ITEM_KEYWORD(kw) {
627 dtrace!("is not this subdoc", kw);
628 break;
629 };
630
631 $F_SELECTOR.can_accumulate(&mut $fpatname)?;
632
633 dtrace!("is this subdoc", kw);
634 let item = NetdocParseable::from_items(input, inner_stop);
635 dtrace!("parsed this subdoc", item.as_ref().map(|_| ()));
636 let item = item?;
637
638 $ACCUMULATE_ITEM_VALUE
639 }
640 }}
641
642 // Resolve all the fields
643 dtrace!("reached end, resolving");
644
645 $FINISH_RESOLVE
646 }
647 }
648}
649
650define_derive_deftly! {
651 use NetdocParseableCommon;
652
653 /// Derive [`NetdocParseableFields`] for a struct with individual items
654 ///
655 /// Defines a struct `FooNetdocParseAccumulator` to be the
656 /// `NetdocParseableFields::Accumulator`.
657 ///
658 /// Similar to
659 /// [`#[derive_deftly(NetdocParseable)]`](derive_deftly_template_NetdocParseable),
660 /// but:
661 ///
662 /// * Derives [`NetdocParseableFields`]
663 /// * The input struct can contain only normal non-structural items
664 /// (so it's not a sub-document with an intro item).
665 /// * The only attributes supported are the field attributes
666 /// `#[deftly(netdoc(keyword = STR))]`
667 /// `#[deftly(netdoc(default))]`
668 /// `#[deftly(netdoc(single_arg))]`
669 /// `#[deftly(netdoc(with = "MODULE"))]`
670 /// `#[deftly(netdoc(flatten))]`
671 export NetdocParseableFields for struct , expect items, beta_deftly:
672
673 ${defcond F_INTRO false}
674 ${defcond F_SUBDOC false}
675 ${defcond F_SIGNATURE false}
676
677 ${define THIS_ITEM item}
678 ${define F_ACCUMULATE_VAR { (&mut acc.$fname) }}
679
680 #[doc = ${concat "Partially parsed `" $tname "`"}]
681 ///
682 /// Used for [`${concat $P::NetdocParseableFields::Accumulator}`].
683 #[derive(Default, Debug)]
684 $tvis struct $<$tname NetdocParseAccumulator><$tdefgens> { $(
685 $fname: $F_ACCUMULATE_TYPE,
686 ) }
687
688 impl<$tgens> $P::NetdocParseableFields for $ttype {
689 type Accumulator = $<$ttype NetdocParseAccumulator>;
690
691 fn is_item_keyword(
692 #[allow(unused_variables)] // If there are no fields, this is unused
693 kw: $P::KeywordRef<'_>,
694 ) -> bool {
695 #[allow(unused_imports)] // false positives in some situations
696 use $P::*;
697
698 ${for fields {
699 ${when not(F_FLATTEN)}
700 kw == $F_KEYWORD ||
701 }}
702 ${for fields {
703 ${when F_FLATTEN}
704 <$ftype as NetdocParseableFields>::is_item_keyword(kw) ||
705 }}
706 false
707 }
708
709 fn accumulate_item(
710 #[allow(unused_variables)] // If there are no fields, this is unused
711 acc: &mut Self::Accumulator,
712 #[allow(unused_variables)] // If there are no fields, this is unused
713 item: $P::UnparsedItem<'_>,
714 ) -> $P::Result<(), $P::ErrorProblem> {
715 #[allow(unused_imports)] // false positives in some situations
716 use $P::*;
717 $DEFINE_DTRACE
718
719 $ITEM_SET_SELECTORS
720 $CHECK_FIELD_TYPES_PARSEABLE
721
722 #[allow(unused_variables)] // If there are no fields, this is unused
723 let kw = item.keyword();
724
725 $NONSTRUCTURAL_ACCUMULATE_ELSE
726 {
727 panic!("accumulate_item called though is_intro_item_keyword returns false");
728 }
729
730 #[allow(unreachable_code)] // If there are no fields!
731 Ok(())
732 }
733
734 fn finish(
735 #[allow(unused_variables)] // If there are no fields, this is unused
736 acc: Self::Accumulator
737 ) -> $P::Result<Self, $P::ErrorProblem> {
738 #[allow(unused_imports)] // false positives in some situations
739 use $P::*;
740 $DEFINE_DTRACE
741
742 dtrace!("finish, resolving");
743
744 $ITEM_SET_SELECTORS
745
746 $( let $fpatname = acc.$fname; )
747 $FINISH_RESOLVE
748 }
749 }
750}
751
752define_derive_deftly! {
753 /// Derive `FooSigned` from `Foo`
754 ///
755 /// Apply this derive to the main body struct `Foo`.
756 ///
757 /// Usually, provide suitable `.verify_...` methods.
758 ///
759 /// The body and signature types have to implement `Clone` and `Debug`.
760 ///
761 /// ### Top-level attributes:
762 ///
763 /// * **`#[deftly(netdoc(signature = "TYPE"))]`**:
764 /// Type of the signature(s) section.
765 ///
766 /// TYPE must implement `NetdocParseable`,
767 /// with `is_intro_item_keyword` reporting *every* signature keyword.
768 /// Normally this is achieved with
769 /// `#[derive_deftly(NetdocParseable)] #[deftly(netdoc(signatures))]`.
770 ///
771 /// ### Generated struct
772 ///
773 /// ```
774 /// # struct Foo; struct FooSignatures;
775 /// pub struct FooSigned {
776 /// body: Foo,
777 /// pub signatures: FooSignatures,
778 /// }
779 ///
780 /// # #[cfg(all())] { r##"
781 /// impl NetdocParseable for FooSigned { .. }
782 /// impl NetdocSigned for FooSigned { .. }
783 /// # "##; }
784 /// ```
785 //
786 // We don't make this a generic struct because the defining module (crate)
787 // will want to add verification methods, which means they must define the struct.
788 export NetdocSigned for struct, expect items, beta_deftly:
789
790 // Convenience alias for our prelude
791 ${define P { $crate::parse2::internal_prelude }}
792
793 // FooSignatures (type name)
794 ${define SIGS_TYPE { $< ${tmeta(netdoc(signatures)) as ty, default $<$ttype Signatures>} > }}
795
796 #[doc = ${concat "Signed (unverified) form of [`" $tname "`]"}]
797 ///
798 /// Embodies:
799 ///
800 #[doc = ${concat " * **[`" $tname "`]**: document body"}]
801 #[doc = ${concat " * **[`" $SIGS_TYPE "`]**: signatures"}]
802 ///
803 /// If this type was parsed from a document text,
804 /// the signatures have *not* yet been verified.
805 ///
806 /// Use a `.verify_...` method to obtain useable, verified, contents.
807 #[derive(Debug, Clone)]
808 $tvis struct $<$ttype Signed> {
809 /// The actual body
810 //
811 // Misuse is prevented by this field not being public.
812 // It can be accessed only in this module, where the verification functions are.
813 body: $ttype,
814
815 /// Signatures
816 $tvis signatures: $SIGS_TYPE,
817 }
818
819 impl<$tgens> $P::NetdocParseable for $<$ttype Signed> {
820 fn doctype_for_error() -> &'static str {
821 $ttype::doctype_for_error()
822 }
823
824 fn is_intro_item_keyword(kw: $P::KeywordRef<'_>) -> bool {
825 $ttype::is_intro_item_keyword(kw)
826 }
827
828 fn from_items<'s>(
829 input: &mut $P::ItemStream<'s>,
830 outer_stop: $P::stop_at!(),
831 ) -> $P::Result<$<$ttype Signed>, $P::ErrorProblem> {
832 input.parse_signed(outer_stop)
833 }
834 }
835
836 impl<$tgens> $P::NetdocSigned for $<$ttype Signed> {
837 type Body = $ttype;
838 type Signatures = $SIGS_TYPE;
839 fn inspect_unverified(&self) -> (&Self::Body, &Self::Signatures) {
840 (&self.body, &self.signatures)
841 }
842 fn unwrap_unverified(self) -> (Self::Body, Self::Signatures) {
843 (self.body, self.signatures)
844 }
845 fn from_parts(body: Self::Body, signatures: Self::Signatures) -> Self {
846 Self { body, signatures }
847 }
848 }
849}
850
851define_derive_deftly! {
852 /// Derive `ItemValueParseable`
853 ///
854 /// Fields in the struct are parsed from the keyword line arguments,
855 /// in the order they appear in the struct.
856 ///
857 /// ### Field type
858 ///
859 /// Each field should be:
860 ///
861 /// * `impl `[`ItemArgumentParseable`] (one argument),
862 /// * `Option<impl ItemArgumentParseable>` (one optional argument),
863 /// * `Vec<impl ItemArgumentParseable>` (zero or more arguments), or
864 /// * `BTreeSet<impl ItemArgumentParseable + Ord>` (zero or more arguments).
865 ///
866 /// `ItemArgumentParseable` can be implemented via `impl FromStr`,
867 /// by writing `impl NormalItemArgument`.
868 ///
869 /// For `Option` or `Vec`, we expect that *if* there are any further arguments,
870 /// they are for this field.
871 /// So absence of any optional argument means absence of following arguments,
872 /// and no arguments can follow a `Vec`.
873 ///
874 /// Some Tor netdocs have optional arguments followed by other data,
875 /// with unclear/ambiguous parsing rules.
876 /// These cases typically require manual implementation of [`ItemValueParseable`].
877 ///
878 /// (Multiplicity is implemented via types in the [`multiplicity`] module,
879 /// specifically [`ArgumentSetSelector`] and [`ArgumentSetMethods`].)
880 ///
881 /// ### Top-level attributes:
882 ///
883 /// * **`#[deftly(netdoc(no_extra_args))]**:
884 ///
885 /// Reject, rather than ignore, additional arguments found in the document
886 /// which aren't described by the struct.
887 ///
888 /// ### Field-level attributes:
889 ///
890 /// * **`#[deftly(netdoc(rest))]**:
891 ///
892 /// The field is the whole rest of the line.
893 /// Must come after any other normal argument fields.
894 /// Only allowed once.
895 ///
896 /// The field type must implement `FromStr`.
897 /// (I.e. `Vec` , `Option` etc., are not allowed.)
898 ///
899 /// * **`#[deftly(netdoc(object))]**:
900 ///
901 /// The field is the Object.
902 /// It must implement [`ItemObjectParseable`]
903 /// (or be `Option<impl ItemObjectParseable>`).
904 ///
905 /// Only allowed once.
906 /// If omittted, any object is rejected.
907 ///
908 /// * **`#[deftly(netdoc(object(label = "LABEL")))]**:
909 ///
910 /// Sets the expected label for an Object.
911 /// If not supplied, uses [`ItemObjectParseable::check_label`].
912 ///
913 /// * **`#[deftly(netdoc(with = "MODULE")]**:
914 ///
915 /// Instead of `ItemArgumentParseable`, the argument is parsed with `MODULE::from_args`,
916 /// which must have the same signature as [`ItemArgumentParseable::from_args`].
917 ///
918 /// With `#[deftly(netdoc(rest))]`, FUNCTION replaces
919 /// `<FIELD AS FromStr>::from_str`.
920 ///
921 /// With `#[deftly(netdoc(objecte))]`, uses `MODULE::try_from`
922 /// which must have the signature `fn(Vec<u8>) -> Result<OBJECT, _>;
923 /// like `TryFrom::<Vec<u8>>>::try_from`.
924 /// LABEL must also be specified
925 /// unless the object also implements `ItemObjectParseable`.
926 /// Errors from parsing will be discarded and replaced with
927 /// [`ErrorProblem::ObjectInvalidData`].
928 ///
929 /// * **`#[deftly(netdoc(sig_hash = "HASH_METHOD"))]**:
930 ///
931 /// This item is a signature item.
932 /// [`SignatureItemParseable`] will be implemented instead of [`ItemValueParseable`].
933 ///
934 /// This field is a document hash.
935 /// The hash will be computed using `HASH_METHOD`,
936 /// which will be resolved with `sig_hash_methods::*` in scope.
937 ///
938 /// `fn HASH_METHOD(body: &SignatureHashInputs) -> HASH_FIELD_VALUE`.
939 export ItemValueParseable for struct, expect items, beta_deftly:
940
941 ${define P { $crate::parse2::internal_prelude }}
942
943 ${defcond F_REST fmeta(netdoc(rest))}
944 ${defcond F_OBJECT fmeta(netdoc(object))}
945 ${defcond F_SIG_HASH fmeta(netdoc(sig_hash))}
946 ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SIG_HASH))}
947
948 ${defcond T_IS_SIGNATURE not(approx_equal(${for fields { ${when F_SIG_HASH} 1 }}, {}))}
949 ${define TRAIT ${if T_IS_SIGNATURE { SignatureItemParseable } else { ItemValueParseable }}}
950 ${define METHOD ${if T_IS_SIGNATURE { from_unparsed_and_body } else { from_unparsed }}}
951
952 impl<$tgens> $P::$TRAIT for $ttype {
953 fn $METHOD<'s>(
954 mut input: $P::UnparsedItem<'s>,
955 ${if T_IS_SIGNATURE {
956 document_body: &SignatureHashInputs<'_>,
957 }}
958 ) -> $P::Result<Self, $P::EP>
959 {
960 #[allow(unused_imports)] // false positive when macro is used with prelude in scope
961 use $P::*;
962
963 let object = input.object();
964 #[allow(unused)]
965 let mut args = input.args_mut();
966 $(
967 let $fpatname = ${select1
968 F_NORMAL { {
969 let selector = MultiplicitySelector::<$ftype>::default();
970 ${if not(fmeta(netdoc(with))) {
971 selector.${paste_spanned $fname check_argument_value_parseable}();
972 }}
973 selector.parse_with(
974 &mut args,
975 ${fmeta(netdoc(with))
976 as path,
977 default { ItemArgumentParseable }}::${paste_spanned $fname from_args},
978 ).map_err(args.error_handler(stringify!($fname)))?
979 } }
980 F_OBJECT { {
981 let selector = MultiplicitySelector::<$ftype>::default();
982 let object = object.map(|object| {
983 let data = object.decode_data()?;
984 ${if fmeta(netdoc(object(label))) {
985 if object.label() != ${fmeta(netdoc(object(label))) as str} {
986 return Err(EP::ObjectIncorrectLabel)
987 }
988 } else {
989 selector.check_label(object.label())?;
990 }}
991 ${if fmeta(netdoc(with)) {
992 ${fmeta(netdoc(with)) as path}::${paste_spanned $fname try_from}
993 (data)
994 .map_err(|_| EP::ObjectInvalidData)
995 } else {
996 selector.${paste_spanned $fname check_object_parseable}();
997 ItemObjectParseable::from_bytes(&data)
998 }}
999 }).transpose()?;
1000 selector.resolve_option(object)?
1001 } }
1002 F_REST { {
1003 // consumes `args`, leading to compile error if the rest field
1004 // isn't last (or is combined with no_extra_args).
1005 let args_consume = args;
1006 ${fmeta(netdoc(with))
1007 as path,
1008 default { <$ftype as FromStr>::from_str }}(args_consume.into_remaining())
1009 .map_err(|_| AE::Invalid)
1010 .map_err(args_consume.error_handler(stringify!($fname)))?
1011 } }
1012 F_SIG_HASH { {
1013 #[allow(unused_imports)]
1014 use $P::sig_hash_methods::*;
1015 ${fmeta(netdoc(sig_hash)) as path}(&document_body)
1016 } }
1017 };
1018 )
1019 ${if approx_equal({}, $( ${when F_OBJECT} $fname )) {
1020 if object.is_some() {
1021 return Err(EP::ObjectUnexpected);
1022 }
1023 }}
1024 ${if tmeta(netdoc(no_extra_args)) {
1025 args.reject_extra_args()?;
1026 }}
1027 Ok($tname { $( $fname: $fpatname, ) })
1028 }
1029 }
1030}