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