Skip to main content

tor_netdoc/parse2/
traits.rs

1//! Core model for netdoc parsing
2
3use super::*;
4
5/// A document or section that can be parsed
6///
7/// Normally [derived](derive_deftly_template_NetdocParseable).
8pub trait NetdocParseable: Sized {
9    /// Document type for errors, normally its intro keyword
10    fn doctype_for_error() -> &'static str;
11
12    /// Is `Keyword` an intro Item Keyword for this kind of document?
13    ///
14    /// This is used with 1-keyword lookahead, to allow us to push or pop
15    /// the parsing state into or out of a sub-document.
16    ///
17    /// For signatures sections, this should report *every* recognised keyword.
18    fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool;
19
20    /// Parse the document from a stream of Items
21    ///
22    /// Should stop before reading any keyword matching `stop_at`.
23    /// (Except, right at the start.)
24    ///
25    /// Should also stop before reading a 2nd intro keyword,
26    /// so that successive calls to this function can parse
27    /// successive sub-documents of this kind.
28    ///
29    /// Otherwise, should continue until EOF.
30    ///
31    /// Must check whether the first item is this document's `is_intro_item_keyword`,
32    /// and error if not.
33    fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, ErrorProblem>;
34
35    /// Is `Keyword` a structural keyword for this kind of document?
36    ///
37    /// Returns `Some(IsStructural)` for:
38    ///   - this type's intro item keyword (`is_intro_item_keyword`)
39    ///   - the intro items or structural items for any of its sub-documents and sections
40    ///     `#[deftly(netdoc(subdoc))]`
41    ///
42    /// (This means it returns true for *any* item in a signatures subdocument
43    /// ie any field in a struct decorated `#[deftly(netdoc(signatures))]`
44    /// since those are considered intro items.)
45    ///
46    /// Used for avoiding parsing ambiguity when a netdoc from a semi-trusted source
47    /// is embedded into another netdoc.
48    /// See <https://spec.torproject.org/dir-spec/creating-key-certificates.html#nesting>.
49    ///
50    /// # Return type and relationship to `is_intro_item_keyword`
51    ///
52    /// Returns `Option<IsStructural>`
53    /// so that it has a different type to [`NetdocParseable::is_intro_item_keyword`],
54    /// preventing accidental confusion between the two kinds of keyword property enquiry.
55    ///
56    /// Our parsing algorithms actually only care about *intro keywords* for sub-documents.
57    /// We don't need to worry about anything else;
58    /// notably, we don't need to care about other structural items within those sub-documents.
59    ///
60    /// Except for authcerts in votes,, which are nested documents
61    /// with partially trusted content.
62    /// That is what this method is for.
63    ///
64    /// So, we privilege `is_intro_item_keyword` by having it return `bool`
65    /// and by the affordances in [`StopAt`].
66    fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural>;
67}
68
69/// A collection of fields that can be parsed within a section
70///
71/// None of the items can be structural.
72///
73/// Normally [derived](derive_deftly_template_NetdocParseableFields).
74pub trait NetdocParseableFields: Sized {
75    /// The partially-parsed set of items.
76    type Accumulator: Sized + Debug + Send + Sync + 'static;
77
78    /// Is this one of the keywords in this struct
79    fn is_item_keyword(kw: KeywordRef<'_>) -> bool;
80
81    /// Accumulate an item in this struct
82    ///
83    /// # Panics
84    ///
85    /// The caller must have first checked the `item`'s keyword with `is_item_keyword`.
86    /// If this *isn't* an item for this structure, may panic.
87    fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem<'_>) -> Result<(), EP>;
88
89    /// Finish
90    ///
91    /// Resolves the `Accumulator` into the output type.
92    /// Generally, this means throwing an error if expected fields were not present.
93    fn finish(acc: Self::Accumulator) -> Result<Self, EP>;
94}
95
96/// An item (value) that can be parsed in a netdoc
97///
98/// This is the type `T` of a field `item: T` in a netdoc type.
99///
100/// An implementation is provided for tuples of `ItemArgumentParseable`,
101/// which parses each argument in turn,
102/// ignores additional arguments,
103/// and rejects any Object.
104///
105/// Typically derived with
106/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
107///
108/// Signature items are special, and implement [`SignatureItemParseable`] instead.
109pub trait ItemValueParseable: Sized {
110    /// Parse the item's value
111    fn from_unparsed(item: UnparsedItem<'_>) -> Result<Self, ErrorProblem>;
112}
113
114/// An (individual) argument that can be parsed from in a netdoc
115///
116/// An implementations is provided for **`T: FromStr`**,
117/// which expects a single argument and passes it to `FromStr`.
118///
119/// For netdoc arguments whose specified syntax spans multiple space-separated words,
120/// use a manual implementation or a wrapper type.
121pub trait ItemArgumentParseable: Sized {
122    /// Parse the argument
123    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError>;
124}
125
126/// An Object value that be parsed from a netdoc
127pub trait ItemObjectParseable: Sized {
128    /// Check that the Label is right
129    fn check_label(label: &str) -> Result<(), ErrorProblem>;
130
131    /// Convert the bytes of the Object (which was present) into the actual value
132    ///
133    /// `input` has been base64-decoded.
134    fn from_bytes(input: &[u8]) -> Result<Self, ErrorProblem>;
135}
136
137/// Token indicating that a keyword is structural
138///
139/// Returned by [`NetdocParseable::is_structural_keyword`]
140#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
141#[allow(clippy::exhaustive_structs)]
142pub struct IsStructural;
143
144//---------- provided blanket impls ----------
145
146impl<T: ItemArgumentParseable> ItemArgumentParseable for Arc<T> {
147    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError> {
148        T::from_args(args).map(Arc::new)
149    }
150}
151
152impl<T: NormalItemArgument + FromStr> ItemArgumentParseable for T {
153    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
154        let v = args
155            .next()
156            .ok_or(AE::Missing)?
157            .parse()
158            .map_err(|_e| AE::Invalid)?;
159        Ok(v)
160    }
161}
162
163impl<T: ItemValueParseable> ItemValueParseable for Arc<T> {
164    fn from_unparsed(item: UnparsedItem<'_>) -> Result<Self, ErrorProblem> {
165        T::from_unparsed(item).map(Arc::new)
166    }
167}
168
169impl<T: NetdocParseable> NetdocParseable for Arc<T> {
170    fn doctype_for_error() -> &'static str {
171        T::doctype_for_error()
172    }
173    fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
174        T::is_intro_item_keyword(kw)
175    }
176    fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
177        T::is_structural_keyword(kw)
178    }
179    fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, EP> {
180        T::from_items(input, stop_at).map(Arc::new)
181    }
182}
183impl<T: NetdocParseableFields> NetdocParseableFields for Arc<T> {
184    type Accumulator = T::Accumulator;
185    fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
186        T::is_item_keyword(kw)
187    }
188    fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem<'_>) -> Result<(), EP> {
189        T::accumulate_item(acc, item)
190    }
191    fn finish(acc: Self::Accumulator) -> Result<Self, EP> {
192        T::finish(acc).map(Arc::new)
193    }
194}
195
196/// implement [`ItemValueParseable`] for a particular tuple size
197macro_rules! item_value_parseable_for_tuple {
198    { $($i:literal)* } => { paste! {
199        impl< $( [<T$i>]: ItemArgumentParseable, )* >
200            ItemValueParseable for ( $( [<T$i>], )* )
201        {
202            fn from_unparsed(
203                #[allow(unused_mut)]
204                mut item: UnparsedItem<'_>,
205            ) -> Result<Self, ErrorProblem> {
206                let r = ( $(
207                    <[<T$i>] as ItemArgumentParseable>::from_args(
208                        item.args_mut(),
209                    ).map_err(item.args().error_handler(stringify!($i)))?,
210                )* );
211                item.check_no_object()?;
212                Ok(r)
213            }
214        }
215    } }
216}
217
218item_value_parseable_for_tuple! {}
219item_value_parseable_for_tuple! { 0 }
220item_value_parseable_for_tuple! { 0 1 }
221item_value_parseable_for_tuple! { 0 1 2 }
222item_value_parseable_for_tuple! { 0 1 2 3 }
223item_value_parseable_for_tuple! { 0 1 2 3 4 }
224item_value_parseable_for_tuple! { 0 1 2 3 4 5 }
225item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 }
226item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 }
227item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 8 }
228item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 8 9 }