Skip to main content

eml_nl/
error.rs

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