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}