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}