use crate::io::{OwnedQualifiedName, Span};
#[derive(thiserror::Error, Debug)]
pub enum EMLErrorKind {
#[error("XML error: {0}")]
XmlError(#[from] quick_xml::Error),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Escape error: {0}")]
EscapeError(#[from] quick_xml::escape::EscapeError),
#[error("Attribute error: {0}")]
AttributeError(#[from] quick_xml::events::attributes::AttrError),
#[error("Encoding error: {0}")]
EncodingError(#[from] quick_xml::encoding::EncodingError),
#[error("UTF-8 conversion error: {0}")]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error("Unexpected end element")]
UnexpectedEndElement,
#[error("Unexpected end of file")]
UnexpectedEof,
#[error("Unexpected parsing event encountered")]
UnexpectedEvent,
#[error("Missing required element: {0}")]
MissingElement(OwnedQualifiedName),
#[error("Missing value for element: {0}")]
MissingElementValue(OwnedQualifiedName),
#[error("Missing any of these elements: {0:?}")]
MissingChoiceElements(Vec<OwnedQualifiedName>),
#[error("Missing required attribute: {0}")]
MissingAttribute(OwnedQualifiedName),
#[error("Unexpected element: {0} inside of {1}")]
UnexpectedElement(OwnedQualifiedName, OwnedQualifiedName),
#[error("Unknown namespace: {0}")]
UnknownNamespace(String),
#[error("Root element must be named EML")]
InvalidRootElement,
#[error("Schema version '{0}' is not supported, only version '5' is supported")]
SchemaVersionNotSupported(String),
#[error("Unknown document type: {0}")]
UnknownDocumentType(String),
#[error("Invalid document type: expected {0}, found {1}")]
InvalidDocumentType(&'static str, String),
#[error("Invalid value for {0}: {1}")]
InvalidValue(
OwnedQualifiedName,
#[source] Box<dyn std::error::Error + Send + Sync + 'static>,
),
#[error("Error converting value: {0}")]
ValueConversionError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Attributes cannot have the default namespace")]
AttributeNamespaceError,
#[error("Elements cannot be in no namespace when a default namespace is defined")]
ElementNamespaceError,
#[error("Missing the ContestIdentifier element")]
MissingContenstIdentifier,
#[error("Used ElectionDate element without using the kiesraad namespace")]
InvalidElectionDateNamespace,
#[error("The RejectedVotes element with ReasonCode 'blanco' is missing")]
MissingRejectedVotesBlank,
#[error("The RejectedVotes element with ReasonCode 'ongeldig' is missing")]
MissingRejectedVotesInvalid,
#[error(
"A Selection is missing a selection type (i.e. Candidate, AffiliationIdentifier or ReferendumOptionIdentifier)"
)]
MissingSelectionType,
#[error("A required property '{0}' is missing for building this struct")]
MissingBuildProperty(&'static str),
#[error("The NominationDate is before the ElectionDate, which is not allowed")]
NominationDateNotBeforeElectionDate,
#[error("The ElectionSubcategory is not valid for the ElectionCategory")]
InvalidElectionSubcategory,
#[error("The voting method specified in the document is not supported, only SPV is supported")]
UnsupportedVotingMethod,
#[error("The preference threshold specified does not match the election identifier")]
InvalidPreferenceThreshold,
#[error("The number of seats specified does not match the subcategory")]
InvalidNumberOfSeats,
#[error("A referendum option was found where none was expected")]
UnexpectedReferendumOptionSelection,
#[error("A candidate without affiliation was found")]
CandidateWithoutAffiliationFound,
#[error("Custom error: {0}")]
Custom(Box<dyn CustomError>),
}
pub trait CustomError: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
impl<T> CustomError for T where T: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static {}
impl EMLErrorKind {
pub(crate) fn with_span(self, span: Span) -> EMLError {
EMLError::Positioned { kind: self, span }
}
pub(crate) fn without_span(self) -> EMLError {
EMLError::UnknownPosition { kind: self }
}
}
#[derive(thiserror::Error, Debug)]
pub enum EMLError {
#[error("Error in EML: {kind} at position {span:?}")]
Positioned {
kind: EMLErrorKind,
span: Span,
},
#[error("Error in EML: {kind}")]
UnknownPosition {
kind: EMLErrorKind,
},
#[error("Multiple errors in EML: {0}")]
Multiple(MultipleEMLErrors),
}
#[derive(Debug)]
pub struct MultipleEMLErrors {
pub errors: Vec<EMLError>,
}
impl std::fmt::Display for MultipleEMLErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"{} non-fatal error(s) and then: {}",
self.errors.len() - 1,
self.errors.last().unwrap()
)
}
}
impl EMLError {
pub(crate) fn invalid_value(
field: OwnedQualifiedName,
source: impl std::error::Error + Send + Sync + 'static,
span: Option<Span>,
) -> Self {
let kind = EMLErrorKind::InvalidValue(field, Box::new(source));
if let Some(span) = span {
EMLError::Positioned { kind, span }
} else {
EMLError::UnknownPosition { kind }
}
}
pub fn custom(source: impl CustomError) -> Self {
EMLErrorKind::Custom(Box::new(source)).without_span()
}
pub(crate) fn from_vec(errors: Vec<EMLError>) -> Self {
if errors.len() == 1 {
errors
.into_iter()
.next()
.expect("Vec must have one element")
} else {
EMLError::Multiple(MultipleEMLErrors { errors })
}
}
pub(crate) fn from_vec_with_additional(mut errors: Vec<EMLError>, error: EMLError) -> Self {
errors.push(error);
Self::from_vec(errors)
}
pub fn kind(&self) -> &EMLErrorKind {
match self {
EMLError::Positioned { kind, .. } => kind,
EMLError::UnknownPosition { kind } => kind,
EMLError::Multiple(MultipleEMLErrors { errors }) => errors
.last()
.map(|e| e.kind())
.expect("Errors vec cannot be empty"),
}
}
pub fn into_kind(self) -> EMLErrorKind {
match self {
EMLError::Positioned { kind, .. } => kind,
EMLError::UnknownPosition { kind } => kind,
EMLError::Multiple(MultipleEMLErrors { errors }) => errors
.into_iter()
.last()
.map(|e| e.into_kind())
.expect("Errors vec cannot be empty"),
}
}
pub fn span(&self) -> Option<Span> {
match self {
EMLError::Positioned { span, .. } => Some(*span),
EMLError::UnknownPosition { .. } => None,
EMLError::Multiple(MultipleEMLErrors { errors }) => {
errors.last().and_then(|e| e.span())
}
}
}
}
pub(crate) trait EMLResultExt<T> {
fn with_span(self, span: Span) -> Result<T, EMLError>;
fn without_span(self) -> Result<T, EMLError>;
}
impl<T, I> EMLResultExt<T> for Result<T, I>
where
I: Into<EMLErrorKind>,
{
fn with_span(self, span: Span) -> Result<T, EMLError> {
self.map_err(|kind| EMLError::Positioned {
kind: kind.into(),
span,
})
}
fn without_span(self) -> Result<T, EMLError> {
self.map_err(|kind| EMLError::UnknownPosition { kind: kind.into() })
}
}
pub(crate) trait EMLValueResultExt<T> {
fn wrap_field_value_error(
self,
element_name: impl Into<OwnedQualifiedName>,
) -> Result<T, EMLError>;
fn wrap_value_error(self) -> Result<T, EMLError>;
}
impl<T, I> EMLValueResultExt<T> for Result<T, I>
where
I: std::error::Error + Send + Sync + 'static,
{
fn wrap_field_value_error(
self,
element_name: impl Into<OwnedQualifiedName>,
) -> Result<T, EMLError> {
self.map_err(|e| EMLError::invalid_value(element_name.into(), Box::new(e), None))
}
fn wrap_value_error(self) -> Result<T, EMLError> {
self.map_err(|e| EMLErrorKind::ValueConversionError(Box::new(e)).without_span())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NS_EML;
#[test]
fn test_creating_invalid_value_error() {
let error = EMLError::invalid_value(
OwnedQualifiedName::from_static("Test", Some(NS_EML)),
std::io::Error::other("error"),
None,
);
assert!(matches!(
error,
EMLError::UnknownPosition {
kind: EMLErrorKind::InvalidValue(_, _)
}
));
let error_with_span = EMLError::invalid_value(
OwnedQualifiedName::from_static("Test", Some(NS_EML)),
std::io::Error::other("error"),
Some(Span { start: 10, end: 20 }),
);
assert!(matches!(
error_with_span,
EMLError::Positioned {
kind: EMLErrorKind::InvalidValue(_, _),
span: Span { start: 10, end: 20 }
}
));
}
#[test]
fn test_creating_multiple_errors() {
let err1 = EMLErrorKind::UnexpectedEndElement.with_span(Span { start: 0, end: 5 });
let err2 =
EMLErrorKind::MissingElement(OwnedQualifiedName::from_static("Test", Some(NS_EML)))
.with_span(Span { start: 10, end: 15 });
let multiple_error = EMLError::from_vec_with_additional(vec![err1], err2);
assert!(matches!(multiple_error, EMLError::Multiple(_)));
let err3 = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
let multiple_error2 = EMLError::from_vec_with_additional(vec![], err3);
assert!(matches!(
multiple_error2,
EMLError::Positioned {
kind: EMLErrorKind::UnexpectedEof,
span: Span { start: 0, end: 10 }
}
));
}
#[test]
fn get_data_from_error() {
let err = EMLErrorKind::UnexpectedEof.with_span(Span { start: 0, end: 10 });
assert!(matches!(err.kind(), &EMLErrorKind::UnexpectedEof));
assert_eq!(err.span(), Some(Span { start: 0, end: 10 }));
let err2 = EMLError::UnknownPosition {
kind: EMLErrorKind::UnexpectedElement(
OwnedQualifiedName::from_static("Test", None),
OwnedQualifiedName::from_static("Test", None),
),
};
assert!(matches!(
err2.kind(),
&EMLErrorKind::UnexpectedElement(_, _)
));
assert_eq!(err2.span(), None);
let err3 = EMLError::Multiple(MultipleEMLErrors {
errors: vec![
EMLError::Positioned {
kind: EMLErrorKind::UnexpectedElement(
OwnedQualifiedName::from_static("Test", None),
OwnedQualifiedName::from_static("Test", None),
),
span: Span { start: 0, end: 5 },
},
EMLError::UnknownPosition {
kind: EMLErrorKind::MissingElement(OwnedQualifiedName::from_static(
"Test", None,
)),
},
],
});
assert!(matches!(err3.kind(), &EMLErrorKind::MissingElement(_)));
assert_eq!(err3.span(), None);
}
}