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

            
33
use 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]
48
pub 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
impl 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]
95
pub 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]
197
pub 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)]
217
pub 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]
225
pub 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

            
252
impl From<signature::Error> for VerifyFailed {
253
4
    fn from(_: signature::Error) -> VerifyFailed {
254
4
        VerifyFailed::VerifyFailed
255
4
    }
256
}
257

            
258
define_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
66
        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
}
282
use derive_deftly_template_ErrorProblem;
283

            
284
impl From<UnexpectedArgument> for ErrorProblem {
285
4
    fn from(ua: UnexpectedArgument) -> ErrorProblem {
286
4
        EP::UnexpectedArgument { column: ua.column }
287
4
    }
288
}
289

            
290
impl From<UnexpectedArgument> for ArgumentError {
291
    fn from(_ua: UnexpectedArgument) -> ArgumentError {
292
        AE::Unexpected
293
    }
294
}