1use crate::{
2 io::{OwnedQualifiedName, Span},
3 utils::{AffiliationId, CandidateId, ElectionTreeHierarchyError},
4};
5
6#[derive(thiserror::Error, Debug)]
8pub enum EMLErrorKind {
9 #[error("XML error: {0}")]
11 XmlError(#[from] quick_xml::Error),
12
13 #[error("I/O error: {0}")]
15 IoError(#[from] std::io::Error),
16
17 #[error("Escape error: {0}")]
19 EscapeError(#[from] quick_xml::escape::EscapeError),
20
21 #[error("Attribute error: {0}")]
23 AttributeError(#[from] quick_xml::events::attributes::AttrError),
24
25 #[error("Encoding error: {0}")]
27 EncodingError(#[from] quick_xml::encoding::EncodingError),
28
29 #[error("UTF-8 conversion error: {0}")]
31 FromUtf8Error(#[from] std::string::FromUtf8Error),
32
33 #[error("Unexpected end element")]
35 UnexpectedEndElement,
36
37 #[error("Unexpected end of file")]
39 UnexpectedEof,
40
41 #[error("Unexpected parsing event encountered")]
43 UnexpectedEvent,
44
45 #[error("Missing required element: {0}")]
47 MissingElement(OwnedQualifiedName),
48
49 #[error("Too many elements: {0}, maximum {1}")]
51 TooManyElements(OwnedQualifiedName, usize),
52
53 #[error("Missing value for element: {0}")]
55 MissingElementValue(OwnedQualifiedName),
56
57 #[error("Missing any of these elements: {0:?}")]
59 MissingChoiceElements(Vec<OwnedQualifiedName>),
60
61 #[error("Missing required attribute: {0}")]
63 MissingAttribute(OwnedQualifiedName),
64
65 #[error("Unexpected element: {0} inside of {1}")]
67 UnexpectedElement(OwnedQualifiedName, OwnedQualifiedName),
68
69 #[error("Unknown namespace: {0}")]
71 UnknownNamespace(String),
72
73 #[error("Root element must be named EML")]
75 InvalidRootElement,
76
77 #[error("Schema version '{0}' is not supported, only version '5' is supported")]
79 SchemaVersionNotSupported(String),
80
81 #[error("Unknown document type: {0}")]
83 UnknownDocumentType(String),
84
85 #[error("Invalid document type: expected {0}, found {1}")]
87 InvalidDocumentType(&'static str, String),
88
89 #[error("Invalid value for {0}: {1}")]
91 InvalidValue(
92 OwnedQualifiedName,
93 #[source] Box<dyn std::error::Error + Send + Sync + 'static>,
94 ),
95
96 #[error("Error converting value: {0}")]
98 ValueConversionError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
99
100 #[error("Attributes cannot have the default namespace")]
102 AttributeNamespaceError,
103
104 #[error("Elements cannot be in no namespace when a default namespace is defined")]
106 ElementNamespaceError,
107
108 #[error("Missing the ContestIdentifier element")]
110 MissingContenstIdentifier,
111
112 #[error("Used ElectionDate element without using the kiesraad namespace")]
114 InvalidElectionDateNamespace,
115
116 #[error("The RejectedVotes element with ReasonCode 'blanco' is missing")]
118 MissingRejectedVotesBlank,
119
120 #[error("The RejectedVotes element with ReasonCode 'ongeldig' is missing")]
122 MissingRejectedVotesInvalid,
123
124 #[error(
126 "A Selection is missing a selection type (i.e. Candidate, AffiliationIdentifier or ReferendumOptionIdentifier)"
127 )]
128 MissingSelectionType,
129
130 #[error("A required property '{0}' is missing for building this struct")]
132 MissingBuildProperty(&'static str),
133
134 #[error("The NominationDate is before the ElectionDate, which is not allowed")]
136 NominationDateNotBeforeElectionDate,
137
138 #[error("The ElectionSubcategory is not valid for the ElectionCategory")]
140 InvalidElectionSubcategory,
141
142 #[error("The voting method specified in the document is not supported, only SPV is supported")]
144 UnsupportedVotingMethod,
145
146 #[error("The preference threshold specified does not match the election identifier")]
148 InvalidPreferenceThreshold,
149
150 #[error("The number of seats specified does not match the subcategory")]
152 InvalidNumberOfSeats,
153
154 #[error("A referendum option was found where none was expected")]
156 UnexpectedReferendumOptionSelection,
157
158 #[error("A candidate without affiliation was found")]
160 CandidateWithoutAffiliationFound,
161
162 #[error("Missing a Contest element when at least one was expected")]
164 MissingContest,
165
166 #[error("Missing the TotalVotes element")]
168 MissingTotalVotes,
169
170 #[error("Could not find a candidate for affiliation id {0} and candidate id {1}")]
172 UnknownCandidate(AffiliationId, CandidateId),
173
174 #[error("Could not find an affiliation for affiliation id {0}")]
176 UnknownAffiliation(AffiliationId),
177
178 #[error("Invalid election tree: {0}")]
180 InvalidElectionTree(#[from] ElectionTreeHierarchyError),
181
182 #[error("Custom error: {0}")]
184 Custom(Box<dyn CustomError>),
185}
186
187pub 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 pub(crate) fn with_span(self, span: Span) -> EMLError {
195 EMLError::Positioned { kind: self, span }
196 }
197
198 pub(crate) fn without_span(self) -> EMLError {
200 EMLError::UnknownPosition { kind: self }
201 }
202}
203
204#[derive(thiserror::Error, Debug)]
209pub enum EMLError {
210 #[error("Error in EML: {kind} at position {span:?}")]
212 Positioned {
213 kind: EMLErrorKind,
215 span: Span,
217 },
218 #[error("Error in EML: {kind}")]
220 UnknownPosition {
221 kind: EMLErrorKind,
223 },
224 #[error("Multiple errors in EML: {0}")]
226 Multiple(MultipleEMLErrors),
227}
228
229#[derive(Debug)]
231pub struct MultipleEMLErrors {
232 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 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 pub fn custom(source: impl CustomError) -> Self {
264 EMLErrorKind::Custom(Box::new(source)).without_span()
265 }
266
267 pub fn value_conversion(source: impl std::error::Error + Send + Sync + 'static) -> Self {
269 EMLErrorKind::ValueConversionError(Box::new(source)).without_span()
270 }
271
272 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 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 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 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 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
335pub(crate) trait EMLResultExt<T> {
337 fn with_span(self, span: Span) -> Result<T, EMLError>;
339 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
359pub(crate) trait EMLValueResultExt<T> {
362 fn wrap_field_value_error(
364 self,
365 element_name: impl Into<OwnedQualifiedName>,
366 ) -> Result<T, EMLError>;
367
368 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}