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}