From 32feb90524329aafbcec731d91a0b50f11121615 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 9 Aug 2021 23:43:00 +0500 Subject: [PATCH 01/12] Remove excess test - it is covered by struct_::attributes --- src/de/mod.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 66d681ff..41aca5eb 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -675,23 +675,6 @@ mod tests { source: String, } - #[test] - fn simple_struct_from_attributes() { - let s = r##" - - "##; - - let item: Item = from_reader(s.as_bytes()).unwrap(); - - assert_eq!( - item, - Item { - name: "hello".to_string(), - source: "world.rs".to_string(), - } - ); - } - #[test] fn multiple_roots_attributes() { let s = r##" From 1b0b7d4e931801c59c6c97d92ce5539d415a0531 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 9 Aug 2021 23:46:27 +0500 Subject: [PATCH 02/12] Move test `simple_struct_from_attribute_and_child` inside `struct_` sub-module --- src/de/mod.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 41aca5eb..34dcca61 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -699,25 +699,6 @@ mod tests { ); } - #[test] - fn simple_struct_from_attribute_and_child() { - let s = r##" - - world.rs - - "##; - - let item: Item = from_str(s).unwrap(); - - assert_eq!( - item, - Item { - name: "hello".to_string(), - source: "world.rs".to_string(), - } - ); - } - #[derive(Debug, Deserialize, PartialEq)] struct Project { name: String, @@ -955,6 +936,26 @@ mod tests { } ); } + + #[test] + fn attribute_and_element() { + let data: Struct = from_str( + r#" + + answer + + "#, + ) + .unwrap(); + + assert_eq!( + data, + Struct { + float: 42.0, + string: "answer".into() + } + ); + } } mod nested_struct { From ff217ef77f520332d33afefe54d1c623c67c5fc5 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 10 Aug 2021 21:22:04 +0500 Subject: [PATCH 03/12] Add tests with excess attributes/elements/text content --- src/de/mod.rs | 129 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 16 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 34dcca61..99dc3e79 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -870,37 +870,97 @@ mod tests { assert_eq!(item, Item); } - #[test] - fn unit() { + mod unit { + use super::*; + #[derive(Debug, Deserialize, PartialEq)] struct Unit; - let data: Unit = from_str("").unwrap(); - assert_eq!(data, Unit); + #[test] + fn simple() { + let data: Unit = from_str("").unwrap(); + assert_eq!(data, Unit); + } + + #[test] + fn excess_attribute() { + let data: Unit = from_str(r#""#).unwrap(); + assert_eq!(data, Unit); + } + + #[test] + fn excess_element() { + let data: Unit = from_str(r#"element"#).unwrap(); + assert_eq!(data, Unit); + } + + #[test] + fn excess_text() { + let data: Unit = from_str(r#"excess text"#).unwrap(); + assert_eq!(data, Unit); + } + + #[test] + fn excess_cdata() { + let data: Unit = from_str(r#""#).unwrap(); + assert_eq!(data, Unit); + } } - #[test] - fn newtype() { + mod newtype { + use super::*; + #[derive(Debug, Deserialize, PartialEq)] struct Newtype(bool); - let data: Newtype = from_str("true").unwrap(); - assert_eq!(data, Newtype(true)); + #[test] + fn simple() { + let data: Newtype = from_str("true").unwrap(); + assert_eq!(data, Newtype(true)); + } + + #[test] + fn excess_attribute() { + let data: Newtype = from_str(r#"true"#).unwrap(); + assert_eq!(data, Newtype(true)); + } } - #[test] - fn tuple() { - let data: (f32, String) = from_str("42answer").unwrap(); - assert_eq!(data, (42.0, "answer".into())); + mod tuple { + use super::*; + + #[test] + fn simple() { + let data: (f32, String) = from_str("42answer").unwrap(); + assert_eq!(data, (42.0, "answer".into())); + } + + #[test] + fn excess_attribute() { + let data: (f32, String) = + from_str(r#"42answer"#).unwrap(); + assert_eq!(data, (42.0, "answer".into())); + } } - #[test] - fn tuple_struct() { + mod tuple_struct { + use super::*; + #[derive(Debug, Deserialize, PartialEq)] struct Tuple(f32, String); - let data: Tuple = from_str("42answer").unwrap(); - assert_eq!(data, Tuple(42.0, "answer".into())); + #[test] + fn simple() { + let data: Tuple = from_str("42answer").unwrap(); + assert_eq!(data, Tuple(42.0, "answer".into())); + } + + #[test] + fn excess_attribute() { + let data: Tuple = + from_str(r#"42answer"#).unwrap(); + assert_eq!(data, Tuple(42.0, "answer".into())); + } } mod struct_ { @@ -925,6 +985,28 @@ mod tests { ); } + #[test] + fn excess_elements() { + let data: Struct = from_str( + r#" + + + 42 + + answer + + "#, + ) + .unwrap(); + assert_eq!( + data, + Struct { + float: 42.0, + string: "answer".into() + } + ); + } + #[test] fn attributes() { let data: Struct = from_str(r#""#).unwrap(); @@ -937,6 +1019,21 @@ mod tests { ); } + #[test] + fn excess_attributes() { + let data: Struct = from_str( + r#""#, + ) + .unwrap(); + assert_eq!( + data, + Struct { + float: 42.0, + string: "answer".into() + } + ); + } + #[test] fn attribute_and_element() { let data: Struct = from_str( From 5f140a2e81c506548c1a11a57b08c7a449be36e3 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 18 Jul 2021 01:18:42 +0500 Subject: [PATCH 04/12] Add debug implementations for some types: this should help when debugging --- src/de/escape.rs | 2 +- src/events/attributes.rs | 2 +- src/reader.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/de/escape.rs b/src/de/escape.rs index 4738fbd0..f1ae5621 100644 --- a/src/de/escape.rs +++ b/src/de/escape.rs @@ -12,7 +12,7 @@ use std::borrow::Cow; /// Escaping the value is actually not always necessary, for instance /// when converting to float, we don't expect any escapable character /// anyway -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct EscapedDeserializer { decoder: Decoder, /// Possible escaped value of text/CDATA or attribute value diff --git a/src/events/attributes.rs b/src/events/attributes.rs index cc4b1f4c..b7f8a6ec 100644 --- a/src/events/attributes.rs +++ b/src/events/attributes.rs @@ -13,7 +13,7 @@ use std::{borrow::Cow, collections::HashMap, io::BufRead, ops::Range}; /// The duplicate check can be turned off by calling [`with_checks(false)`]. /// /// [`with_checks(false)`]: #method.with_checks -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Attributes<'a> { /// slice of `Element` corresponding to attributes bytes: &'a [u8], diff --git a/src/reader.rs b/src/reader.rs index 22d1c173..961c9be2 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1568,12 +1568,12 @@ impl NamespaceBufferIndex { /// Utf8 Decoder #[cfg(not(feature = "encoding"))] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct Decoder; /// Utf8 Decoder #[cfg(feature = "encoding")] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct Decoder { encoding: &'static Encoding, } From 24cbfc1868d89893d918d7cff195dfd751a6109c Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 18 Jul 2021 15:07:32 +0500 Subject: [PATCH 05/12] Add an auxiliary method to print ownership status of strings This will help when debugging excess allocations --- src/de/mod.rs | 2 +- src/events/attributes.rs | 4 ++-- src/events/mod.rs | 12 ++++++------ src/utils.rs | 19 ++++++++++++++++++- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 99dc3e79..cff53c57 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -578,7 +578,7 @@ mod tests { break; } - assert_eq!(format!("{:?}", event1), format!("{:?}", event2)); + assert_eq!(event1, event2); } } diff --git a/src/events/attributes.rs b/src/events/attributes.rs index b7f8a6ec..83ebd489 100644 --- a/src/events/attributes.rs +++ b/src/events/attributes.rs @@ -281,12 +281,12 @@ impl<'a> Attribute<'a> { impl<'a> std::fmt::Debug for Attribute<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use crate::utils::write_byte_string; + use crate::utils::{write_byte_string, write_cow_string}; write!(f, "Attribute {{ key: ")?; write_byte_string(f, self.key)?; write!(f, ", value: ")?; - write_byte_string(f, &self.value)?; + write_cow_string(f, &self.value)?; write!(f, " }}") } } diff --git a/src/events/mod.rs b/src/events/mod.rs index d366f122..97f94a96 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -365,10 +365,10 @@ impl<'a> BytesStart<'a> { impl<'a> std::fmt::Debug for BytesStart<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use crate::utils::write_byte_string; + use crate::utils::write_cow_string; write!(f, "BytesStart {{ buf: ")?; - write_byte_string(f, &self.buf)?; + write_cow_string(f, &self.buf)?; write!(f, ", name_len: {} }}", self.name_len) } } @@ -548,10 +548,10 @@ impl<'a> BytesEnd<'a> { impl<'a> std::fmt::Debug for BytesEnd<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use crate::utils::write_byte_string; + use crate::utils::write_cow_string; write!(f, "BytesEnd {{ name: ")?; - write_byte_string(f, &self.name)?; + write_cow_string(f, &self.name)?; write!(f, " }}") } } @@ -852,10 +852,10 @@ impl<'a> BytesText<'a> { impl<'a> std::fmt::Debug for BytesText<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use crate::utils::write_byte_string; + use crate::utils::write_cow_string; write!(f, "BytesText {{ content: ")?; - write_byte_string(f, &self.content)?; + write_cow_string(f, &self.content)?; write!(f, " }}") } } diff --git a/src/utils.rs b/src/utils.rs index adef9160..a3fd7c83 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,21 @@ -pub fn write_byte_string(f: &mut std::fmt::Formatter<'_>, byte_string: &[u8]) -> std::fmt::Result { +use std::borrow::Cow; +use std::fmt::{Formatter, Result}; + +pub fn write_cow_string(f: &mut Formatter<'_>, cow_string: &Cow<[u8]>) -> Result { + match cow_string { + Cow::Owned(s) => { + write!(f, "Owned(")?; + write_byte_string(f, &s)?; + } + Cow::Borrowed(s) => { + write!(f, "Borrowed(")?; + write_byte_string(f, s)?; + } + } + write!(f, ")") +} + +pub fn write_byte_string(f: &mut Formatter<'_>, byte_string: &[u8]) -> Result { write!(f, "\"")?; for b in byte_string { match *b { From 0bf9f2a418f8c30446c3013c5c16cc5ec37b4b47 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 10 Aug 2021 00:06:34 +0500 Subject: [PATCH 06/12] Add tests for malformed input for structs and add tests for maps --- src/de/mod.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/src/de/mod.rs b/src/de/mod.rs index cff53c57..1b9487ad 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -963,6 +963,127 @@ mod tests { } } + macro_rules! maplike_errors { + ($type:ty) => { + mod non_closed { + use super::*; + + #[test] + fn attributes() { + let data = from_str::<$type>(r#""#); + + match data { + Err(DeError::Eof) => (), + _ => panic!("Expected `Eof`, found {:?}", data), + } + } + + #[test] + fn elements_root() { + let data = from_str::<$type>(r#"answer"#); + + match data { + Err(DeError::Eof) => (), + _ => panic!("Expected `Eof`, found {:?}", data), + } + } + + #[test] + fn elements_child() { + let data = from_str::<$type>(r#"answer"#); + + match data { + Err(DeError::Eof) => (), + _ => panic!("Expected `Eof`, found {:?}", data), + } + } + } + + mod mismatched_end { + use super::*; + use crate::errors::Error::EndEventMismatch; + + #[test] + fn attributes() { + let data = + from_str::<$type>(r#""#); + + match data { + Err(DeError::Xml(EndEventMismatch { .. })) => (), + _ => panic!("Expected `Xml(EndEventMismatch)`, found {:?}", data), + } + } + + #[test] + fn elements_root() { + let data = from_str::<$type>( + r#"answer"#, + ); + + match data { + Err(DeError::Xml(EndEventMismatch { .. })) => (), + _ => panic!("Expected `Xml(EndEventMismatch)`, found {:?}", data), + } + } + + #[test] + fn elements_child() { + let data = + from_str::<$type>(r#"answer"#); + + match data { + Err(DeError::Xml(EndEventMismatch { .. })) => (), + _ => panic!("Expected `Xml(EndEventMismatch)`, found {:?}", data), + } + } + } + }; + } + + mod map { + use super::*; + use std::collections::HashMap; + use std::iter::FromIterator; + + #[test] + fn elements() { + let data: HashMap<(), ()> = + from_str(r#"42answer"#).unwrap(); + assert_eq!( + data, + HashMap::from_iter([((), ()), ((), ()),].iter().cloned()) + ); + } + + #[test] + fn attributes() { + let data: HashMap<(), ()> = from_str(r#""#).unwrap(); + assert_eq!( + data, + HashMap::from_iter([((), ()), ((), ()),].iter().cloned()) + ); + } + + #[test] + fn attribute_and_element() { + let data: HashMap<(), ()> = from_str( + r#" + + answer + + "#, + ) + .unwrap(); + + assert_eq!( + data, + HashMap::from_iter([((), ()), ((), ()),].iter().cloned()) + ); + } + + maplike_errors!(HashMap<(), ()>); + } + mod struct_ { use super::*; @@ -1053,6 +1174,8 @@ mod tests { } ); } + + maplike_errors!(Struct); } mod nested_struct { From 56e1c63d6df110a6b05777e212712b79eb0eda9c Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 12 Aug 2021 00:16:20 +0500 Subject: [PATCH 07/12] Allow deserialization of unit from any escaped value. This will allow deserialization of attributes into fields with unit types when you don't needed the actual value of the attribute but only the fact that attribute was defined --- src/de/escape.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/de/escape.rs b/src/de/escape.rs index f1ae5621..b487e5c4 100644 --- a/src/de/escape.rs +++ b/src/de/escape.rs @@ -145,13 +145,7 @@ impl<'de> serde::Deserializer<'de> for EscapedDeserializer { where V: Visitor<'de>, { - if self.escaped_value.is_empty() { - visitor.visit_unit() - } else { - Err(DeError::InvalidUnit( - "Expecting unit, got non empty attribute".into(), - )) - } + visitor.visit_unit() } fn deserialize_option(self, visitor: V) -> Result From f5e3251b087037583c1e5f41f5b1e9c9d34d342f Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 22 Aug 2021 01:47:03 +0500 Subject: [PATCH 08/12] Convert `Error::UnexpectedEof` into `DeError::Eof` --- src/de/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 1b9487ad..8a87202e 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -114,6 +114,7 @@ mod var; pub use crate::errors::serialize::DeError; use crate::{ + errors::Error, events::{BytesStart, BytesText, Event}, reader::Decoder, Reader, @@ -514,7 +515,10 @@ impl<'i, R: BufRead + 'i> BorrowingReader<'i> for IoReader { } fn read_to_end(&mut self, name: &[u8]) -> Result<(), DeError> { - Ok(self.reader.read_to_end(name, &mut self.buf)?) + match self.reader.read_to_end(name, &mut self.buf) { + Err(Error::UnexpectedEof(_)) => Err(DeError::Eof), + other => Ok(other?), + } } fn decoder(&self) -> Decoder { @@ -540,7 +544,10 @@ impl<'de> BorrowingReader<'de> for SliceReader<'de> { } fn read_to_end(&mut self, name: &[u8]) -> Result<(), DeError> { - Ok(self.reader.read_to_end_unbuffered(name)?) + match self.reader.read_to_end_unbuffered(name) { + Err(Error::UnexpectedEof(_)) => Err(DeError::Eof), + other => Ok(other?), + } } fn decoder(&self) -> Decoder { From 12c753d98b6f5ae9beae4462a73680bca0fb71dc Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 22 Aug 2021 01:55:31 +0500 Subject: [PATCH 09/12] Update changelog --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index a079fd8b..ff485019 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,10 @@ ## Unreleased +- test: add tests for malformed inputs for serde deserializer +- fix: allow to deserialize `unit`s from any data in attribute values and text nodes +- refactor: unify errors when EOF encountered during serde deserialization + ## 0.23.0-alpha3 - fix: use element name (with namespace) when unflattening (serialize feature) From d3cb093128c1a9d3ad561921151a0b34f90f1eb5 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 22 Aug 2021 22:32:29 +0500 Subject: [PATCH 10/12] Add test for `read_to_end` This method is important for basic deserialization methods so need to test it --- src/de/mod.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/de/mod.rs b/src/de/mod.rs index 8a87202e..fb1a33bf 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -560,6 +560,58 @@ mod tests { use super::*; use serde::Deserialize; + #[test] + fn read_to_end() { + use crate::events::BytesEnd; + use crate::events::Event::*; + + let mut reader = Reader::from_bytes( + r#" + + textcontent + + + + "# + .as_bytes(), + ); + reader + .expand_empty_elements(true) + .check_end_names(true) + .trim_text(true); + let mut de = Deserializer::from_borrowing_reader(SliceReader { reader }); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"root")) + ); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed(br#"tag a="1""#, 3)) + ); + assert_eq!(de.read_to_end(b"tag").unwrap(), ()); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed(br#"tag a="2""#, 3)) + ); + assert_eq!( + de.next().unwrap(), + CData(BytesText::from_plain_str("cdata content")) + ); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"tag"))); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed(b"self-closed", 11)) + ); + assert_eq!(de.read_to_end(b"self-closed").unwrap(), ()); + + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"root"))); + assert_eq!(de.next().unwrap(), Eof); + } + #[test] fn borrowing_reader_parity() { let s = r##" From d683c377d9d9b75c2daabbb384030a30287ab624 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 23 Aug 2021 00:16:44 +0500 Subject: [PATCH 11/12] Add check that all XML events was consumed if deserialization is succedeed --- Changelog.md | 2 ++ src/de/mod.rs | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index ff485019..8af95792 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,8 @@ - test: add tests for malformed inputs for serde deserializer - fix: allow to deserialize `unit`s from any data in attribute values and text nodes - refactor: unify errors when EOF encountered during serde deserialization +- test: ensure that after deserializing all XML was consumed +- feat: add `Deserializer::from_str` and `Deserializer::from_bytes` ## 0.23.0-alpha3 diff --git a/src/de/mod.rs b/src/de/mod.rs index fb1a33bf..f27e01b2 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -148,12 +148,7 @@ pub fn from_str<'de, T: Deserialize<'de>>(s: &'de str) -> Result { /// Deserialize a xml slice of bytes pub fn from_bytes<'de, T: Deserialize<'de>>(s: &'de [u8]) -> Result { - let mut reader = Reader::from_bytes(s); - reader - .expand_empty_elements(true) - .check_end_names(true) - .trim_text(true); - let mut de = Deserializer::from_borrowing_reader(SliceReader { reader }); + let mut de = Deserializer::from_bytes(s); T::deserialize(&mut de) } @@ -263,6 +258,23 @@ impl<'de, R: BorrowingReader<'de>> Deserializer<'de, R> { } } +impl<'de> Deserializer<'de, SliceReader<'de>> { + /// Create new deserializer that will borrow data from the specified string + pub fn from_str(s: &'de str) -> Self { + Self::from_bytes(s.as_bytes()) + } + + /// Create new deserializer that will borrow data from the specified byte array + pub fn from_bytes(bytes: &'de [u8]) -> Self { + let mut reader = Reader::from_bytes(bytes); + reader + .expand_empty_elements(true) + .check_end_names(true) + .trim_text(true); + Self::from_borrowing_reader(SliceReader { reader }) + } +} + macro_rules! deserialize_type { ($deserialize:ident => $visit:ident) => { fn $deserialize>(self, visitor: V) -> Result { @@ -560,6 +572,20 @@ mod tests { use super::*; use serde::Deserialize; + /// Deserialize an instance of type T from a string of XML text. + /// If deserialization was succeeded checks that all XML events was consumed + fn from_str<'de, T: Deserialize<'de>>(s: &'de str) -> Result { + let mut de = Deserializer::from_str(s); + let result = T::deserialize(&mut de); + + // If type was deserialized, the whole XML document should be consumed + if let Ok(_) = result { + assert_eq!(de.next().unwrap(), Event::Eof); + } + + result + } + #[test] fn read_to_end() { use crate::events::BytesEnd; From b3c1595caedd0fc20dd6a7b8ead8295cad6f7b0e Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 23 Aug 2021 00:52:40 +0500 Subject: [PATCH 12/12] Add test for `ignored_any` --- src/de/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/de/mod.rs b/src/de/mod.rs index f27e01b2..f689463b 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -452,10 +452,18 @@ impl<'de, 'a, R: BorrowingReader<'de>> de::Deserializer<'de> for &'a mut Deseria self.deserialize_string(visitor) } + /// Always call `visitor.visit_unit()` because returned value ignored in any case. + /// + /// This method consumes any single [event][Event] except the [`Start`][Event::Start] + /// event. in which case all events up to corresponding [`End`][Event::End] event will + /// be consumed. + /// + /// This method returns error if current event is [`End`][Event::End] or [`Eof`][Event::Eof] fn deserialize_ignored_any>(self, visitor: V) -> Result { match self.next()? { Event::Start(e) => self.read_to_end(e.name())?, Event::End(_) => return Err(DeError::End), + Event::Eof => return Err(DeError::Eof), _ => (), } visitor.visit_unit() @@ -570,6 +578,7 @@ impl<'de> BorrowingReader<'de> for SliceReader<'de> { #[cfg(test)] mod tests { use super::*; + use serde::de::IgnoredAny; use serde::Deserialize; /// Deserialize an instance of type T from a string of XML text. @@ -955,6 +964,22 @@ mod tests { assert_eq!(item, Item); } + /// Tests calling `deserialize_ignored_any` + #[test] + fn ignored_any() { + let err = from_str::(""); + match err { + Err(DeError::Eof) => {} + other => panic!("Expected `Eof`, found {:?}", other), + } + + from_str::(r#""#).unwrap(); + from_str::(r#""#).unwrap(); + from_str::(r#"text"#).unwrap(); + from_str::(r#""#).unwrap(); + from_str::(r#""#).unwrap(); + } + mod unit { use super::*;