Skip to main content

eml_nl/
error.rs

1use crate::io::{OwnedQualifiedName, Span};
2
3/// Different kinds of errors that can occur during EML_NL processing.
4#[derive(thiserror::Error, Debug)]
5pub enum EMLErrorKind {
6    /// An error originanting from the XML parser
7    #[error("XML error: {0}")]
8    XmlError(#[from] quick_xml::Error),
9
10    /// An input/output error
11    #[error("I/O error: {0}")]
12    IoError(#[from] std::io::Error),
13
14    /// An error during escaping/unescaping XML content
15    #[error("Escape error: {0}")]
16    EscapeError(#[from] quick_xml::escape::EscapeError),
17
18    /// An error related to parsing XML attributes
19    #[error("Attribute error: {0}")]
20    AttributeError(#[from] quick_xml::events::attributes::AttrError),
21
22    /// An error related to XML encoding/decoding
23    #[error("Encoding error: {0}")]
24    EncodingError(#[from] quick_xml::encoding::EncodingError),
25
26    /// An error converting from UTF-8
27    #[error("UTF-8 conversion error: {0}")]
28    FromUtf8Error(#[from] std::string::FromUtf8Error),
29
30    /// An end element was found, but it was not the expected one
31    #[error("Unexpected end element")]
32    UnexpectedEndElement,
33
34    /// The end of the file was reached unexpectedly
35    #[error("Unexpected end of file")]
36    UnexpectedEof,
37
38    /// An unexpected parsing event was encountered during XML parsing
39    #[error("Unexpected parsing event encountered")]
40    UnexpectedEvent,
41
42    /// A required element was missing
43    #[error("Missing required element: {0}")]
44    MissingElement(OwnedQualifiedName),
45
46    /// An element existed, but it was empty or had no text content
47    #[error("Missing value for element: {0}")]
48    MissingElementValue(OwnedQualifiedName),
49
50    /// None of the choice elements were found
51    #[error("Missing any of these elements: {0:?}")]
52    MissingChoiceElements(Vec<OwnedQualifiedName>),
53
54    /// A required attribute was missing
55    #[error("Missing required attribute: {0}")]
56    MissingAttribute(OwnedQualifiedName),
57
58    /// An unexpected element was found
59    #[error("Unexpected element: {0} inside of {1}")]
60    UnexpectedElement(OwnedQualifiedName, OwnedQualifiedName),
61
62    /// A namespace was encountered that is not recognized
63    #[error("Unknown namespace: {0}")]
64    UnknownNamespace(String),
65
66    /// The root element was not named "EML"
67    #[error("Root element must be named EML")]
68    InvalidRootElement,
69
70    /// The EML schema version is not supported
71    #[error("Schema version '{0}' is not supported, only version '5' is supported")]
72    SchemaVersionNotSupported(String),
73
74    /// The document type is not recognized
75    #[error("Unknown document type: {0}")]
76    UnknownDocumentType(String),
77
78    /// The document type is invalid, a specific type was expected
79    #[error("Invalid document type: expected {0}, found {1}")]
80    InvalidDocumentType(&'static str, String),
81
82    /// An invalid value was encountered for a specific attribute/element
83    #[error("Invalid value for {0}: {1}")]
84    InvalidValue(
85        OwnedQualifiedName,
86        #[source] Box<dyn std::error::Error + Send + Sync + 'static>,
87    ),
88
89    /// An error occurred while converting a value to the parsed type
90    #[error("Error converting value: {0}")]
91    ValueConversionError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
92
93    /// Attributes cannot have the default namespace
94    #[error("Attributes cannot have the default namespace")]
95    AttributeNamespaceError,
96
97    /// Elements cannot be in no namespace when a default namespace is defined
98    #[error("Elements cannot be in no namespace when a default namespace is defined")]
99    ElementNamespaceError,
100
101    /// The ContestIdentifier element is missing
102    #[error("Missing the ContestIdentifier element")]
103    MissingContenstIdentifier,
104
105    /// The ElectionDate element is used without using the kiesraad namespace
106    #[error("Used ElectionDate element without using the kiesraad namespace")]
107    InvalidElectionDateNamespace,
108
109    /// The RejectedVotes element with ReasonCode "blanco" is missing
110    #[error("The RejectedVotes element with ReasonCode 'blanco' is missing")]
111    MissingRejectedVotesBlank,
112
113    /// The RejectedVotes element with ReasonCode "ongeldig" is missing
114    #[error("The RejectedVotes element with ReasonCode 'ongeldig' is missing")]
115    MissingRejectedVotesInvalid,
116
117    /// A Selection is missing a selection type (i.e. Candidate, AffiliationIdentifier or ReferendumOptionIdentifier)
118    #[error(
119        "A Selection is missing a selection type (i.e. Candidate, AffiliationIdentifier or ReferendumOptionIdentifier)"
120    )]
121    MissingSelectionType,
122
123    /// A field that is required for building a struct is missing.
124    #[error("A required property '{0}' is missing for building this struct")]
125    MissingBuildProperty(&'static str),
126
127    /// The NominationDate is before the ElectionDate, which is not allowed.
128    #[error("The NominationDate is before the ElectionDate, which is not allowed")]
129    NominationDateNotBeforeElectionDate,
130
131    /// The ElectionSubcategory is not valid for the ElectionCategory.
132    #[error("The ElectionSubcategory is not valid for the ElectionCategory")]
133    InvalidElectionSubcategory,
134
135    /// The voting method specified in the document is not supported.
136    #[error("The voting method specified in the document is not supported, only SPV is supported")]
137    UnsupportedVotingMethod,
138
139    /// The preference threshold specified in the document is not valid.
140    #[error("The preference threshold specified does not match the election identifier")]
141    InvalidPreferenceThreshold,
142
143    /// The number of seats specified in the document is not valid.
144    #[error("The number of seats specified does not match the subcategory")]
145    InvalidNumberOfSeats,
146
147    /// A referendum option was found where none was expected.
148    #[error("A referendum option was found where none was expected")]
149    UnexpectedReferendumOptionSelection,
150
151    /// A candidate was found without an affiliation, which is not allowed.
152    #[error("A candidate without affiliation was found")]
153    CandidateWithoutAffiliationFound,
154
155    /// A custom error with something that can be displayed
156    #[error("Custom error: {0}")]
157    Custom(Box<dyn CustomError>),
158}
159
160/// Custom error type that can be used in EMLErrorKind::Custom
161pub trait CustomError: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
162
163impl<T> CustomError for T where T: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
164
165impl EMLErrorKind {
166    /// Adds span information to the error.
167    pub(crate) fn with_span(self, span: Span) -> EMLError {
168        EMLError::Positioned { kind: self, span }
169    }
170
171    /// Converts the error kind to an error without span information.
172    pub(crate) fn without_span(self) -> EMLError {
173        EMLError::UnknownPosition { kind: self }
174    }
175}
176
177/// An error encountered during EML_NL processing.
178///
179/// The error includes the kind of error as well as an optional span indicating
180/// where in the source XML the error approximately occured.
181#[derive(thiserror::Error, Debug)]
182pub enum EMLError {
183    /// An error with position information in a document
184    #[error("Error in EML: {kind} at position {span:?}")]
185    Positioned {
186        /// The kind of error that occured
187        kind: EMLErrorKind,
188        /// The span (position) in the document where the error occured
189        span: Span,
190    },
191    /// An error without position information
192    #[error("Error in EML: {kind}")]
193    UnknownPosition {
194        /// The kind of error that occured
195        kind: EMLErrorKind,
196    },
197    /// A list of multiple errors
198    #[error("Multiple errors in EML: {0}")]
199    Multiple(MultipleEMLErrors),
200}
201
202/// An error containing multiple EMLErrors
203#[derive(Debug)]
204pub struct MultipleEMLErrors {
205    /// The list of errors
206    pub errors: Vec<EMLError>,
207}
208
209impl std::fmt::Display for MultipleEMLErrors {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        writeln!(
212            f,
213            "{} non-fatal error(s) and then: {}",
214            self.errors.len() - 1,
215            self.errors.last().unwrap()
216        )
217    }
218}
219
220impl EMLError {
221    /// Create a new invalid value error
222    pub(crate) fn invalid_value(
223        field: OwnedQualifiedName,
224        source: impl std::error::Error + Send + Sync + 'static,
225        span: Option<Span>,
226    ) -> Self {
227        let kind = EMLErrorKind::InvalidValue(field, Box::new(source));
228        if let Some(span) = span {
229            EMLError::Positioned { kind, span }
230        } else {
231            EMLError::UnknownPosition { kind }
232        }
233    }
234
235    /// Create a new custom error.
236    pub fn custom(source: impl CustomError) -> Self {
237        EMLErrorKind::Custom(Box::new(source)).without_span()
238    }
239
240    /// Create an EMLError from a list of errors.
241    pub(crate) fn from_vec(errors: Vec<EMLError>) -> Self {
242        if errors.len() == 1 {
243            errors
244                .into_iter()
245                .next()
246                .expect("Vec must have one element")
247        } else {
248            EMLError::Multiple(MultipleEMLErrors { errors })
249        }
250    }
251
252    /// Create an EMLError from a vector of errors.
253    pub(crate) fn from_vec_with_additional(mut errors: Vec<EMLError>, error: EMLError) -> Self {
254        errors.push(error);
255        Self::from_vec(errors)
256    }
257
258    /// Returns the kind of this error.
259    ///
260    /// When this error consists of multiple errors, None is returned.
261    ///
262    /// Note: when multiple errors are present, the kind of the last error is returned.
263    pub fn kind(&self) -> &EMLErrorKind {
264        match self {
265            EMLError::Positioned { kind, .. } => kind,
266            EMLError::UnknownPosition { kind } => kind,
267            EMLError::Multiple(MultipleEMLErrors { errors }) => errors
268                .last()
269                .map(|e| e.kind())
270                .expect("Errors vec cannot be empty"),
271        }
272    }
273
274    /// Returns the kind of this error as an EMLErrorKind, consuming the error.
275    ///
276    /// When this error consists of multiple errors, the kind of the last error is returned.
277    pub fn into_kind(self) -> EMLErrorKind {
278        match self {
279            EMLError::Positioned { kind, .. } => kind,
280            EMLError::UnknownPosition { kind } => kind,
281            EMLError::Multiple(MultipleEMLErrors { errors }) => errors
282                .into_iter()
283                .last()
284                .map(|e| e.into_kind())
285                .expect("Errors vec cannot be empty"),
286        }
287    }
288
289    /// Returns the span of this error, if available.
290    ///
291    /// Note: when multiple errors are present, the span of the last error is returned.
292    pub fn span(&self) -> Option<Span> {
293        match self {
294            EMLError::Positioned { span, .. } => Some(*span),
295            EMLError::UnknownPosition { .. } => None,
296            EMLError::Multiple(MultipleEMLErrors { errors }) => {
297                errors.last().and_then(|e| e.span())
298            }
299        }
300    }
301}
302
303/// Extension trait for Result to add context to EMLError
304pub(crate) trait EMLResultExt<T> {
305    /// Adds span information to the error if it occurs.
306    fn with_span(self, span: Span) -> Result<T, EMLError>;
307    /// Converts the error kind to an error without span information.
308    fn without_span(self) -> Result<T, EMLError>;
309}
310
311impl<T, I> EMLResultExt<T> for Result<T, I>
312where
313    I: Into<EMLErrorKind>,
314{
315    fn with_span(self, span: Span) -> Result<T, EMLError> {
316        self.map_err(|kind| EMLError::Positioned {
317            kind: kind.into(),
318            span,
319        })
320    }
321
322    fn without_span(self) -> Result<T, EMLError> {
323        self.map_err(|kind| EMLError::UnknownPosition { kind: kind.into() })
324    }
325}
326
327/// Extension trait for Result to add context to EMLError for errors that can be
328/// converted into EMLErrorKind::InvalidValue.
329pub(crate) trait EMLValueResultExt<T> {
330    /// Convert the error into an EMLError with context about the field that caused the error.
331    fn wrap_field_value_error(
332        self,
333        element_name: impl Into<OwnedQualifiedName>,
334    ) -> Result<T, EMLError>;
335
336    /// Convert the error into an EMLError that the value is invalid.
337    fn wrap_value_error(self) -> Result<T, EMLError>;
338}
339
340impl<T, I> EMLValueResultExt<T> for Result<T, I>
341where
342    I: std::error::Error + Send + Sync + 'static,
343{
344    fn wrap_field_value_error(
345        self,
346        element_name: impl Into<OwnedQualifiedName>,
347    ) -> Result<T, EMLError> {
348        self.map_err(|e| EMLError::invalid_value(element_name.into(), Box::new(e), None))
349    }
350
351    fn wrap_value_error(self) -> Result<T, EMLError> {
352        self.map_err(|e| EMLErrorKind::ValueConversionError(Box::new(e)).without_span())
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use crate::NS_EML;
360
361    #[test]
362    fn test_creating_invalid_value_error() {
363        let error = EMLError::invalid_value(
364            OwnedQualifiedName::from_static("Test", Some(NS_EML)),
365            std::io::Error::other("error"),
366            None,
367        );
368
369        assert!(matches!(
370            error,
371            EMLError::UnknownPosition {
372                kind: EMLErrorKind::InvalidValue(_, _)
373            }
374        ));
375
376        let error_with_span = EMLError::invalid_value(
377            OwnedQualifiedName::from_static("Test", Some(NS_EML)),
378            std::io::Error::other("error"),
379            Some(Span { start: 10, end: 20 }),
380        );
381
382        assert!(matches!(
383            error_with_span,
384            EMLError::Positioned {
385                kind: EMLErrorKind::InvalidValue(_, _),
386                span: Span { start: 10, end: 20 }
387            }
388        ));
389    }
390
391    #[test]
392    fn test_creating_multiple_errors() {
393        let err1 = EMLErrorKind::UnexpectedEndElement.with_span(Span { start: 0, end: 5 });
394        let err2 =
395            EMLErrorKind::MissingElement(OwnedQualifiedName::from_static("Test", Some(NS_EML)))
396                .with_span(Span { start: 10, end: 15 });
397
398        let multiple_error = EMLError::from_vec_with_additional(vec![err1], err2);
399        assert!(matches!(multiple_error, EMLError::Multiple(_)));
400
401        let err3 = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
402        let multiple_error2 = EMLError::from_vec_with_additional(vec![], err3);
403        assert!(matches!(
404            multiple_error2,
405            EMLError::Positioned {
406                kind: EMLErrorKind::UnexpectedEof,
407                span: Span { start: 0, end: 10 }
408            }
409        ));
410    }
411
412    #[test]
413    fn get_data_from_error() {
414        let err = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
415        assert!(matches!(err.kind(), &EMLErrorKind::UnexpectedEof));
416        assert_eq!(err.span(), Some(Span { start: 0, end: 10 }));
417
418        let err2 = EMLError::UnknownPosition {
419            kind: EMLErrorKind::UnexpectedElement(
420                OwnedQualifiedName::from_static("Test", None),
421                OwnedQualifiedName::from_static("Test", None),
422            ),
423        };
424
425        assert!(matches!(
426            err2.kind(),
427            &EMLErrorKind::UnexpectedElement(_, _)
428        ));
429        assert_eq!(err2.span(), None);
430
431        let err3 = EMLError::Multiple(MultipleEMLErrors {
432            errors: vec![
433                EMLError::Positioned {
434                    kind: EMLErrorKind::UnexpectedElement(
435                        OwnedQualifiedName::from_static("Test", None),
436                        OwnedQualifiedName::from_static("Test", None),
437                    ),
438                    span: Span { start: 0, end: 5 },
439                },
440                EMLError::UnknownPosition {
441                    kind: EMLErrorKind::MissingElement(OwnedQualifiedName::from_static(
442                        "Test", None,
443                    )),
444                },
445            ],
446        });
447        assert!(matches!(err3.kind(), &EMLErrorKind::MissingElement(_)));
448        assert_eq!(err3.span(), None);
449    }
450}