Skip to main content

tor_netdoc/parse2/
error.rs

1#![allow(clippy::useless_format)] // TODO MSRV 1.89, see ParseError, below
2//! Parsing errors
3//!
4//! # Error philosophy
5//!
6//! We don't spend a huge amount of effort producing precise and informative errors.
7//!
8//! We report:
9//!
10//!  * A line number in the document where the error occurred.
11//!    For a problem with an item keyword line, that line is reported.
12//!    For an Object, a line somewhere in or just after the object is reported.
13//!
14//!  * The column number of an invalid or unexpected item argument.
15//!
16//!  * The expected keyword of a missing item.
17//!
18//!  * The struct field name of a missing or invalid argument.
19//!
20//!  * The file name (might be a nominal file name)
21//!
22//!  * What kind of document we were trying to parse.
23//!
24//! We do not report:
25//!
26//!  * Byte offsets.
27//!
28//!  * Any more details of the error for syntactically invalid arguments,
29//!    bad base64 or bad binary data, etc. (eg we discard the `FromStr::Err`)
30//!
31//! This saves a good deal of work.
32
33use super::*;
34
35/// Error encountered when parsing a document, including its location
36#[derive(Error, Clone, Debug, Eq, PartialEq)]
37#[error(
38    "failed to parse network document, type {doctype}: {file}:{lno}{}",
39    match column {
40        // TODO MSRV 1.89 (or maybe earlier): change format! to format_args!
41        // https://releases.rs/docs/1.89.0/#libraries
42        // 2x here, and remove the clippy allow at the module top-level.
43        Some(column) => format!(".{}", *column),
44        None => format!(""),
45    },
46)]
47#[non_exhaustive]
48pub struct ParseError {
49    /// What the problem was
50    #[source]
51    pub problem: ErrorProblem,
52    /// The document type, from `NetdocParseable::doctype_for_error`
53    pub doctype: &'static str,
54    /// Where the document came from, in human-readable form, filename or `<...>`
55    pub file: String,
56    /// Line number
57    pub lno: usize,
58    /// Column number
59    pub column: Option<usize>,
60}
61
62impl ParseError {
63    /// Constructs a new [`ParseError`].
64    pub fn new(
65        problem: ErrorProblem,
66        doctype: &'static str,
67        file: &str,
68        lno: usize,
69        column: Option<usize>,
70    ) -> Self {
71        Self {
72            problem,
73            doctype,
74            file: file.to_owned(),
75            lno,
76            column,
77        }
78    }
79}
80
81/// Problem found when parsing a document
82///
83/// Just the nature of the problem, including possibly which field or argument
84/// if that's necessary to disambiguate, but not including location in the document.
85///
86/// We are quite minimal:
87/// we do not report the `Display` of argument parse errors, for example.
88///
89/// The column, if there is one, is not printed by the `Display` impl.
90/// This is so that it can be properly formatted as part of a file-and-line-and-column,
91/// by the `Display` impl for [`ParseError`].
92#[derive(Error, Copy, Clone, Debug, Eq, PartialEq, Deftly)]
93#[derive_deftly(ErrorProblem)]
94#[non_exhaustive]
95pub enum ErrorProblem {
96    /// Empty document
97    #[error("empty document")]
98    EmptyDocument,
99    /// Wrong document type
100    #[error("wrong document type")]
101    WrongDocumentType,
102    /// Multiple top-level documents
103    #[error("multiple top-level documents")]
104    MultipleDocuments,
105    /// Item missing required base64-encoded Object
106    #[error("item missing required base64-encoded Object")]
107    MissingObject,
108    /// Item repeated when not allowed
109    #[error("item repeated when not allowed")]
110    ItemRepeated,
111    /// Item forbidden (in this kind of document or location)
112    #[error("item forbidden (in this kind of document or location)")]
113    ItemForbidden,
114    /// Item repeated when not allowed
115    #[error("item repeated when not allowed")]
116    ItemMisplacedAfterSignature,
117    /// Document contains nul byte
118    #[error("document contains nul byte")]
119    NulByte,
120    /// Item keyword line starts with whitespace
121    #[error("item keyword line starts with whitespace")]
122    KeywordLineStartsWithWhitespace,
123    /// No keyword when item keyword line expected
124    #[error("no keyword when item keyword line expected")]
125    MissingKeyword,
126    /// No keyword when item keyword line expected
127    #[error("no keyword when item keyword line expected: {0}")]
128    InvalidKeyword(#[from] keyword::InvalidKeyword),
129    /// Missing item {keyword}
130    #[error("missing item {keyword}")]
131    MissingItem {
132        /// Keyword for item that was missing
133        keyword: &'static str,
134    },
135    /// Missing argument {field}
136    #[error("missing argument {field}")]
137    MissingArgument {
138        /// Field name for argument that was missing
139        field: &'static str,
140    },
141    /// Invalid value for argument {field}
142    #[error("invalid value for argument {field}")]
143    InvalidArgument {
144        /// Field name for argument that had invalid value
145        field: &'static str,
146        /// Column of the bad argument value.
147        column: usize,
148    },
149    /// Unexpected additional argument(s)
150    #[error("too many arguments")]
151    UnexpectedArgument {
152        /// Column of the unexpdcted argument value.
153        column: usize,
154    },
155    /// Base64-encoded Object footer not found
156    #[error("base64-encoded Object footer not found")]
157    ObjectMissingFooter,
158    /// Base64-encoded Object END label does not match BEGIN
159    #[error("base64-encoded Object END label does not match BEGIN")]
160    ObjectMismatchedLabels,
161    /// Base64-encoded Object END label does not match BEGIN
162    #[error("base64-encoded Object label is not as expected")]
163    ObjectIncorrectLabel,
164    /// Base64-encoded Object has incorrectly formatted delimiter lines
165    #[error("base64-encoded Object has incorrectly formatted delimiter lines")]
166    InvalidObjectDelimiters,
167    /// Base64-encoded Object found where none expected
168    #[error("base64-encoded Object found where none expected")]
169    ObjectUnexpected,
170    /// Base64-encoded Object contains invalid base64
171    #[error("base64-encoded Object contains invalid base64")]
172    ObjectInvalidBase64,
173    /// Base64-encoded Object contains valid base64 specifying invalid data
174    #[error("base64-encoded Object contains invalid data")]
175    ObjectInvalidData,
176    /// Other parsing problem
177    #[error("other problem: {0}")]
178    OtherBadDocument(&'static str),
179    /// Internal error in document parser
180    #[error("internal error in document parser: {0}")]
181    Internal(&'static str),
182    /// Invalid API usage
183    #[error("document parsing API misused: {0}")]
184    BadApiUsage(&'static str),
185}
186
187/// Problem found when parsing an individual argument in a netdoc keyword item
188///
189/// Just the nature of the problem.
190/// We are quite minimal:
191/// we do not report the `Display` of argument parse errors, for example.
192///
193/// The field name and location in the line will be added when this is converted
194/// to an `ErrorProblem`.
195#[derive(Error, Copy, Clone, Debug, Eq, PartialEq)]
196#[non_exhaustive]
197pub enum ArgumentError {
198    /// Missing argument {field}
199    #[error("missing argument")]
200    Missing,
201    /// Invalid value for argument {field}
202    #[error("invalid value for argument")]
203    Invalid,
204    /// Unexpected additional argument(s)
205    #[error("too many arguments")]
206    Unexpected,
207}
208
209/// An unexpected argument was encountered
210///
211/// Returned by [`ArgumentStream::reject_extra_args`],
212/// and convertible to [`ErrorProblem`] and [`ArgumentError`].
213///
214/// Includes some information about the location of the error,
215/// as is necessary for those conversions.
216#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
217pub struct UnexpectedArgument {
218    /// Column of the start of the unexpected argument.
219    pub(super) column: usize,
220}
221
222/// Error from signature verification (and timeliness check)
223#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
224#[non_exhaustive]
225pub enum VerifyFailed {
226    /// Signature verification failed
227    #[error("netdoc signature verification failed")]
228    VerifyFailed,
229    /// Document is too new - clock skew?
230    #[error("document is too new - clock skew?")]
231    TooNew,
232    /// Document is too old
233    #[error("document is too old")]
234    TooOld,
235    /// Document not signed by the right testator (or too few known testators)
236    #[error("document not signed by the right testator (or too few known testators)")]
237    InsufficientTrustedSigners,
238    /// document has inconsistent content
239    #[error("document has inconsistent content")]
240    Inconsistent,
241    /// inner parse failure
242    #[error("parsing problem in embedded document")]
243    ParseEmbedded(#[from] ErrorProblem),
244    /// Something else is wrong
245    #[error("document has uncategorised problem found during verification")]
246    Other,
247    /// Bug
248    #[error("verification prevented by software bug")]
249    Bug,
250}
251
252impl From<signature::Error> for VerifyFailed {
253    fn from(_: signature::Error) -> VerifyFailed {
254        VerifyFailed::VerifyFailed
255    }
256}
257
258define_derive_deftly! {
259    /// Bespoke derives for `ErrorProblem`
260    ///
261    /// Currently, provides the `column` function.
262    ErrorProblem:
263
264    impl ErrorProblem {
265        /// Obtain the `column` of this error
266        //
267        // Our usual getters macro is `amplify` but it doesn't support conditional
268        // getters of enum fields, like we want here.
269        pub fn column(&self) -> Option<usize> {
270            Some(match self {
271              // Iterate over all fields in all variants.  There's only one field `column`
272              // in any variant, so this is precisely all variants with such a field.
273              ${for fields {
274                ${when approx_equal($fname, column)}
275                $vtype { column, .. } => *column,
276              }}
277                _ => return None,
278            })
279        }
280    }
281}
282use derive_deftly_template_ErrorProblem;
283
284impl From<UnexpectedArgument> for ErrorProblem {
285    fn from(ua: UnexpectedArgument) -> ErrorProblem {
286        EP::UnexpectedArgument { column: ua.column }
287    }
288}
289
290impl From<UnexpectedArgument> for ArgumentError {
291    fn from(_ua: UnexpectedArgument) -> ArgumentError {
292        AE::Unexpected
293    }
294}