tor_netdoc/
parse2.rs

1//! New netdoc parsing arrangements, with `derive`
2//!
3//! # Parsing principles
4//!
5//! A parseable network document is a type implementing [`NetdocParseable`].
6//! usually via the
7//! [`NetdocParseable` derive=deftly macro`](crate::derive_deftly_template_NetdocParseable).
8//!
9//! A document type is responsible for recognising its own heading item.
10//! Its parser will also be told other of structural items that it should not consume.
11//! The structural lines can then be used to pass control to the appropriate parser.
12//!
13//! # Ordering
14//!
15//! We don't parse things into a sorted order.
16//! Sorting will be done on output.
17// TODO we don't implement deriving output yet.
18//!
19//! # Types, and signature handling
20//!
21//! Most top-level network documents are signed somehow.
22//! In this case there are three types:
23//!
24//!   * **`FooSigned`**: a signed `Foo`, with its signatures, not yet verified.
25//!     Implements [`NetdocSigned`],
26//!     typically by invoking the
27//!     [`NetdocSigned` derive macro](crate::derive_deftly_template_NetdocSigned)
28//!     on `Foo`.
29//!
30//!     Type-specific methods are provided for verification,
31//!     to obtain a `Foo`.
32//!
33//!   * **`Foo`**: the body data for the document.
34//!     This doesn't contain any signatures.
35//!     Having one of these to play with means signatures have already been validated.
36//!     Implement `NetdocParseable`, via
37//!     [derive](crate::derive_deftly_template_NetdocParseable).
38//!
39//!   * **`FooSignatures`**: the signatures for a `Foo`.
40//!     Implement `NetdocParseable`, via
41//!     [derive](crate::derive_deftly_template_NetdocParseable),
42//!     with `#[deftly(netdoc(signatures))]`.
43//!
44//! # Naming conventions
45//!
46//!   * `DocumentName`: important types,
47//!     including network documents or sub-documents,
48//!     eg `NetworkStatsuMd` and `RouterVote`,
49//!     and types that are generally useful.
50//!   * `NddDoucmnetSection`: sections and sub-documents
51//!     that the user won't normally need to name.
52//!   * `NdiItemValue`: parsed value for a network document Item.
53//!     eg `NdiVoteStatus` representing the whole of the RHS of a `vote-status` Item.
54//!     Often not needed since `ItemValueParseable` is implemented for suitable tuples.
55//!   * `NdaArgumentValue`: parsed value for a single argument;
56//!     eg `NdaVoteStatus` representing the `vote` or `status` argument.
57//!
58//! # Relationship to tor_netdoc::parse
59//!
60//! This is a completely new parsing approach, based on different principles.
61//! The key principle is the recognition of "structural keywords",
62//! recursively within a parsing stack, via the p`NetdocParseable`] trait.
63//!
64//! This allows the parser to be derived.  We have type-driven parsing
65//! of whole Documents, Items, and their Arguments and Objects,
66//! including of their multiplicity.
67//!
68//! The different keyword handling means we can't use most of the existing lexer,
69//! and need new item parsing API:
70//!
71//!  * [`NetdocParseable`] trait.
72//!  * [`KeywordRef`] type.
73//!  * [`ItemStream`], [`UnparsedItem`], [`ArgumentStream`], [`UnparsedObject`].
74//!
75//! The different error handling means we have our own error types.
76//! (The crate's existing parse errors have information that we don't track,
77//! and is also a portmanteau error for parsing, writing, and other functions.)
78//!
79//! Document signing is handled in a more abstract way.
80//!
81//! Some old netdoc constructs are not supported.
82//! For example, the obsolete `opt` prefix on safe-to-ignore Items.
83//! The parser may make different decisions about netdocs with anomalous item ordering.
84
85#[doc(hidden)]
86#[macro_use]
87pub mod internal_prelude;
88
89#[macro_use]
90mod structural;
91
92#[macro_use]
93mod derive;
94
95mod error;
96mod impls;
97pub mod keyword;
98mod lex;
99mod lines;
100pub mod multiplicity;
101mod signatures;
102mod traits;
103
104pub mod poc;
105
106#[cfg(test)]
107mod test;
108
109use internal_prelude::*;
110
111pub use error::{ErrorProblem, ParseError, VerifyFailed};
112pub use impls::times::NdaSystemTimeDeprecatedSyntax;
113pub use keyword::KeywordRef;
114pub use lex::{ArgumentStream, ItemStream, NoFurtherArguments, UnparsedItem, UnparsedObject};
115pub use lines::{Lines, Peeked, StrExt};
116pub use signatures::{
117    SignatureHashInputs, SignatureItemParseable, check_validity_time, sig_hash_methods,
118};
119pub use structural::{StopAt, StopPredicate};
120pub use traits::{ItemArgumentParseable, ItemObjectParseable, ItemValueParseable, NetdocParseable};
121
122#[doc(hidden)]
123pub use derive::netdoc_parseable_derive_debug;
124
125//---------- parser ----------
126
127/// Common code for `parse_netdoc` and `parse_netdoc_multiple`
128///
129/// Creates the `ItemStream`, calls `parse_completely`, and handles errors.
130fn parse_internal<T, D: NetdocParseable>(
131    input: &str,
132    file: &str,
133    parse_completely: impl FnOnce(&mut ItemStream) -> Result<T, ErrorProblem>,
134) -> Result<T, ParseError> {
135    let mut items = ItemStream::new(input)?;
136    parse_completely(&mut items).map_err(|problem| ParseError {
137        problem,
138        doctype: D::doctype_for_error(),
139        file: file.to_owned(),
140        lno: items.lno_for_error(),
141    })
142}
143
144/// Parse a network document - **toplevel entrypoint**
145pub fn parse_netdoc<D: NetdocParseable>(input: &str, file: &str) -> Result<D, ParseError> {
146    parse_internal::<_, D>(input, file, |items| {
147        let doc = D::from_items(items, StopAt(false))?;
148        if let Some(_kw) = items.peek_keyword()? {
149            return Err(EP::MultipleDocuments);
150        }
151        Ok(doc)
152    })
153}
154
155/// Parse a network document - **toplevel entrypoint**
156pub fn parse_netdoc_multiple<D: NetdocParseable>(
157    input: &str,
158    file: &str,
159) -> Result<Vec<D>, ParseError> {
160    parse_internal::<_, D>(input, file, |items| {
161        let mut docs = vec![];
162        while items.peek_keyword()?.is_some() {
163            let doc = D::from_items(items, StopAt(false))?;
164            docs.push(doc);
165        }
166        Ok(docs)
167    })
168}