From 2e12fc977ec8da4243b220bd87b7ba9cd5988494 Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 30 Mar 2022 00:03:32 +0500 Subject: [PATCH] seq: Allow overlapping between list items and other items Example of such XML: Here we need to skip `` in order to collect all ``s. So ability to skip events and replay them later was added This fixes all remaining tests --- .github/workflows/rust.yml | 2 + Cargo.toml | 40 ++++ Changelog.md | 5 + src/de/map.rs | 36 ++-- src/de/mod.rs | 382 +++++++++++++++++++++++++++++++++- src/de/seq.rs | 25 ++- tests/serde-de.rs | 410 +++++++++++++++++++++++++++++-------- 7 files changed, 794 insertions(+), 106 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2dfba93f..3216d9cf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,6 +22,8 @@ jobs: run: cargo test --features encoding,serialize - name: Run tests (escape-html+serialize) run: cargo test --features escape-html,serialize + - name: Run tests (all features) + run: cargo test --all-features - name: Check fmt run: cargo fmt -- --check diff --git a/Cargo.toml b/Cargo.toml index 1113a795..cfa8de1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,46 @@ default = [] ## [standard compliant]: https://www.w3.org/TR/xml11/#charencoding encoding = ["encoding_rs"] +## This feature enables support of deserializing of lists which tags are overlapped +## with tags that does not correspond to the list. +## +## When this feature is enabled, that XML: +## ```xml +## +## +## +## +## +## +## ``` +## could be deserialized to a struct: +## ```ignore +## #[derive(Deserialize)] +## #[serde(rename_all = "kebab-case")] +## struct AnyName { +## item: Vec<()>, +## another_item: (), +## } +## ``` +## +## When feature is not enabled (default), only first element will be assotiated +## with a field, and deserializer will report an error when it encounter a second +## ``. +## +## Note, that enabling this feature can lead to high and even unlimited memory +## consumption, because deserializer should check all events up to the end of a +## container tag (`` in that example) to figure out that there are no +## more items for a field. If `` or EOF even not encountered, the +## parsing will never end which can lead to DoS. +## +## Having several lists and overlapped elements for them in XML could also lead +## to quadratic parsing time, because deserialzier have to check list of events +## as many times as count of sequence fields present. +## +## This feature works only with `serialize` feature and has no effect if `serialize` +## is not enabled. +overlapped-lists = [] + ## Enables support for [`serde`] serialization and deserialization serialize = ["serde"] diff --git a/Changelog.md b/Changelog.md index f31b4287..95b226a3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,11 @@ ## Unreleased +### New Features + +- [#12]: Allow overlapping between elements of sequence and other elements + (using new feature `overlapped-lists`) + ### Bug Fixes - [#9]: Deserialization erroneously was successful in some cases where error is expected. diff --git a/src/de/map.rs b/src/de/map.rs index 97db27ae..167311b4 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -105,6 +105,9 @@ enum ValueSource { /// [list of known fields]: MapAccess::fields Content, /// Next value should be deserialized from an element with a dedicated name. + /// If deserialized type is a sequence, then that sequence will collect all + /// elements with the same name until it will be filled. If not all elements + /// would be consumed, the rest will be ignored. /// /// That state is set when call to [`peek()`] returns a [`Start`] event, which /// [`name()`] represents a field name. That name will be deserialized as a key. @@ -585,20 +588,29 @@ where T: DeserializeSeed<'de>, { let decoder = self.map.de.reader.decoder(); - match self.map.de.peek()? { - // Stop iteration when list elements ends - DeEvent::Start(e) if !self.filter.is_suitable(&e, decoder)? => Ok(None), + loop { + break match self.map.de.peek()? { + // If we see a tag that we not interested, skip it + #[cfg(feature = "overlapped-lists")] + DeEvent::Start(e) if !self.filter.is_suitable(&e, decoder)? => { + self.map.de.skip()?; + continue; + } + // Stop iteration when list elements ends + #[cfg(not(feature = "overlapped-lists"))] + 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().to_owned())), - // We cannot get `Eof` legally, because we always inside of the - // opened tag `self.map.start` - DeEvent::Eof => Err(DeError::UnexpectedEof), + // 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().to_owned())), + // We cannot get `Eof` legally, because we always inside of the + // opened tag `self.map.start` + DeEvent::Eof => Err(DeError::UnexpectedEof), - // Start(tag), Text, CData - _ => seed.deserialize(&mut *self.map.de).map(Some), + // Start(tag), Text, CData + _ => seed.deserialize(&mut *self.map.de).map(Some), + }; } } } diff --git a/src/de/mod.rs b/src/de/mod.rs index 10b5c987..4b8f2290 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -226,6 +226,8 @@ use crate::{ }; use serde::de::{self, Deserialize, DeserializeOwned, Visitor}; use std::borrow::Cow; +#[cfg(feature = "overlapped-lists")] +use std::collections::VecDeque; use std::io::BufRead; pub(crate) const INNER_VALUE: &str = "$value"; @@ -248,12 +250,35 @@ pub enum DeEvent<'a> { Eof, } -/// An xml deserializer +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A structure that deserializes XML into Rust values. pub struct Deserializer<'de, R> where R: XmlRead<'de>, { + /// An XML reader that streams events into this deserializer reader: R, + + /// When deserializing sequences sometimes we have to skip unwanted events. + /// That events should be stored and then replayed. This is a replay buffer, + /// that streams events while not empty. When it exhausted, events will + /// requested from [`Self::reader`]. + #[cfg(feature = "overlapped-lists")] + read: VecDeque>, + /// When deserializing sequences sometimes we have to skip events, because XML + /// is tolerant to elements order and even if in the XSD order is strictly + /// specified (using `xs:sequence`) most of XML parsers allows order violations. + /// That means, that elements, forming a sequence, could be overlapped with + /// other elements, do not related to that sequence. + /// + /// In order to support this, deserializer will scan events and skip unwanted + /// events, store them here. After call [`Self::start_replay()`] all events + /// moved from this to [`Self::read`]. + #[cfg(feature = "overlapped-lists")] + write: VecDeque>, + + #[cfg(not(feature = "overlapped-lists"))] peek: Option>, } @@ -345,6 +370,13 @@ where pub fn new(reader: R) -> Self { Deserializer { reader, + + #[cfg(feature = "overlapped-lists")] + read: VecDeque::new(), + #[cfg(feature = "overlapped-lists")] + write: VecDeque::new(), + + #[cfg(not(feature = "overlapped-lists"))] peek: None, } } @@ -355,6 +387,20 @@ where Self::new(reader) } + #[cfg(feature = "overlapped-lists")] + fn peek(&mut self) -> Result<&DeEvent<'de>, DeError> { + if self.read.is_empty() { + self.read.push_front(self.reader.next()?); + } + if let Some(event) = self.read.front() { + return Ok(&event); + } + // SAFETY: `self.read` was filled in the code above. + // NOTE: Can be replaced with `unsafe { std::hint::unreachable_unchecked() }` + // if unsafe code will be allowed + unreachable!() + } + #[cfg(not(feature = "overlapped-lists"))] fn peek(&mut self) -> Result<&DeEvent<'de>, DeError> { if self.peek.is_none() { self.peek = Some(self.reader.next()?); @@ -370,12 +416,69 @@ where } fn next(&mut self) -> Result, DeError> { + // Replay skipped or peeked events + #[cfg(feature = "overlapped-lists")] + if let Some(event) = self.read.pop_front() { + return Ok(event); + } + #[cfg(not(feature = "overlapped-lists"))] if let Some(e) = self.peek.take() { return Ok(e); } self.reader.next() } + /// Extracts XML tree of events from and stores them in the skipped events + /// buffer from which they can be retrieved later. You MUST call + /// [`Self::start_replay()`] after calling this to give acces to the skipped + /// events and release internal buffers. + #[cfg(feature = "overlapped-lists")] + fn skip(&mut self) -> Result<(), DeError> { + let event = self.next()?; + self.write.push_back(event); + match self.write.back() { + // Skip all subtree, if we skip a start event + Some(DeEvent::Start(e)) => { + let end = e.name().to_owned(); + let mut depth = 0; + loop { + let event = self.next()?; + match event { + DeEvent::Start(ref e) if e.name() == end => { + self.write.push_back(event); + depth += 1; + } + DeEvent::End(ref e) if e.name() == end => { + self.write.push_back(event); + if depth == 0 { + return Ok(()); + } + depth -= 1; + } + _ => self.write.push_back(event), + } + } + } + _ => Ok(()), + } + } + + /// Moves all buffered events to the end of [`Self::write`] buffer and swaps + /// read and write buffers. + /// + /// After calling this method, [`Self::peek()`] and [`Self::next()`] starts + /// return events that was skipped previously by calling [`Self::skip()`], + /// and only when all that events will be consumed, the deserializer starts + /// to drain events from underlying reader. + /// + /// This method MUST be called if any number of [`Self::skip()`] was called + /// after [`Self::new()`] or `start_replay()` or you'll lost events. + #[cfg(feature = "overlapped-lists")] + fn start_replay(&mut self) { + self.write.append(&mut self.read); + std::mem::swap(&mut self.read, &mut self.write); + } + fn next_start(&mut self) -> Result>, DeError> { loop { let e = self.next()?; @@ -465,6 +568,33 @@ where self.reader.decoder() } + /// Drops all events until event with [name](BytesEnd::name()) `name` won't be + /// dropped. This method should be called after [`Self::next()`] + #[cfg(feature = "overlapped-lists")] + fn read_to_end(&mut self, name: &[u8]) -> Result<(), DeError> { + let mut depth = 0; + loop { + match self.read.pop_front() { + Some(DeEvent::Start(e)) if e.name() == name => { + depth += 1; + } + Some(DeEvent::End(e)) if e.name() == name => { + if depth == 0 { + return Ok(()); + } + depth -= 1; + } + + // Drop all other skipped events + Some(_) => continue, + + // If we do not have skipped events, use effective reading that will + // not allocate memory for events + None => return self.reader.read_to_end(name), + } + } + } + #[cfg(not(feature = "overlapped-lists"))] fn read_to_end(&mut self, name: &[u8]) -> Result<(), DeError> { // First one might be in self.peek match self.next()? { @@ -638,7 +768,10 @@ where where V: Visitor<'de>, { - visitor.visit_seq(seq::TopLevelSeqAccess::new(self)?) + let seq = visitor.visit_seq(seq::TopLevelSeqAccess::new(self)?); + #[cfg(feature = "overlapped-lists")] + self.start_replay(); + seq } fn deserialize_map(self, visitor: V) -> Result @@ -793,6 +926,251 @@ mod tests { use super::*; use pretty_assertions::assert_eq; + #[cfg(feature = "overlapped-lists")] + mod skip { + use super::*; + use crate::de::DeEvent::*; + use crate::events::{BytesEnd, BytesText}; + use pretty_assertions::assert_eq; + + /// Checks that `peek()` and `read()` behaves correctly after `skip()` + #[test] + fn read_and_peek() { + let mut de = Deserializer::from_slice( + br#" + + + text + + + + + + "#, + ); + + // Initial conditions - both are empty + assert_eq!(de.read, vec![]); + assert_eq!(de.write, vec![]); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"root")) + ); + assert_eq!( + de.peek().unwrap(), + &Start(BytesStart::borrowed_name(b"inner")) + ); + + // Should skip first tree + de.skip().unwrap(); + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::borrowed_name(b"inner")), + Text(BytesText::from_escaped_str("text")), + Start(BytesStart::borrowed_name(b"inner")), + End(BytesEnd::borrowed(b"inner")), + End(BytesEnd::borrowed(b"inner")), + ] + ); + + // Consume . Now unconsumed XML looks like: + // + // + // text + // + // + // + // + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"next")) + ); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"next"))); + + // We finish writing. Next call to `next()` should start replay that messages: + // + // + // text + // + // + // + // and after that stream that messages: + // + // + // + de.start_replay(); + assert_eq!( + de.read, + vec![ + Start(BytesStart::borrowed_name(b"inner")), + Text(BytesText::from_escaped_str("text")), + Start(BytesStart::borrowed_name(b"inner")), + End(BytesEnd::borrowed(b"inner")), + End(BytesEnd::borrowed(b"inner")), + ] + ); + assert_eq!(de.write, vec![]); + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"inner")) + ); + + // Skip `#text` node and consume after it + de.skip().unwrap(); + assert_eq!( + de.read, + vec![ + Start(BytesStart::borrowed_name(b"inner")), + End(BytesEnd::borrowed(b"inner")), + End(BytesEnd::borrowed(b"inner")), + ] + ); + assert_eq!( + de.write, + vec![ + // This comment here to keep the same formatting of both arrays + // otherwise rustfmt suggest one-line it + Text(BytesText::from_escaped_str("text")), + ] + ); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"inner")) + ); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"inner"))); + + // We finish writing. Next call to `next()` should start replay messages: + // + // text + // + // + // and after that stream that messages: + // + // + // + de.start_replay(); + assert_eq!( + de.read, + vec![ + Text(BytesText::from_escaped_str("text")), + End(BytesEnd::borrowed(b"inner")), + ] + ); + assert_eq!(de.write, vec![]); + assert_eq!( + de.next().unwrap(), + Text(BytesText::from_escaped_str("text")) + ); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"inner"))); + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"target")) + ); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"target"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"root"))); + } + + /// Checks that `read_to_end()` behaves correctly after `skip()` + #[test] + fn read_to_end() { + let mut de = Deserializer::from_slice( + br#" + + + text + + + + + + + "#, + ); + + // Initial conditions - both are empty + assert_eq!(de.read, vec![]); + assert_eq!(de.write, vec![]); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"root")) + ); + + // Skip the tree + de.skip().unwrap(); + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::borrowed_name(b"skip")), + Text(BytesText::from_escaped_str("text")), + Start(BytesStart::borrowed_name(b"skip")), + End(BytesEnd::borrowed(b"skip")), + End(BytesEnd::borrowed(b"skip")), + ] + ); + + // Drop all events thet represents tree. Now unconsumed XML looks like: + // + // + // text + // + // + // + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"target")) + ); + de.read_to_end(b"target").unwrap(); + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::borrowed_name(b"skip")), + Text(BytesText::from_escaped_str("text")), + Start(BytesStart::borrowed_name(b"skip")), + End(BytesEnd::borrowed(b"skip")), + End(BytesEnd::borrowed(b"skip")), + ] + ); + + // We finish writing. Next call to `next()` should start replay that messages: + // + // + // text + // + // + // + // and after that stream that messages: + // + // + de.start_replay(); + assert_eq!( + de.read, + vec![ + Start(BytesStart::borrowed_name(b"skip")), + Text(BytesText::from_escaped_str("text")), + Start(BytesStart::borrowed_name(b"skip")), + End(BytesEnd::borrowed(b"skip")), + End(BytesEnd::borrowed(b"skip")), + ] + ); + assert_eq!(de.write, vec![]); + + assert_eq!( + de.next().unwrap(), + Start(BytesStart::borrowed_name(b"skip")) + ); + de.read_to_end(b"skip").unwrap(); + + assert_eq!(de.next().unwrap(), End(BytesEnd::borrowed(b"root"))); + } + } + #[test] fn read_to_end() { use crate::de::DeEvent::*; diff --git a/src/de/seq.rs b/src/de/seq.rs index 0997ff43..05681fda 100644 --- a/src/de/seq.rs +++ b/src/de/seq.rs @@ -81,6 +81,7 @@ where /// Deserializer used to deserialize sequence items de: &'a mut Deserializer<'de, R>, /// Filter that determines is that tag is a part of this sequence? + /// All tags, that not pass this check, will be skipped filter: TagFilter<'de>, } @@ -111,15 +112,23 @@ where T: DeserializeSeed<'de>, { let decoder = self.de.reader.decoder(); - match self.de.peek()? { - // Stop iteration when list elements ends - DeEvent::Start(e) if !self.filter.is_suitable(e, decoder)? => Ok(None), - // This is unmatched End tag at top-level - DeEvent::End(e) => Err(DeError::UnexpectedEnd(e.name().to_owned())), - DeEvent::Eof => Ok(None), + loop { + break match self.de.peek()? { + // If we see a tag that we not interested, skip it + #[cfg(feature = "overlapped-lists")] + DeEvent::Start(e) if !self.filter.is_suitable(e, decoder)? => { + self.de.skip()?; + continue; + } + // Stop iteration when list elements ends + #[cfg(not(feature = "overlapped-lists"))] + DeEvent::Start(e) if !self.filter.is_suitable(e, decoder)? => Ok(None), + DeEvent::End(_) => Ok(None), + DeEvent::Eof => Ok(None), - // Start(tag), Text, CData - _ => seed.deserialize(&mut *self.de).map(Some), + // Start(tag), Text, CData + _ => seed.deserialize(&mut *self.de).map(Some), + }; } } } diff --git a/tests/serde-de.rs b/tests/serde-de.rs index 278f3580..1b100ff1 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -738,6 +738,8 @@ mod seq { /// That fields should be skipped during deserialization mod unknown_items { use super::*; + #[cfg(not(feature = "overlapped-lists"))] + use pretty_assertions::assert_eq; #[test] fn before() { @@ -771,7 +773,7 @@ mod seq { #[test] fn overlapped() { - from_str::( + let data = from_str::( r#" @@ -780,8 +782,21 @@ mod seq { "#, - ) - .unwrap(); + ); + + #[cfg(feature = "overlapped-lists")] + data.unwrap(); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -791,6 +806,8 @@ mod seq { /// fields comes in an arbitrary order mod field_before_list { use super::*; + #[cfg(not(feature = "overlapped-lists"))] + use pretty_assertions::assert_eq; #[derive(Debug, PartialEq, Deserialize)] struct Root { @@ -830,7 +847,7 @@ mod seq { #[test] fn overlapped() { - from_str::( + let data = from_str::( r#" @@ -839,8 +856,21 @@ mod seq { "#, - ) - .unwrap(); + ); + + #[cfg(feature = "overlapped-lists")] + data.unwrap(); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -850,6 +880,8 @@ mod seq { /// fields comes in an arbitrary order mod field_after_list { use super::*; + #[cfg(not(feature = "overlapped-lists"))] + use pretty_assertions::assert_eq; #[derive(Debug, PartialEq, Deserialize)] struct Root { @@ -889,7 +921,7 @@ mod seq { #[test] fn overlapped() { - from_str::( + let data = from_str::( r#" @@ -898,8 +930,21 @@ mod seq { "#, - ) - .unwrap(); + ); + + #[cfg(feature = "overlapped-lists")] + data.unwrap(); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -907,6 +952,8 @@ mod seq { /// Lists should be deserialized even when them overlaps mod two_lists { use super::*; + #[cfg(not(feature = "overlapped-lists"))] + use pretty_assertions::assert_eq; #[derive(Debug, PartialEq, Deserialize)] struct Pair { @@ -932,7 +979,7 @@ mod seq { #[test] fn overlapped() { - from_str::( + let data = from_str::( r#" @@ -942,8 +989,21 @@ mod seq { "#, - ) - .unwrap(); + ); + + #[cfg(feature = "overlapped-lists")] + data.unwrap(); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -1117,7 +1177,7 @@ mod seq { #[test] fn overlapped() { - let data: List = from_str( + let data = from_str::( r#" @@ -1126,15 +1186,24 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), List { item: vec![(), (), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `item`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `item`")), got {:?}"#, + e + ), + } } } @@ -1200,7 +1269,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -1209,16 +1278,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { node: (), item: vec![(), (), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "duplicate field `item`") + } + e => panic!( + r#"Expected Err(Custom("duplicate field `item`")), got {:?}"#, + e + ), + } } } @@ -1284,7 +1364,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -1293,16 +1373,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { item: vec![(), (), ()], node: (), } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "duplicate field `item`") + } + e => panic!( + r#"Expected Err(Custom("duplicate field `item`")), got {:?}"#, + e + ), + } } } @@ -1344,7 +1435,7 @@ mod seq { #[test] fn overlapped() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -1354,16 +1445,25 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: vec![(), (), ()], element: vec![(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `item`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `item`")), got {:?}"#, + e + ), + } } } @@ -1713,7 +1813,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -1722,16 +1822,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { node: (), item: [Choice::One, Choice::Two, Choice::Other("three".into())], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -1798,7 +1909,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -1807,16 +1918,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { item: [Choice::One, Choice::Two, Choice::Other("three".into())], node: (), } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -1894,7 +2016,7 @@ mod seq { /// elements in an XML, and the first element is a fixed-name one #[test] fn overlapped_fixed_before() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -1904,23 +2026,34 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: [Choice::One, Choice::Two, Choice::Other("three".into())], element: [(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 2") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 2")), got {:?}"#, + e + ), + } } /// A list with fixed-name elements are mixed with a list with variable-name /// elements in an XML, and the first element is a variable-name one #[test] fn overlapped_fixed_after() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -1930,16 +2063,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: [Choice::One, Choice::Two, Choice::Other("three".into())], element: [(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -2012,7 +2156,7 @@ mod seq { /// elements in an XML, and the first element is a fixed-name one #[test] fn overlapped_fixed_before() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2022,23 +2166,34 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: [Choice::One, Choice::Two, Choice::Other("three".into())], element: [(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 2") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 2")), got {:?}"#, + e + ), + } } /// A list with fixed-name elements are mixed with a list with variable-name /// elements in an XML, and the first element is a variable-name one #[test] fn overlapped_fixed_after() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2048,16 +2203,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: [Choice::One, Choice::Two, Choice::Other("three".into())], element: [(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } @@ -2105,7 +2271,7 @@ mod seq { #[test] #[ignore = "There is no way to associate XML elements with `item` or `element` without extra knowledge from type"] fn overlapped() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2115,16 +2281,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: [Choice::One, Choice::Two, Choice::Other("three".into())], element: [Choice2::First, Choice2::Second], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } } @@ -2331,7 +2508,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -2340,16 +2517,25 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { node: (), item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `$value`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `$value`")), got {:?}"#, + e + ), + } } } @@ -2416,7 +2602,7 @@ mod seq { #[test] fn overlapped() { - let data: Root = from_str( + let data = from_str::( r#" @@ -2425,16 +2611,25 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Root { item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], node: (), } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `$value`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `$value`")), got {:?}"#, + e + ), + } } } @@ -2512,7 +2707,7 @@ mod seq { /// elements in an XML, and the first element is a fixed-name one #[test] fn overlapped_fixed_before() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2522,23 +2717,32 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], element: vec![(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `element`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `element`")), got {:?}"#, + e + ), + } } /// A list with fixed-name elements are mixed with a list with variable-name /// elements in an XML, and the first element is a variable-name one #[test] fn overlapped_fixed_after() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2548,16 +2752,25 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], element: vec![(), ()], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `$value`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `$value`")), got {:?}"#, + e + ), + } } } @@ -2630,7 +2843,7 @@ mod seq { /// elements in an XML, and the first element is a fixed-name one #[test] fn overlapped_fixed_before() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2640,23 +2853,32 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { element: vec![(), ()], item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `element`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `element`")), got {:?}"#, + e + ), + } } /// A list with fixed-name elements are mixed with a list with variable-name /// elements in an XML, and the first element is a variable-name one #[test] fn overlapped_fixed_after() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2666,16 +2888,25 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { element: vec![(), ()], item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `$value`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `$value`")), got {:?}"#, + e + ), + } } } @@ -2723,7 +2954,7 @@ mod seq { #[test] #[ignore = "There is no way to associate XML elements with `item` or `element` without extra knowledge from type"] fn overlapped() { - let data: Pair = from_str( + let data = from_str::( r#" @@ -2733,16 +2964,27 @@ mod seq { "#, - ) - .unwrap(); + ); + #[cfg(feature = "overlapped-lists")] assert_eq!( - data, + data.unwrap(), Pair { item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], element: vec![Choice2::First, Choice2::Second], } ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => { + assert_eq!(e, "invalid length 1, expected an array of length 3") + } + e => panic!( + r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, + e + ), + } } } }