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
/// 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]
76
pub 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]
178
pub 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)]
198
pub 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]
206
pub 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

            
230
impl From<signature::Error> for VerifyFailed {
231
4
    fn from(_: signature::Error) -> VerifyFailed {
232
4
        VerifyFailed::VerifyFailed
233
4
    }
234
}
235

            
236
define_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
119
        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
}
260
use derive_deftly_template_ErrorProblem;
261

            
262
impl From<UnexpectedArgument> for ErrorProblem {
263
6
    fn from(ua: UnexpectedArgument) -> ErrorProblem {
264
6
        EP::UnexpectedArgument { column: ua.column }
265
6
    }
266
}
267

            
268
impl From<UnexpectedArgument> for ArgumentError {
269
    fn from(_ua: UnexpectedArgument) -> ArgumentError {
270
        AE::Unexpected
271
    }
272
}