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 // Sub-documents and flattened fields have their keywords inside;
247 // if we ask for the field-based keyword name for one of those then that's a bug.
248 ${error "internal error, subdoc KeywordRef"}
249 }}
250 ${fmeta(netdoc(keyword)) as str,
251 default ${concat ${kebab_case $fname}}}
252 }}}
253 // Field keyword as `&str` for debugging and error reporting
254 ${define F_KEYWORD_REPORT {
255 ${if F_SUBDOC { ${concat $fname} }
256 else { $F_KEYWORD_STR }}
257 }}
258 // Field keyword as `KeywordRef`
259 ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
260
261 // The effective field type for parsing.
262 //
263 // Handles #[deftly(netdoc(default))], in which case we parse as if the field was Option,
264 // and substitute in the default at the end.
265 ${define F_EFFECTIVE_TYPE {
266 ${if all(fmeta(netdoc(default)), not(F_INTRO)) {
267 Option::<$ftype>
268 } else {
269 $ftype
270 }}
271 }}
272
273 impl<$tgens> $P::NetdocParseable for $ttype {
274 fn doctype_for_error() -> &'static str {
275 ${tmeta(netdoc(doctype_for_error)) as expr,
276 default ${concat ${for fields { ${when F_INTRO} $F_KEYWORD_STR }}}}
277 }
278
279 fn is_intro_item_keyword(kw: $P::KeywordRef<'_>) -> bool {
280 use $P::*;
281
282 ${for fields {
283 ${when any(F_SIGNATURE, F_INTRO)}
284 kw == $F_KEYWORD
285 }}
286 }
287
288 //##### main parsing function #####
289
290 fn from_items<'s>(
291 input: &mut $P::ItemStream<'s>,
292 outer_stop: $P::stop_at!(),
293 ) -> $P::Result<$ttype, $P::ErrorProblem> {
294 use $P::*;
295
296 //----- compile-time check that fields are in the right order in the struct -----
297
298 ${if not(T_SIGNATURES) { // signatures structs have only signature fields
299 netdoc_ordering_check! {
300 $(
301 ${select1
302 F_INTRO { intro }
303 F_NORMAL { normal }
304 F_FLATTEN { normal }
305 F_SUBDOC { subdoc }
306 }
307 $fname
308 )
309 }
310 }}
311
312 //----- Debugging -----
313
314 macro_rules! dtrace { { $$msg:literal $$(, $$val:expr )* $$(,)? } => {
315 ${if tmeta(netdoc(debug)) {
316 netdoc_parseable_derive_debug(
317 ${concat $ttype},
318 $$msg,
319 &[ $$( &&$$val as _, )* ],
320 )
321 }}
322 }}
323
324 //----- prepare item set selectors for every field -----
325
326 $(
327 ${when not(any(F_FLATTEN))}
328
329 // See `mod multiplicity`.
330 ${if not(all(F_INTRO, fmeta(netdoc(with)))) {
331 // If the intro it has `with`, we don't check its trait impl, and this ends up unused
332 let $<selector_ $fname> = ItemSetSelector::<$F_EFFECTIVE_TYPE>::default();
333 }}
334
335 // Expands to `selector_FIELD.check_SOMETHING();`
336 //
337 // If the relevant trait isn't implemented, rustc reports the error by
338 // pointing at the `check-something` call. We re-span that identifier
339 // to point to the field name, so that's where the error is reported.
340 //
341 // Without this, we just get a report that `item` doesn't implement the required
342 // trait - but `item` is a local variable here, so the error points into the macro
343 ${if not(all(any(F_INTRO, F_NORMAL), fmeta(netdoc(with)))) {
344 $<selector_ $fname> . ${paste_spanned $fname ${select1
345 any(F_INTRO, F_NORMAL){
346 // For the intro item, this is not completely precise, because the
347 // it will allow Option<> and Vec<> which aren't allowed there.
348 ${if
349 fmeta(netdoc(single_arg)) { check_item_argument_parseable }
350 else { check_item_value_parseable }
351 }
352 }
353 F_SIGNATURE { check_signature_item_parseable }
354 F_SUBDOC { check_subdoc_parseable }
355 }} ();
356 }}
357 )
358
359 // Is this an intro item keyword ?
360 //
361 // Expands to an appropriate `is_intro_item_keyword` method invocation,
362 // but *without arguments*. So, something a bit like an expression of type
363 // fn(KeywordRef) -> bool
364 ${define F_SUBDOC_IS_INTRO_ITEM_KEYWORD {
365 ${if not(F_SUBDOC) { ${error "internal-error: subdoc kw, but not subdoc field"} }}
366 $<selector_ $fname>.is_intro_item_keyword
367 }}
368
369 //----- Helper fragments for parsing individual pieces of the document -----
370
371 // Peeks a keyword, and returns it but only if it's part of this (sub)doc.
372 // Return `None` if it was in outer_stop
373 let peek_keyword = |input: &mut ItemStream<'s>| -> Result<Option<KeywordRef<'s>>, EP> {
374 let Some(kw) = input.peek_keyword()? else {
375 dtrace!("stopping, because EOF");
376 return Ok(None)
377 };
378 if outer_stop.stop_at(kw) {
379 dtrace!("stopping, because peeked", kw);
380 return Ok(None)
381 }
382 Ok(Some(kw))
383 };
384
385 // Returns the actual item as an UnparsedItem, committing to consuming it.
386 // Can panic if called without previous `peek_keyword`.
387 ${define THIS_ITEM {
388 input.next_item()?.expect("peeked")
389 }}
390
391 ${define ITEM_VALUE_FROM_UNPARSED {
392 ${if fmeta(netdoc(with)) {
393 ${fmeta(netdoc(with)) as path}
394 ::${paste_spanned $fname from_unparsed}
395 (item)?
396 } else if fmeta(netdoc(single_arg)) { {
397 let item = ItemValueParseable::from_unparsed(item)?;
398 let (item,) = item;
399 item
400 } } else {
401 ItemValueParseable::from_unparsed(item)?
402 }}
403 }}
404
405 // Accumulates `item` (which must be DataSet::Value) into `Putnam`
406 ${define ACCUMULATE_ITEM_VALUE { {
407 $<selector_ $fname>.accumulate(&mut $fpatname, item)?;
408 } }}
409
410 //----- keyword classification closures -----
411
412 // Is this a keyword for one of our sub-documents?
413 let is_subdoc_kw = ${for fields {
414 ${when F_SUBDOC}
415 StopAt(|kw: KeywordRef<'_>| $F_SUBDOC_IS_INTRO_ITEM_KEYWORD(kw)) |
416 }}
417 StopAt(false)
418 ;
419 // Is this a keyword for one of our parents or sub-documents?
420 let inner_stop = outer_stop | is_subdoc_kw;
421
422 //========== actual parsing ==========
423
424 // For each parsing loop/section, where we aren't looking for precisely one thing,
425 // we should explicitly decide what to do with each of:
426 // - F_INTRO - intro item for this document (maybe next instance in parent)
427 // - F_NORMAL - normal items
428 // - subdocuments, is_subdoc_kw and F_SUBDOC
429 // - F_SIGNATURE
430 // - our parent's structural keywords, outer_stop
431 // 5 cases in all.
432
433 // Note the body of the document (before the signatures)
434 ${if T_SIGNATURES {
435 let signed_doc_body = input.body_sofar_for_signature();
436 }}
437
438 //----- Parse the intro item, and introduce bindings for the other items. -----
439 dtrace!("looking for intro item");
440
441 $( ${select1 F_INTRO {
442
443 let item = input.next_item()?.ok_or(EP::EmptyDocument)?;
444 dtrace!("intro", item);
445 if !Self::is_intro_item_keyword(item.keyword()) {
446 Err(EP::WrongDocumentType)?;
447 }
448 let $fpatname: $ftype = $ITEM_VALUE_FROM_UNPARSED;
449
450 } F_FLATTEN {
451
452 let mut $fpatname = <$ftype as NetdocParseableFields>::Accumulator::default();
453
454 } else {
455
456 let mut $fpatname: Option<$F_EFFECTIVE_TYPE> = None;
457
458 }})
459
460 //----- Parse the normal items -----
461 dtrace!("looking for normal items");
462
463 while let Some(kw) = peek_keyword(input)? {
464 dtrace!("for normal, peeked", kw);
465 if inner_stop.stop_at(kw) {
466 dtrace!("is inner stop", kw);
467 break;
468 };
469 ${for fields {
470 ${when not(any(F_FLATTEN, F_SUBDOC))}
471
472 if kw == $F_KEYWORD {
473 ${select1
474 F_NORMAL {
475 let item = $THIS_ITEM;
476 dtrace!("is normal", item);
477 let item = $ITEM_VALUE_FROM_UNPARSED;
478 $ACCUMULATE_ITEM_VALUE
479 }
480 F_SIGNATURE {
481 let hash_inputs = input
482 .peek_signature_hash_inputs(signed_doc_body)?
483 .expect("not eof, we peeked kw");
484
485 let item = $THIS_ITEM;
486 dtrace!("is signature", item);
487 let item =
488 SignatureItemParseable::from_unparsed_and_body(item, &hash_inputs)?;
489 $ACCUMULATE_ITEM_VALUE
490 }
491 F_INTRO {
492 dtrace!("is intro", kw);
493 break;
494 } // start of next similar document
495 }
496 } else
497 }}
498 ${for fields {
499 ${when F_FLATTEN}
500
501 if $ftype::is_item_keyword(kw) {
502 dtrace!(${concat "is flatten in " $fname}, kw);
503 let item = $THIS_ITEM;
504 $ftype::accumulate_item(&mut $fpatname, item)?;
505 } else
506 }}
507 {
508 dtrace!("is unknown (in normal)");
509 let _: UnparsedItem = $THIS_ITEM;
510 }
511 }
512
513 //----- Parse the subdocs, in order -----
514 dtrace!("looking for subdocs");
515
516 ${for fields {
517 ${when F_SUBDOC}
518 dtrace!("looking for subdoc", $F_KEYWORD_REPORT);
519
520 loop {
521 let Some(kw) = peek_keyword(input)? else { break };
522 dtrace!("for subdoc, peek", kw);
523
524 if !$F_SUBDOC_IS_INTRO_ITEM_KEYWORD(kw) {
525 dtrace!("is not this subdoc", kw);
526 break;
527 };
528
529 $<selector_ $fname>.can_accumulate(&mut $fpatname)?;
530
531 dtrace!("is this subdoc", kw);
532 let item = NetdocParseable::from_items(input, inner_stop);
533 dtrace!("parsed this subdoc", item.as_ref().map(|_| ()));
534 let item = item?;
535
536 $ACCUMULATE_ITEM_VALUE
537 }
538 }}
539
540 // Resolve all the fields
541 dtrace!("reached end, resolving");
542
543 ${for fields {
544 ${select1
545 F_INTRO {}
546 any(F_NORMAL, F_SIGNATURE) {
547 let $fpatname = $<selector_ $fname>.finish($fpatname, $F_KEYWORD_REPORT)?;
548 }
549 F_FLATTEN {
550 let $fpatname = <$ftype as NetdocParseableFields>::finish($fpatname)?;
551 }
552 F_SUBDOC {
553 let $fpatname = $<selector_ $fname>.finish_subdoc($fpatname)?;
554 }
555 }}}
556 $(
557 ${when not(F_INTRO)}
558 ${if fmeta(netdoc(default)) {
559 let $fpatname = Option::unwrap_or_default($fpatname);
560 }}
561 )
562
563 let r = $vpat;
564
565 Ok(r)
566 }
567 }
568}
569
570define_derive_deftly! {
571 /// Derive [`NetdocParseableFields`] for a struct with individual items
572 ///
573 /// Defines a struct `FooNetdocParseAccumulator` to be the
574 /// `NetdocParseableFields::Accumulator`.
575 ///
576 /// Similar to
577 /// [`#[derive_deftly(NetdocParseable)]`](derive_deftly_template_NetdocParseable),
578 /// but:
579 ///
580 /// * Derives [`NetdocParseableFields`]
581 /// * The input struct can contain only normal non-structural items
582 /// (so it's not a sub-document with an intro item).
583 /// * The only attributes supported are the field attributes
584 /// `#[deftly(netdoc(keyword = STR))]`
585 /// `#[deftly(netdoc(default))]`
586 /// `#[deftly(netdoc(single_arg))]`
587 /// `#[deftly(netdoc(with = "MODULE"))]`
588 export NetdocParseableFields for struct , expect items, beta_deftly:
589
590 // TODO deduplicate with copy in NetdocParseable after rust-derive-deftly#39
591
592 // Convenience alias for our prelude
593 ${define P { $crate::parse2::internal_prelude }}
594
595 // The effective field type for parsing.
596 //
597 // Handles #[deftly(netdoc(default))], in which case we parse as if the field was Option,
598 // and substitute in the default at the end.
599 //
600 ${define F_EFFECTIVE_TYPE {
601 ${if all(fmeta(netdoc(default))) {
602 Option::<$ftype>
603 } else {
604 $ftype
605 }}
606 }}
607 ${define F_ITEM_SET_SELECTOR {
608 ItemSetSelector::<$F_EFFECTIVE_TYPE>::default()
609 }}
610
611 // NOTE! These keyword defines are simpler than the ones for NetdocParseable.
612 // Care must be taken if they are deduplicated as noted above.
613 // Field keyword as `&str`
614 ${define F_KEYWORD_STR { ${concat
615 ${fmeta(netdoc(keyword)) as str,
616 default ${kebab_case $fname}}
617 }}}
618 // Field keyword as `KeywordRef`
619 ${define F_KEYWORD { (KeywordRef::new_const($F_KEYWORD_STR)) }}
620
621 #[derive(Default, Debug)]
622 $tvis struct $<$tname NetdocParseAccumulator><$tdefgens> { $(
623 $fname: Option<$F_EFFECTIVE_TYPE>,
624 ) }
625
626 impl<$tgens> $P::NetdocParseableFields for $ttype {
627 type Accumulator = $<$ttype NetdocParseAccumulator>;
628
629 fn is_item_keyword(
630 #[allow(unused_variables)] // If there are no fields, this is unused
631 kw: $P::KeywordRef<'_>,
632 ) -> bool {
633 ${for fields {
634 kw == $F_KEYWORD ||
635 }}
636 false
637 }
638
639 fn accumulate_item(
640 #[allow(unused_variables)] // If there are no fields, this is unused
641 acc: &mut Self::Accumulator,
642 #[allow(unused_variables)] // If there are no fields, this is unused
643 item: $P::UnparsedItem<'_>,
644 ) -> $P::Result<(), $P::ErrorProblem> {
645 $(
646 if item.keyword() == $F_KEYWORD {
647 let selector = $F_ITEM_SET_SELECTOR;
648 ${if fmeta(netdoc(with)) {
649 let item = ${fmeta(netdoc(with)) as path}
650 ::${paste_spanned $fname from_unparsed}
651 (item)?;
652 } else if fmeta(netdoc(single_arg)) {
653 selector.${paste_spanned $fname check_item_argument_parseable}();
654 let item = ItemValueParseable::from_unparsed(item)?;
655 let (item,) = item;
656 } else {
657 selector.${paste_spanned $fname check_item_value_parseable}();
658 let item = ItemValueParseable::from_unparsed(item)?;
659 }}
660 selector.accumulate(&mut acc.$fname, item)
661 } else
662 )
663 {
664 panic!("accumulate_item called though is_intro_item_keyword returns false");
665 }
666 }
667
668 fn finish(
669 #[allow(unused_variables)] // If there are no fields, this is unused
670 acc: Self::Accumulator
671 ) -> $P::Result<Self, $P::ErrorProblem> {
672 $(
673 let $fpatname = $F_ITEM_SET_SELECTOR.finish(acc.$fname, $F_KEYWORD_STR)?;
674 ${if fmeta(netdoc(default)) {
675 let $fpatname = Option::unwrap_or_default($fpatname);
676 }}
677 )
678 Ok($vpat)
679 }
680 }
681}
682
683define_derive_deftly! {
684 /// Derive `FooSigned` from `Foo`
685 ///
686 /// Apply this derive to the main body struct `Foo`.
687 ///
688 /// Usually, provide suitable `.verify_...` methods.
689 ///
690 /// The body and signature types have to implement `Clone` and `Debug`.
691 ///
692 /// ### Top-level attributes:
693 ///
694 /// * **`#[deftly(netdoc(signature = "TYPE"))]`**:
695 /// Type of the signature(s) section.
696 ///
697 /// TYPE must implement `NetdocParseable`,
698 /// with `is_intro_item_keyword` reporting *every* signature keyword.
699 /// Normally this is achieved with
700 /// `#[derive_deftly(NetdocParseable)] #[deftly(netdoc(signatures))]`.
701 ///
702 /// ### Generated struct
703 ///
704 /// ```
705 /// # struct Foo; struct FooSignatures;
706 /// pub struct FooSigned {
707 /// body: Foo,
708 /// pub signatures: FooSignatures,
709 /// }
710 ///
711 /// # #[cfg(all())] { r##"
712 /// impl NetdocParseable for FooSigned { .. }
713 /// impl NetdocSigned for FooSigned { .. }
714 /// # "##; }
715 /// ```
716 //
717 // We don't make this a generic struct because the defining module (crate)
718 // will want to add verification methods, which means they must define the struct.
719 export NetdocSigned for struct, expect items, beta_deftly:
720
721 // Convenience alias for our prelude
722 ${define P { $crate::parse2::internal_prelude }}
723
724 // FooSignatures (type name)
725 ${define SIGS_TYPE { $< ${tmeta(netdoc(signatures)) as ty, default $<$ttype Signatures>} > }}
726
727 #[doc = ${concat "Signed (unverified) form of [`" $tname "`]"}]
728 ///
729 /// Embodies:
730 ///
731 #[doc = ${concat " * **[`" $tname "`]**: document body"}]
732 #[doc = ${concat " * **[`" $SIGS_TYPE "`]**: signatures"}]
733 ///
734 /// If this type was parsed from a document text,
735 /// the signatures have *not* yet been verified.
736 ///
737 /// Use a `.verify_...` method to obtain useable, verified, contents.
738 #[derive(Debug, Clone)]
739 $tvis struct $<$ttype Signed> {
740 /// The actual body
741 //
742 // Misuse is prevented by this field not being public.
743 // It can be accessed only in this module, where the verification functions are.
744 body: $ttype,
745
746 /// Signatures
747 $tvis signatures: $SIGS_TYPE,
748 }
749
750 impl<$tgens> $P::NetdocParseable for $<$ttype Signed> {
751 fn doctype_for_error() -> &'static str {
752 $ttype::doctype_for_error()
753 }
754
755 fn is_intro_item_keyword(kw: $P::KeywordRef<'_>) -> bool {
756 $ttype::is_intro_item_keyword(kw)
757 }
758
759 fn from_items<'s>(
760 input: &mut $P::ItemStream<'s>,
761 outer_stop: $P::stop_at!(),
762 ) -> $P::Result<$<$ttype Signed>, $P::ErrorProblem> {
763 input.parse_signed(outer_stop)
764 }
765 }
766
767 impl<$tgens> $P::NetdocSigned for $<$ttype Signed> {
768 type Body = $ttype;
769 type Signatures = $SIGS_TYPE;
770 fn inspect_unverified(&self) -> (&Self::Body, &Self::Signatures) {
771 (&self.body, &self.signatures)
772 }
773 fn unwrap_unverified(self) -> (Self::Body, Self::Signatures) {
774 (self.body, self.signatures)
775 }
776 fn from_parts(body: Self::Body, signatures: Self::Signatures) -> Self {
777 Self { body, signatures }
778 }
779 }
780}
781
782define_derive_deftly! {
783 /// Derive `ItemValueParseable`
784 ///
785 /// Fields in the struct are parsed from the keyword line arguments,
786 /// in the order they appear in the struct.
787 ///
788 /// ### Field type
789 ///
790 /// Each field should be:
791 ///
792 /// * `impl `[`ItemArgumentParseable`] (one argument),
793 /// * `Option<impl ItemArgumentParseable>` (one optional argument), or
794 /// * `Vec<impl ItemArgumentParseable>` (zero or more arguments).
795 ///
796 /// `ItemArgumentParseable` is implemented for every `impl FromStr`,
797 /// so `impl FromStr`, `Option<impl FromStr>` and `Vec<impl FromStr>`
798 /// are supported.
799 ///
800 /// For `Option` or `Vec`, we expect that *if* there are any further arguments,
801 /// they are for this field.
802 /// So absence of any optional argument means absence of following arguments,
803 /// and no arguments can follow a `Vec`.
804 ///
805 /// Some Tor netdocs have optional arguments followed by other data,
806 /// with unclear/ambiguous parsing rules.
807 /// These cases typically require manual implementation of [`ItemValueParseable`].
808 ///
809 /// (Multiplicity is implemented via types in the [`multiplicity`] module,
810 /// specifically [`ArgumentSetSelector`] and [`ArgumentSetMethods`].)
811 ///
812 /// ### Top-level attributes:
813 ///
814 /// * **`#[deftly(netdoc(no_extra_args))]**:
815 ///
816 /// Reject, rather than ignore, additional arguments found in the document
817 /// which aren't described by the struct.
818 ///
819 /// ### Field-level attributes:
820 ///
821 /// * **`#[deftly(netdoc(rest))]**:
822 ///
823 /// The field is the whole rest of the line.
824 /// Must come after any other normal argument fields.
825 ///
826 /// The field type must implement `FromStr`.
827 ///
828 /// * **`#[deftly(netdoc(object))]**:
829 ///
830 /// The field is the Object.
831 /// It must implement [`ItemObjectParseable`]
832 /// (or be `Option<impl ItemObjectParseable>`).
833 ///
834 /// Only allowed once.
835 /// If omittted, any object is rejected.
836 ///
837 /// * **`#[deftly(netdoc(object(label = "LABEL")))]**:
838 ///
839 /// Sets the expected label for an Object.
840 /// If not supplied, uses [`ItemObjectParseable::check_label`].
841 ///
842 /// * **`#[deftly(netdoc(with = "MODULE")]**:
843 ///
844 /// Instead of `ItemArgumentParseable`, the item is parsed with `MODULE::from_args`,
845 /// which must have the same signature as [`ItemArgumentParseable::from_args`].
846 ///
847 /// With `#[deftly(netdoc(rest))]`, FUNCTION replaces
848 /// `<FIELD AS FromStr>::from_str`.
849 ///
850 /// With `#[deftly(netdoc(objecte))]`, uses `MODULE::try_from`
851 /// must have the signature `fn(Vec<u8>) -> Result<OBJECT, _>;
852 /// like `TryFrom::<Vec<u8>>>::try_from`.
853 /// LABEL must also be specified
854 /// unless the object also implements `ItemObjectParseable`.
855 /// Errors from parsing will be discarded and replaced with
856 /// [`ErrorProblem::ObjectInvalidData`].
857 ///
858 /// * **`#[deftly(netdoc(sig_hash = "HASH_METHOD"))]**:
859 ///
860 /// This item is a signature item.
861 /// [`SignatureItemParseable`] will be implemented instead of [`ItemValueParseable`].
862 ///
863 /// This field is a document hash.
864 /// The hash will be computed using `HASH_METHOD`,
865 /// which will be resolved with `sig_hash_methods::*` in scope.
866 ///
867 /// `fn HASH_METHOD(body: &SignatureHashInputs) -> HASH_FIELD_VALUE`.
868 export ItemValueParseable for struct, expect items, beta_deftly:
869
870 ${define P { $crate::parse2::internal_prelude }}
871
872 ${defcond F_REST fmeta(netdoc(rest))}
873 ${defcond F_OBJECT fmeta(netdoc(object))}
874 ${defcond F_SIG_HASH fmeta(netdoc(sig_hash))}
875 ${defcond F_NORMAL not(any(F_REST, F_OBJECT, F_SIG_HASH))}
876
877 ${defcond T_IS_SIGNATURE not(approx_equal(${for fields { ${when F_SIG_HASH} 1 }}, {}))}
878 ${define TRAIT ${if T_IS_SIGNATURE { SignatureItemParseable } else { ItemValueParseable }}}
879 ${define METHOD ${if T_IS_SIGNATURE { from_unparsed_and_body } else { from_unparsed }}}
880
881 impl<$tgens> $P::$TRAIT for $ttype {
882 fn $METHOD<'s>(
883 mut input: $P::UnparsedItem<'s>,
884 ${if T_IS_SIGNATURE {
885 document_body: &SignatureHashInputs<'_>,
886 }}
887 ) -> $P::Result<Self, $P::EP>
888 {
889 #[allow(unused_imports)] // false positive when macro is used with prelude in scope
890 use $P::*;
891
892 let object = input.object();
893 #[allow(unused)]
894 let mut args = input.args_mut();
895 $(
896 let $fpatname = ${select1
897 F_NORMAL { {
898 let selector = ArgumentSetSelector::<$ftype>::default();
899 ${if not(fmeta(netdoc(with))) {
900 selector.${paste_spanned $fname check_argument_value_parseable}();
901 }}
902 selector.parse_with(
903 &mut args,
904 stringify!($fname),
905 ${fmeta(netdoc(with))
906 as path,
907 default { ItemArgumentParseable::from_args }},
908 )?
909 } }
910 F_OBJECT { {
911 let selector = ObjectSetSelector::<$ftype>::default();
912 let object = object.map(|object| {
913 let data = object.decode_data()?;
914 ${if fmeta(netdoc(object(label))) {
915 if object.label() != ${fmeta(netdoc(object(label))) as str} {
916 return Err(EP::ObjectIncorrectLabel)
917 }
918 } else {
919 selector.check_label(object.label())?;
920 }}
921 ${if fmeta(netdoc(with)) {
922 ${fmeta(netdoc(with)) as path}::${paste_spanned $fname try_from}
923 (data)
924 .map_err(|_| EP::ObjectInvalidData)
925 } else {
926 selector.${paste_spanned $fname check_object_parseable}();
927 ItemObjectParseable::from_bytes(&data)
928 }}
929 }).transpose()?;
930 selector.resolve_option(object)?
931 } }
932 F_REST { {
933 // consumes `args`, leading to compile error if the rest field
934 // isn't last (or is combined with no_extra_args).
935 let args_consume = args;
936 ${fmeta(netdoc(with))
937 as path,
938 default { <$ftype as FromStr>::from_str }}(args_consume.into_remaining())
939 .map_err(|_| EP::InvalidArgument { field: stringify!($fname) })?
940 } }
941 F_SIG_HASH { {
942 #[allow(unused_imports)]
943 use $P::sig_hash_methods::*;
944 ${fmeta(netdoc(sig_hash)) as path}(&document_body)
945 } }
946 };
947 )
948 ${if approx_equal({}, $( ${when F_OBJECT} $fname )) {
949 if object.is_some() {
950 return Err(EP::ObjectUnexpected);
951 }
952 }}
953 ${if tmeta(netdoc(no_extra_args)) {
954 args.reject_extra_args()?;
955 }}
956 Ok($tname { $( $fname: $fpatname, ) })
957 }
958 }
959}