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    Other(&'static str),
160}
161
162/// Problem found when parsing an individual argument in a netdoc keyword item
163///
164/// Just the nature of the problem.
165/// We are quite minimal:
166/// we do not report the `Display` of argument parse errors, for example.
167///
168/// The field name and location in the line will be added when this is converted
169/// to an `ErrorProblem`.
170#[derive(Error, Copy, Clone, Debug, Eq, PartialEq)]
171#[non_exhaustive]
172pub enum ArgumentError {
173    /// Missing argument {field}
174    #[error("missing argument")]
175    Missing,
176    /// Invalid value for argument {field}
177    #[error("invalid value for argument")]
178    Invalid,
179    /// Unexpected additional argument(s)
180    #[error("too many arguments")]
181    Unexpected,
182}
183
184/// An unexpected argument was encountered
185///
186/// Returned by [`ArgumentStream::reject_extra_args`],
187/// and convertible to [`ErrorProblem`] and [`ArgumentError`].
188///
189/// Includes some information about the location of the error,
190/// as is necessary for those conversions.
191#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
192pub struct UnexpectedArgument {
193    /// Column of the start of the unexpected argument.
194    pub(super) column: usize,
195}
196
197/// Error from signature verification (and timeliness check)
198#[derive(Error, Debug, Clone, Copy)]
199#[non_exhaustive]
200pub enum VerifyFailed {
201    /// Signature verification failed
202    #[error("netdoc signature verification failed")]
203    VerifyFailed,
204    /// Document is too new - clock skew?
205    #[error("document is too new - clock skew?")]
206    TooNew,
207    /// Document is too old
208    #[error("document is too old")]
209    TooOld,
210    /// Document not signed by the right testator (or too few known testators)
211    #[error("document not signed by the right testator (or too few known testators)")]
212    InsufficientTrustedSigners,
213    /// document has inconsistent content
214    #[error("document has inconsistent content")]
215    Inconsistent,
216    /// Something else is wrong
217    #[error("document has uncategorised problem found during verification")]
218    Other,
219}
220
221impl From<signature::Error> for VerifyFailed {
222    fn from(_: signature::Error) -> VerifyFailed {
223        VerifyFailed::VerifyFailed
224    }
225}
226
227define_derive_deftly! {
228    /// Bespoke derives for `ErrorProblem`
229    ///
230    /// Currently, provides the `column` function.
231    ErrorProblem:
232
233    impl ErrorProblem {
234        /// Obtain the `column` of this error
235        //
236        // Our usual getters macro is `amplify` but it doesn't support conditional
237        // getters of enum fields, like we want here.
238        pub fn column(&self) -> Option<usize> {
239            Some(match self {
240              // Iterate over all fields in all variants.  There's only one field `column`
241              // in any variant, so this is precisely all variants with such a field.
242              ${for fields {
243                ${when approx_equal($fname, column)}
244                $vtype { column, .. } => *column,
245              }}
246                _ => return None,
247            })
248        }
249    }
250}
251use derive_deftly_template_ErrorProblem;
252
253impl From<UnexpectedArgument> for ErrorProblem {
254    fn from(ua: UnexpectedArgument) -> ErrorProblem {
255        EP::UnexpectedArgument { column: ua.column }
256    }
257}
258
259impl From<UnexpectedArgument> for ArgumentError {
260    fn from(_ua: UnexpectedArgument) -> ArgumentError {
261        AE::Unexpected
262    }
263}