1use crate::{
2 io::{OwnedQualifiedName, Span},
3 utils::{AffiliationId, CandidateId},
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("Missing value for element: {0}")]
51 MissingElementValue(OwnedQualifiedName),
52
53 #[error("Missing any of these elements: {0:?}")]
55 MissingChoiceElements(Vec<OwnedQualifiedName>),
56
57 #[error("Missing required attribute: {0}")]
59 MissingAttribute(OwnedQualifiedName),
60
61 #[error("Unexpected element: {0} inside of {1}")]
63 UnexpectedElement(OwnedQualifiedName, OwnedQualifiedName),
64
65 #[error("Unknown namespace: {0}")]
67 UnknownNamespace(String),
68
69 #[error("Root element must be named EML")]
71 InvalidRootElement,
72
73 #[error("Schema version '{0}' is not supported, only version '5' is supported")]
75 SchemaVersionNotSupported(String),
76
77 #[error("Unknown document type: {0}")]
79 UnknownDocumentType(String),
80
81 #[error("Invalid document type: expected {0}, found {1}")]
83 InvalidDocumentType(&'static str, String),
84
85 #[error("Invalid value for {0}: {1}")]
87 InvalidValue(
88 OwnedQualifiedName,
89 #[source] Box<dyn std::error::Error + Send + Sync + 'static>,
90 ),
91
92 #[error("Error converting value: {0}")]
94 ValueConversionError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
95
96 #[error("Attributes cannot have the default namespace")]
98 AttributeNamespaceError,
99
100 #[error("Elements cannot be in no namespace when a default namespace is defined")]
102 ElementNamespaceError,
103
104 #[error("Missing the ContestIdentifier element")]
106 MissingContenstIdentifier,
107
108 #[error("Used ElectionDate element without using the kiesraad namespace")]
110 InvalidElectionDateNamespace,
111
112 #[error("The RejectedVotes element with ReasonCode 'blanco' is missing")]
114 MissingRejectedVotesBlank,
115
116 #[error("The RejectedVotes element with ReasonCode 'ongeldig' is missing")]
118 MissingRejectedVotesInvalid,
119
120 #[error(
122 "A Selection is missing a selection type (i.e. Candidate, AffiliationIdentifier or ReferendumOptionIdentifier)"
123 )]
124 MissingSelectionType,
125
126 #[error("A required property '{0}' is missing for building this struct")]
128 MissingBuildProperty(&'static str),
129
130 #[error("The NominationDate is before the ElectionDate, which is not allowed")]
132 NominationDateNotBeforeElectionDate,
133
134 #[error("The ElectionSubcategory is not valid for the ElectionCategory")]
136 InvalidElectionSubcategory,
137
138 #[error("The voting method specified in the document is not supported, only SPV is supported")]
140 UnsupportedVotingMethod,
141
142 #[error("The preference threshold specified does not match the election identifier")]
144 InvalidPreferenceThreshold,
145
146 #[error("The number of seats specified does not match the subcategory")]
148 InvalidNumberOfSeats,
149
150 #[error("A referendum option was found where none was expected")]
152 UnexpectedReferendumOptionSelection,
153
154 #[error("A candidate without affiliation was found")]
156 CandidateWithoutAffiliationFound,
157
158 #[error("Missing a Contest element when at least one was expected")]
160 MissingContest,
161
162 #[error("Missing the TotalVotes element")]
164 MissingTotalVotes,
165
166 #[error("Could not find a candidate for affiliation id {0} and candidate id {1}")]
168 UnknownCandidate(AffiliationId, CandidateId),
169
170 #[error("Could not find an affiliation for affiliation id {0}")]
172 UnknownAffiliation(AffiliationId),
173
174 #[error("Custom error: {0}")]
176 Custom(Box<dyn CustomError>),
177}
178
179pub trait CustomError: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
181
182impl<T> CustomError for T where T: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
183
184impl EMLErrorKind {
185 pub(crate) fn with_span(self, span: Span) -> EMLError {
187 EMLError::Positioned { kind: self, span }
188 }
189
190 pub(crate) fn without_span(self) -> EMLError {
192 EMLError::UnknownPosition { kind: self }
193 }
194}
195
196#[derive(thiserror::Error, Debug)]
201pub enum EMLError {
202 #[error("Error in EML: {kind} at position {span:?}")]
204 Positioned {
205 kind: EMLErrorKind,
207 span: Span,
209 },
210 #[error("Error in EML: {kind}")]
212 UnknownPosition {
213 kind: EMLErrorKind,
215 },
216 #[error("Multiple errors in EML: {0}")]
218 Multiple(MultipleEMLErrors),
219}
220
221#[derive(Debug)]
223pub struct MultipleEMLErrors {
224 pub errors: Vec<EMLError>,
226}
227
228impl std::fmt::Display for MultipleEMLErrors {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 writeln!(
231 f,
232 "{} non-fatal error(s) and then: {}",
233 self.errors.len() - 1,
234 self.errors.last().unwrap()
235 )
236 }
237}
238
239impl EMLError {
240 pub(crate) fn invalid_value(
242 field: OwnedQualifiedName,
243 source: impl std::error::Error + Send + Sync + 'static,
244 span: Option<Span>,
245 ) -> Self {
246 let kind = EMLErrorKind::InvalidValue(field, Box::new(source));
247 if let Some(span) = span {
248 EMLError::Positioned { kind, span }
249 } else {
250 EMLError::UnknownPosition { kind }
251 }
252 }
253
254 pub fn custom(source: impl CustomError) -> Self {
256 EMLErrorKind::Custom(Box::new(source)).without_span()
257 }
258
259 pub(crate) fn from_vec(errors: Vec<EMLError>) -> Self {
261 if errors.len() == 1 {
262 errors
263 .into_iter()
264 .next()
265 .expect("Vec must have one element")
266 } else {
267 EMLError::Multiple(MultipleEMLErrors { errors })
268 }
269 }
270
271 pub(crate) fn from_vec_with_additional(mut errors: Vec<EMLError>, error: EMLError) -> Self {
273 errors.push(error);
274 Self::from_vec(errors)
275 }
276
277 pub fn kind(&self) -> &EMLErrorKind {
283 match self {
284 EMLError::Positioned { kind, .. } => kind,
285 EMLError::UnknownPosition { kind } => kind,
286 EMLError::Multiple(MultipleEMLErrors { errors }) => errors
287 .last()
288 .map(|e| e.kind())
289 .expect("Errors vec cannot be empty"),
290 }
291 }
292
293 pub fn into_kind(self) -> EMLErrorKind {
297 match self {
298 EMLError::Positioned { kind, .. } => kind,
299 EMLError::UnknownPosition { kind } => kind,
300 EMLError::Multiple(MultipleEMLErrors { errors }) => errors
301 .into_iter()
302 .last()
303 .map(|e| e.into_kind())
304 .expect("Errors vec cannot be empty"),
305 }
306 }
307
308 pub fn span(&self) -> Option<Span> {
312 match self {
313 EMLError::Positioned { span, .. } => Some(*span),
314 EMLError::UnknownPosition { .. } => None,
315 EMLError::Multiple(MultipleEMLErrors { errors }) => {
316 errors.last().and_then(|e| e.span())
317 }
318 }
319 }
320}
321
322pub(crate) trait EMLResultExt<T> {
324 fn with_span(self, span: Span) -> Result<T, EMLError>;
326 fn without_span(self) -> Result<T, EMLError>;
328}
329
330impl<T, I> EMLResultExt<T> for Result<T, I>
331where
332 I: Into<EMLErrorKind>,
333{
334 fn with_span(self, span: Span) -> Result<T, EMLError> {
335 self.map_err(|kind| EMLError::Positioned {
336 kind: kind.into(),
337 span,
338 })
339 }
340
341 fn without_span(self) -> Result<T, EMLError> {
342 self.map_err(|kind| EMLError::UnknownPosition { kind: kind.into() })
343 }
344}
345
346pub(crate) trait EMLValueResultExt<T> {
349 fn wrap_field_value_error(
351 self,
352 element_name: impl Into<OwnedQualifiedName>,
353 ) -> Result<T, EMLError>;
354
355 fn wrap_value_error(self) -> Result<T, EMLError>;
357}
358
359impl<T, I> EMLValueResultExt<T> for Result<T, I>
360where
361 I: std::error::Error + Send + Sync + 'static,
362{
363 fn wrap_field_value_error(
364 self,
365 element_name: impl Into<OwnedQualifiedName>,
366 ) -> Result<T, EMLError> {
367 self.map_err(|e| EMLError::invalid_value(element_name.into(), Box::new(e), None))
368 }
369
370 fn wrap_value_error(self) -> Result<T, EMLError> {
371 self.map_err(|e| EMLErrorKind::ValueConversionError(Box::new(e)).without_span())
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use crate::NS_EML;
379
380 #[test]
381 fn test_creating_invalid_value_error() {
382 let error = EMLError::invalid_value(
383 OwnedQualifiedName::from_static("Test", Some(NS_EML)),
384 std::io::Error::other("error"),
385 None,
386 );
387
388 assert!(matches!(
389 error,
390 EMLError::UnknownPosition {
391 kind: EMLErrorKind::InvalidValue(_, _)
392 }
393 ));
394
395 let error_with_span = EMLError::invalid_value(
396 OwnedQualifiedName::from_static("Test", Some(NS_EML)),
397 std::io::Error::other("error"),
398 Some(Span { start: 10, end: 20 }),
399 );
400
401 assert!(matches!(
402 error_with_span,
403 EMLError::Positioned {
404 kind: EMLErrorKind::InvalidValue(_, _),
405 span: Span { start: 10, end: 20 }
406 }
407 ));
408 }
409
410 #[test]
411 fn test_creating_multiple_errors() {
412 let err1 = EMLErrorKind::UnexpectedEndElement.with_span(Span { start: 0, end: 5 });
413 let err2 =
414 EMLErrorKind::MissingElement(OwnedQualifiedName::from_static("Test", Some(NS_EML)))
415 .with_span(Span { start: 10, end: 15 });
416
417 let multiple_error = EMLError::from_vec_with_additional(vec![err1], err2);
418 assert!(matches!(multiple_error, EMLError::Multiple(_)));
419
420 let err3 = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
421 let multiple_error2 = EMLError::from_vec_with_additional(vec![], err3);
422 assert!(matches!(
423 multiple_error2,
424 EMLError::Positioned {
425 kind: EMLErrorKind::UnexpectedEof,
426 span: Span { start: 0, end: 10 }
427 }
428 ));
429 }
430
431 #[test]
432 fn get_data_from_error() {
433 let err = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
434 assert!(matches!(err.kind(), &EMLErrorKind::UnexpectedEof));
435 assert_eq!(err.span(), Some(Span { start: 0, end: 10 }));
436
437 let err2 = EMLError::UnknownPosition {
438 kind: EMLErrorKind::UnexpectedElement(
439 OwnedQualifiedName::from_static("Test", None),
440 OwnedQualifiedName::from_static("Test", None),
441 ),
442 };
443
444 assert!(matches!(
445 err2.kind(),
446 &EMLErrorKind::UnexpectedElement(_, _)
447 ));
448 assert_eq!(err2.span(), None);
449
450 let err3 = EMLError::Multiple(MultipleEMLErrors {
451 errors: vec![
452 EMLError::Positioned {
453 kind: EMLErrorKind::UnexpectedElement(
454 OwnedQualifiedName::from_static("Test", None),
455 OwnedQualifiedName::from_static("Test", None),
456 ),
457 span: Span { start: 0, end: 5 },
458 },
459 EMLError::UnknownPosition {
460 kind: EMLErrorKind::MissingElement(OwnedQualifiedName::from_static(
461 "Test", None,
462 )),
463 },
464 ],
465 });
466 assert!(matches!(err3.kind(), &EMLErrorKind::MissingElement(_)));
467 assert_eq!(err3.span(), None);
468 }
469}