Skip to content

Commit

Permalink
Merge pull request #675 from Mingun/errors
Browse files Browse the repository at this point in the history
Rework errors
  • Loading branch information
Mingun authored Nov 3, 2023
2 parents 120e074 + a049cd2 commit b95b503
Show file tree
Hide file tree
Showing 28 changed files with 835 additions and 615 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ include = ["src/*", "LICENSE-MIT.md", "README.md"]
[dependencies]
document-features = { version = "0.2", optional = true }
encoding_rs = { version = "0.8", optional = true }
serde = { version = ">=1.0.100", optional = true }
serde = { version = ">=1.0.139", optional = true }
tokio = { version = "1.10", optional = true, default-features = false, features = ["io-util"] }
memchr = "2.1"
arbitrary = { version = "1", features = ["derive"], optional = true }
Expand Down
12 changes: 12 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@

### Misc Changes

- [#675]: Minimum supported version of serde raised to 1.0.139
- [#675]: Rework the `quick_xml::Error` type to provide more accurate information:
- `Error::EndEventMismatch` replaced by `IllFormedError::MismatchedEnd` in some cases
- `Error::EndEventMismatch` replaced by `IllFormedError::UnmatchedEnd` in some cases
- `Error::TextNotFound` was removed because not used
- `Error::UnexpectedBang` replaced by `SyntaxError`
- `Error::UnexpectedEof` replaced by `SyntaxError` in some cases
- `Error::UnexpectedEof` replaced by `IllFormedError` in some cases
- `Error::UnexpectedToken` replaced by `IllFormedError::DoubleHyphenInComment`

[#675]: https://github.com/tafia/quick-xml/pull/675


## 0.31.0 -- 2023-10-22

Expand Down
29 changes: 25 additions & 4 deletions examples/read_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Note: for this specific data set using serde feature would simplify
// this simple data is purely to make it easier to understand the code

use quick_xml::events::attributes::AttrError;
use quick_xml::events::{BytesStart, Event};
use quick_xml::name::QName;
use quick_xml::reader::Reader;
Expand Down Expand Up @@ -29,6 +30,26 @@ const XML: &str = r#"
</Localization>
"#;

#[derive(Debug)]
enum AppError {
/// XML parsing error
Xml(quick_xml::Error),
/// The `Translation/Text` node is missed
NoText(String),
}

impl From<quick_xml::Error> for AppError {
fn from(error: quick_xml::Error) -> Self {
Self::Xml(error)
}
}

impl From<AttrError> for AppError {
fn from(error: AttrError) -> Self {
Self::Xml(quick_xml::Error::InvalidAttr(error))
}
}

#[derive(Debug)]
struct Translation {
tag: String,
Expand All @@ -40,7 +61,7 @@ impl Translation {
fn new_from_element(
reader: &mut Reader<&[u8]>,
element: BytesStart,
) -> Result<Translation, quick_xml::Error> {
) -> Result<Translation, AppError> {
let mut tag = Cow::Borrowed("");
let mut lang = Cow::Borrowed("");

Expand Down Expand Up @@ -68,16 +89,16 @@ impl Translation {
} else {
dbg!("Expected Event::Start for Text, got: {:?}", &event);
let name_string = reader.decoder().decode(name.as_ref())?;
Err(quick_xml::Error::UnexpectedToken(name_string.into()))
Err(AppError::NoText(name_string.into()))
}
} else {
let event_string = format!("{:?}", event);
Err(quick_xml::Error::UnexpectedToken(event_string))
Err(AppError::NoText(event_string))
}
}
}

fn main() -> Result<(), quick_xml::Error> {
fn main() -> Result<(), AppError> {
// In a real-world use case, Settings would likely be a struct
// HashMap here is just to make the sample code short
let mut settings: HashMap<String, String>;
Expand Down
61 changes: 8 additions & 53 deletions src/de/key.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::de::simple_type::UnitOnly;
use crate::de::str2bool;
use crate::encoding::Decoder;
use crate::errors::serialize::DeError;
use crate::name::QName;
use crate::utils::CowRef;
use serde::de::{DeserializeSeed, Deserializer, EnumAccess, VariantAccess, Visitor};
use serde::de::{DeserializeSeed, Deserializer, EnumAccess, Visitor};
use serde::{forward_to_deserialize_any, serde_if_integer128};
use std::borrow::Cow;

Expand Down Expand Up @@ -240,60 +241,14 @@ impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'de, 'd> {

impl<'de, 'd> EnumAccess<'de> for QNameDeserializer<'de, 'd> {
type Error = DeError;
type Variant = QNameUnitOnly;
type Variant = UnitOnly;

fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where
V: DeserializeSeed<'de>,
{
let name = seed.deserialize(self)?;
Ok((name, QNameUnitOnly))
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

/// Deserializer of variant data, that supports only unit variants.
/// Attempt to deserialize newtype, tuple or struct variant will return a
/// [`DeError::Unsupported`] error.
pub struct QNameUnitOnly;
impl<'de> VariantAccess<'de> for QNameUnitOnly {
type Error = DeError;

#[inline]
fn unit_variant(self) -> Result<(), DeError> {
Ok(())
}

fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, DeError>
where
T: DeserializeSeed<'de>,
{
Err(DeError::Unsupported(
"enum newtype variants are not supported as an XML names".into(),
))
}

fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, DeError>
where
V: Visitor<'de>,
{
Err(DeError::Unsupported(
"enum tuple variants are not supported as an XML names".into(),
))
}

fn struct_variant<V>(
self,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, DeError>
where
V: Visitor<'de>,
{
Err(DeError::Unsupported(
"enum struct variants are not supported as an XML names".into(),
))
Ok((name, UnitOnly))
}
}

Expand Down Expand Up @@ -405,7 +360,7 @@ mod tests {
match err {
DeError::$kind(e) => assert_eq!(e, $reason),
_ => panic!(
"Expected `{}({})`, found `{:?}`",
"Expected `Err({}({}))`, but got `{:?}`",
stringify!($kind),
$reason,
err
Expand Down Expand Up @@ -473,11 +428,11 @@ mod tests {
deserialized_to!(enum_unit: Enum = "Unit" => Enum::Unit);
deserialized_to!(enum_unit_for_attr: Enum = "@Attr" => Enum::Attr);
err!(enum_newtype: Enum = "Newtype"
=> Unsupported("enum newtype variants are not supported as an XML names"));
=> Custom("invalid type: unit value, expected a string"));
err!(enum_tuple: Enum = "Tuple"
=> Unsupported("enum tuple variants are not supported as an XML names"));
=> Custom("invalid type: unit value, expected tuple variant Enum::Tuple"));
err!(enum_struct: Enum = "Struct"
=> Unsupported("enum struct variants are not supported as an XML names"));
=> Custom("invalid type: unit value, expected struct variant Enum::Struct"));

// Field identifiers cannot be serialized, and IgnoredAny represented _something_
// which is not concrete
Expand Down
23 changes: 13 additions & 10 deletions src/de/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
de::{str2bool, DeEvent, Deserializer, XmlRead, TEXT_KEY, VALUE_KEY},
encoding::Decoder,
errors::serialize::DeError,
errors::Error,
events::attributes::IterState,
events::BytesStart,
name::QName,
Expand Down Expand Up @@ -300,7 +301,7 @@ where
}
// We cannot get `Eof` legally, because we always inside of the
// opened tag `self.start`
DeEvent::Eof => Err(DeError::UnexpectedEof),
DeEvent::Eof => Err(Error::missed_end(self.start.name(), decoder).into()),
}
}
}
Expand Down Expand Up @@ -616,9 +617,9 @@ where
if self.fixed_name {
match self.map.de.next()? {
// Handles <field>UnitEnumVariant</field>
DeEvent::Start(_) => {
DeEvent::Start(e) => {
// skip <field>, read text after it and ensure that it is ended by </field>
let text = self.map.de.read_text()?;
let text = self.map.de.read_text(e.name())?;
if text.is_empty() {
// Map empty text (<field/>) to a special `$text` variant
visitor.visit_enum(SimpleTypeDeserializer::from_text(TEXT_KEY.into()))
Expand Down Expand Up @@ -669,8 +670,8 @@ where
seed.deserialize(BorrowedStrDeserializer::<DeError>::new(TEXT_KEY))?,
true,
),
DeEvent::End(e) => return Err(DeError::UnexpectedEnd(e.name().into_inner().to_vec())),
DeEvent::Eof => return Err(DeError::UnexpectedEof),
// SAFETY: we use that deserializer only when we peeked `Start` or `Text` event
_ => unreachable!(),
};
Ok((
name,
Expand Down Expand Up @@ -927,12 +928,14 @@ where
DeEvent::Start(e) if !self.filter.is_suitable(e, decoder)? => Ok(None),

// Stop iteration after reaching a closing tag
DeEvent::End(e) if e.name() == self.map.start.name() => Ok(None),
// This is a unmatched closing tag, so the XML is invalid
DeEvent::End(e) => Err(DeError::UnexpectedEnd(e.name().as_ref().to_owned())),
// The matching tag name is guaranteed by the reader
DeEvent::End(e) => {
debug_assert_eq!(self.map.start.name(), e.name());
Ok(None)
}
// We cannot get `Eof` legally, because we always inside of the
// opened tag `self.map.start`
DeEvent::Eof => Err(DeError::UnexpectedEof),
DeEvent::Eof => Err(Error::missed_end(self.map.start.name(), decoder).into()),

DeEvent::Text(_) => match self.map.de.next()? {
DeEvent::Text(e) => seed.deserialize(TextDeserializer(e)).map(Some),
Expand Down Expand Up @@ -1018,7 +1021,7 @@ where
/// [`CData`]: crate::events::Event::CData
#[inline]
fn read_string(&mut self) -> Result<Cow<'de, str>, DeError> {
self.de.read_text()
self.de.read_text(self.start.name())
}
}

Expand Down
Loading

0 comments on commit b95b503

Please sign in to comment.