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}