use quick_xml::{encoding::EncodingError, escape::EscapeError, events::attributes::AttrError};
use std::{fmt, io};
use thiserror::Error;
#[derive(Debug, Error)]
#[error("error serializing JUnit report")]
pub struct SerializeError {
#[from]
inner: quick_xml::Error,
}
impl From<EncodingError> for SerializeError {
fn from(inner: EncodingError) -> Self {
Self {
inner: quick_xml::Error::Encoding(inner),
}
}
}
impl From<io::Error> for SerializeError {
fn from(inner: io::Error) -> Self {
Self {
inner: quick_xml::Error::from(inner),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathElement {
TestSuites,
TestSuite(usize, Option<String>),
TestCase(usize, Option<String>),
Properties,
Property(usize),
Failure,
Error,
Skipped,
FlakyFailure,
FlakyError,
RerunFailure,
RerunError,
SystemOut,
SystemErr,
Attribute(String),
}
impl fmt::Display for PathElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathElement::TestSuites => write!(f, "testsuites"),
PathElement::TestSuite(idx, Some(name)) => {
write!(f, "testsuite[{}](\"{}\")", idx, name)
}
PathElement::TestSuite(idx, None) => write!(f, "testsuite[{}]", idx),
PathElement::TestCase(idx, Some(name)) => write!(f, "testcase[{}](\"{}\")", idx, name),
PathElement::TestCase(idx, None) => write!(f, "testcase[{}]", idx),
PathElement::Properties => write!(f, "properties"),
PathElement::Property(idx) => write!(f, "property[{}]", idx),
PathElement::Failure => write!(f, "failure"),
PathElement::Error => write!(f, "error"),
PathElement::Skipped => write!(f, "skipped"),
PathElement::FlakyFailure => write!(f, "flakyFailure"),
PathElement::FlakyError => write!(f, "flakyError"),
PathElement::RerunFailure => write!(f, "rerunFailure"),
PathElement::RerunError => write!(f, "rerunError"),
PathElement::SystemOut => write!(f, "system-out"),
PathElement::SystemErr => write!(f, "system-err"),
PathElement::Attribute(name) => write!(f, "@{}", name),
}
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DeserializeErrorKind {
#[error("error parsing XML")]
XmlError(#[from] quick_xml::Error),
#[error("invalid UTF-8")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("invalid integer: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("invalid float: {0}")]
ParseFloatError(#[from] std::num::ParseFloatError),
#[error("invalid timestamp: {0}")]
ParseTimestampError(String),
#[error("invalid duration: {0}")]
ParseDurationError(String),
#[error("invalid UUID: {0}")]
ParseUuidError(#[from] uuid::Error),
#[error("missing required attribute: {0}")]
MissingAttribute(String),
#[error("unexpected element: {0}")]
UnexpectedElement(String),
#[error("invalid structure: {0}")]
InvalidStructure(String),
#[error("I/O error")]
IoError(#[from] io::Error),
#[error("attribute error: {0}")]
AttrError(#[from] AttrError),
#[error("XML unescape error: {0}")]
EscapeError(#[from] EscapeError),
}
#[derive(Debug)]
pub struct DeserializeError {
kind: DeserializeErrorKind,
path: Vec<PathElement>,
}
impl DeserializeError {
pub fn new(kind: DeserializeErrorKind, path: Vec<PathElement>) -> Self {
Self { kind, path }
}
pub fn kind(&self) -> &DeserializeErrorKind {
&self.kind
}
pub fn path(&self) -> &[PathElement] {
&self.path
}
}
impl fmt::Display for DeserializeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.path.is_empty() {
write!(f, "{}", self.kind)
} else {
write!(f, "at ")?;
for (i, element) in self.path.iter().enumerate() {
if i > 0 {
write!(f, "/")?;
}
write!(f, "{}", element)?;
}
write!(f, ": {}", self.kind)
}
}
}
impl std::error::Error for DeserializeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.kind.source()
}
}