quick_junit/
errors.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use quick_xml::{encoding::EncodingError, escape::EscapeError, events::attributes::AttrError};
5use std::{fmt, io};
6use thiserror::Error;
7
8/// An error that occurs while serializing a [`Report`](crate::Report).
9///
10/// Returned by [`Report::serialize`](crate::Report::serialize) and
11/// [`Report::to_string`](crate::Report::to_string).
12#[derive(Debug, Error)]
13#[error("error serializing JUnit report")]
14pub struct SerializeError {
15    #[from]
16    inner: quick_xml::Error,
17}
18
19impl From<EncodingError> for SerializeError {
20    fn from(inner: EncodingError) -> Self {
21        Self {
22            inner: quick_xml::Error::Encoding(inner),
23        }
24    }
25}
26
27impl From<io::Error> for SerializeError {
28    fn from(inner: io::Error) -> Self {
29        Self {
30            inner: quick_xml::Error::from(inner),
31        }
32    }
33}
34
35/// Represents a location in the XML document structure.
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub enum PathElement {
38    /// The root `<testsuites>` element
39    TestSuites,
40    /// A `<testsuite>` element at the given index, with optional name
41    TestSuite(usize, Option<String>),
42    /// A `<testcase>` element at the given index, with optional name
43    TestCase(usize, Option<String>),
44    /// The `<properties>` container element
45    Properties,
46    /// A `<property>` element at the given index
47    Property(usize),
48    /// A `<failure>` element
49    Failure,
50    /// An `<error>` element
51    Error,
52    /// A `<skipped>` element
53    Skipped,
54    /// A `<flakyFailure>` element
55    FlakyFailure,
56    /// A `<flakyError>` element
57    FlakyError,
58    /// A `<rerunFailure>` element
59    RerunFailure,
60    /// A `<rerunError>` element
61    RerunError,
62    /// A `<system-out>` element
63    SystemOut,
64    /// A `<system-err>` element
65    SystemErr,
66    /// An attribute with the given name
67    Attribute(String),
68}
69
70impl fmt::Display for PathElement {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            PathElement::TestSuites => write!(f, "testsuites"),
74            PathElement::TestSuite(idx, Some(name)) => {
75                write!(f, "testsuite[{}](\"{}\")", idx, name)
76            }
77            PathElement::TestSuite(idx, None) => write!(f, "testsuite[{}]", idx),
78            PathElement::TestCase(idx, Some(name)) => write!(f, "testcase[{}](\"{}\")", idx, name),
79            PathElement::TestCase(idx, None) => write!(f, "testcase[{}]", idx),
80            PathElement::Properties => write!(f, "properties"),
81            PathElement::Property(idx) => write!(f, "property[{}]", idx),
82            PathElement::Failure => write!(f, "failure"),
83            PathElement::Error => write!(f, "error"),
84            PathElement::Skipped => write!(f, "skipped"),
85            PathElement::FlakyFailure => write!(f, "flakyFailure"),
86            PathElement::FlakyError => write!(f, "flakyError"),
87            PathElement::RerunFailure => write!(f, "rerunFailure"),
88            PathElement::RerunError => write!(f, "rerunError"),
89            PathElement::SystemOut => write!(f, "system-out"),
90            PathElement::SystemErr => write!(f, "system-err"),
91            PathElement::Attribute(name) => write!(f, "@{}", name),
92        }
93    }
94}
95
96/// The kind of error that occurred during deserialization.
97#[derive(Debug, Error)]
98#[non_exhaustive]
99pub enum DeserializeErrorKind {
100    /// An error occurred while parsing XML.
101    #[error("error parsing XML")]
102    XmlError(#[from] quick_xml::Error),
103
104    /// An error occurred while parsing UTF-8.
105    #[error("invalid UTF-8")]
106    Utf8Error(#[from] std::str::Utf8Error),
107
108    /// An error occurred while parsing an integer.
109    #[error("invalid integer: {0}")]
110    ParseIntError(#[from] std::num::ParseIntError),
111
112    /// An error occurred while parsing a float.
113    #[error("invalid float: {0}")]
114    ParseFloatError(#[from] std::num::ParseFloatError),
115
116    /// An error occurred while parsing a timestamp.
117    #[error("invalid timestamp: {0}")]
118    ParseTimestampError(String),
119
120    /// An error occurred while parsing a duration.
121    #[error("invalid duration: {0}")]
122    ParseDurationError(String),
123
124    /// An error occurred while parsing a UUID.
125    #[error("invalid UUID: {0}")]
126    ParseUuidError(#[from] uuid::Error),
127
128    /// Missing required attribute.
129    #[error("missing required attribute: {0}")]
130    MissingAttribute(String),
131
132    /// Unexpected XML element.
133    #[error("unexpected element: {0}")]
134    UnexpectedElement(String),
135
136    /// Invalid XML structure.
137    #[error("invalid structure: {0}")]
138    InvalidStructure(String),
139
140    /// An I/O error occurred.
141    #[error("I/O error")]
142    IoError(#[from] io::Error),
143
144    /// An error occurred while parsing XML attributes.
145    #[error("attribute error: {0}")]
146    AttrError(#[from] AttrError),
147
148    /// An error occurred while unescaping XML entities.
149    #[error("XML unescape error: {0}")]
150    EscapeError(#[from] EscapeError),
151}
152
153/// An error that occurs while deserializing a [`Report`](crate::Report).
154///
155/// Returned by [`Report::deserialize`](crate::Report::deserialize) and
156/// [`Report::deserialize_from_str`](crate::Report::deserialize_from_str).
157#[derive(Debug)]
158pub struct DeserializeError {
159    /// The kind of error that occurred
160    kind: DeserializeErrorKind,
161    /// The path to the location in the XML document where the error occurred
162    path: Vec<PathElement>,
163}
164
165impl DeserializeError {
166    /// Creates a new error with the given kind and path.
167    pub fn new(kind: DeserializeErrorKind, path: Vec<PathElement>) -> Self {
168        Self { kind, path }
169    }
170
171    /// Returns the kind of error.
172    pub fn kind(&self) -> &DeserializeErrorKind {
173        &self.kind
174    }
175
176    /// Returns the path to the error location.
177    pub fn path(&self) -> &[PathElement] {
178        &self.path
179    }
180}
181
182impl fmt::Display for DeserializeError {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        if self.path.is_empty() {
185            write!(f, "{}", self.kind)
186        } else {
187            write!(f, "at ")?;
188            for (i, element) in self.path.iter().enumerate() {
189                if i > 0 {
190                    write!(f, "/")?;
191                }
192                write!(f, "{}", element)?;
193            }
194            write!(f, ": {}", self.kind)
195        }
196    }
197}
198
199impl std::error::Error for DeserializeError {
200    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
201        self.kind.source()
202    }
203}