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
62/// Problem found when parsing a document
63///
64/// Just the nature of the problem, including possibly which field or argument
65/// if that's necessary to disambiguate, but not including location in the document.
66///
67/// We are quite minimal:
68/// we do not report the `Display` of argument parse errors, for example.
69///
70/// The column, if there is one, is not printed by the `Display` impl.
71/// This is so that it can be properly formatted as part of a file-and-line-and-column,
72/// by the `Display` impl for [`ParseError`].
73#[derive(Error, Copy, Clone, Debug, Eq, PartialEq, Deftly)]
74#[derive_deftly(ErrorProblem)]
75#[non_exhaustive]
76pub enum ErrorProblem {
77    /// Empty document
78    #[error("empty document")]
79    EmptyDocument,
80    /// Wrong document type
81    #[error("wrong document type")]
82    WrongDocumentType,
83    /// Multiple top-level documents
84    #[error("multiple top-level documents")]
85    MultipleDocuments,
86    /// Item missing required base64-encoded Object
87    #[error("item missing required base64-encoded Object")]
88    MissingObject,
89    /// Item repeated when not allowed
90    #[error("item repeated when not allowed")]
91    ItemRepeated,
92    /// Item forbidden (in this kind of document or location)
93    #[error("item forbidden (in this kind of document or location)")]
94    ItemForbidden,
95    /// Item repeated when not allowed
96    #[error("item repeated when not allowed")]
97    ItemMisplacedAfterSignature,
98    /// Document contains nul byte
99    #[error("document contains nul byte")]
100    NulByte,
101    /// Item keyword line starts with whitespace
102    #[error("item keyword line starts with whitespace")]
103    KeywordLineStartsWithWhitespace,
104    /// No keyword when item keyword line expected
105    #[error("no keyword when item keyword line expected")]
106    MissingKeyword,
107    /// No keyword when item keyword line expected
108    #[error("no keyword when item keyword line expected: {0}")]
109    InvalidKeyword(#[from] keyword::InvalidKeyword),
110    /// Missing item {keyword}
111    #[error("missing item {keyword}")]
112    MissingItem {
113        /// Keyword for item that was missing
114        keyword: &'static str,
115    },
116    /// Missing argument {field}
117    #[error("missing argument {field}")]
118    MissingArgument {
119        /// Field name for argument that was missing
120        field: &'static str,
121    },
122    /// Invalid value for argument {field}
123    #[error("invalid value for argument {field}")]
124    InvalidArgument {
125        /// Field name for argument that had invalid value
126        field: &'static str,
127        /// Column of the bad argument value.
128        column: usize,
129    },
130    /// Unexpected additional argument(s)
131    #[error("too many arguments")]
132    UnexpectedArgument {
133        /// Column of the unexpdcted argument value.
134        column: usize,
135    },
136    /// Base64-encoded Object footer not found
137    #[error("base64-encoded Object footer not found")]
138    ObjectMissingFooter,
139    /// Base64-encoded Object END label does not match BEGIN
140    #[error("base64-encoded Object END label does not match BEGIN")]
141    ObjectMismatchedLabels,
142    /// Base64-encoded Object END label does not match BEGIN
143    #[error("base64-encoded Object label is not as expected")]
144    ObjectIncorrectLabel,
145    /// Base64-encoded Object has incorrectly formatted delimiter lines
146    #[error("base64-encoded Object has incorrectly formatted delimiter lines")]
147    InvalidObjectDelimiters,
148    /// Base64-encoded Object found where none expected
149    #[error("base64-encoded Object found where none expected")]
150    ObjectUnexpected,
151    /// Base64-encoded Object contains invalid base64
152    #[error("base64-encoded Object contains invalid base64")]
153    ObjectInvalidBase64,
154    /// Base64-encoded Object contains valid base64 specifying invalid data
155    #[error("base64-encoded Object contains invalid data")]
156    ObjectInvalidData,
157    /// Other parsing proble
158    #[error("other problem: {0}")]
159    OtherBadDocument(&'static str),
160    /// Internal error in document parser
161    #[error("internal error in document parser: {0}")]
162    Internal(&'static str),
163    /// Invalid API usage
164    #[error("document parsing API misused: {0}")]
165    BadApiUsage(&'static str),
166}
167
168/// Problem found when parsing an individual argument in a netdoc keyword item
169///
170/// Just the nature of the problem.
171/// We are quite minimal:
172/// we do not report the `Display` of argument parse errors, for example.
173///
174/// The field name and location in the line will be added when this is converted
175/// to an `ErrorProblem`.
176#[derive(Error, Copy, Clone, Debug, Eq, PartialEq)]
177#[non_exhaustive]
178pub enum ArgumentError {
179    /// Missing argument {field}
180    #[error("missing argument")]
181    Missing,
182    /// Invalid value for argument {field}
183    #[error("invalid value for argument")]
184    Invalid,
185    /// Unexpected additional argument(s)
186    #[error("too many arguments")]
187    Unexpected,
188}
189
190/// An unexpected argument was encountered
191///
192/// Returned by [`ArgumentStream::reject_extra_args`],
193/// and convertible to [`ErrorProblem`] and [`ArgumentError`].
194///
195/// Includes some information about the location of the error,
196/// as is necessary for those conversions.
197#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
198pub struct UnexpectedArgument {
199    /// Column of the start of the unexpected argument.
200    pub(super) column: usize,
201}
202
203/// Error from signature verification (and timeliness check)
204#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum VerifyFailed {
207    /// Signature verification failed
208    #[error("netdoc signature verification failed")]
209    VerifyFailed,
210    /// Document is too new - clock skew?
211    #[error("document is too new - clock skew?")]
212    TooNew,
213    /// Document is too old
214    #[error("document is too old")]
215    TooOld,
216    /// Document not signed by the right testator (or too few known testators)
217    #[error("document not signed by the right testator (or too few known testators)")]
218    InsufficientTrustedSigners,
219    /// document has inconsistent content
220    #[error("document has inconsistent content")]
221    Inconsistent,
222    /// inner parse failure
223    #[error("parsing problem in embedded document")]
224    ParseEmbedded(#[from] ErrorProblem),
225    /// Something else is wrong
226    #[error("document has uncategorised problem found during verification")]
227    Other,
228}
229
230impl From<signature::Error> for VerifyFailed {
231    fn from(_: signature::Error) -> VerifyFailed {
232        VerifyFailed::VerifyFailed
233    }
234}
235
236define_derive_deftly! {
237    /// Bespoke derives for `ErrorProblem`
238    ///
239    /// Currently, provides the `column` function.
240    ErrorProblem:
241
242    impl ErrorProblem {
243        /// Obtain the `column` of this error
244        //
245        // Our usual getters macro is `amplify` but it doesn't support conditional
246        // getters of enum fields, like we want here.
247        pub fn column(&self) -> Option<usize> {
248            Some(match self {
249              // Iterate over all fields in all variants.  There's only one field `column`
250              // in any variant, so this is precisely all variants with such a field.
251              ${for fields {
252                ${when approx_equal($fname, column)}
253                $vtype { column, .. } => *column,
254              }}
255                _ => return None,
256            })
257        }
258    }
259}
260use derive_deftly_template_ErrorProblem;
261
262impl From<UnexpectedArgument> for ErrorProblem {
263    fn from(ua: UnexpectedArgument) -> ErrorProblem {
264        EP::UnexpectedArgument { column: ua.column }
265    }
266}
267
268impl From<UnexpectedArgument> for ArgumentError {
269    fn from(_ua: UnexpectedArgument) -> ArgumentError {
270        AE::Unexpected
271    }
272}