diff --git a/src/de/map.rs b/src/de/map.rs index dc317e85..26d5532b 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -553,13 +553,13 @@ where } else { TagFilter::Exclude(self.map.fields) }; - let seq = visitor.visit_seq(MapValueSeqAccess { + visitor.visit_seq(MapValueSeqAccess { + #[cfg(feature = "overlapped-lists")] + checkpoint: self.map.de.skip_checkpoint(), + map: self.map, filter, - }); - #[cfg(feature = "overlapped-lists")] - self.map.de.start_replay(); - seq + }) } #[inline] @@ -588,6 +588,22 @@ where /// When feature `overlapped-lists` is activated, all tags, that not pass /// this check, will be skipped. filter: TagFilter<'de>, + + /// Checkpoint after which all skipped events should be returned. All events, + /// that was skipped before creating this checkpoint, will still stay buffered + /// and will not be returned + #[cfg(feature = "overlapped-lists")] + checkpoint: usize, +} + +#[cfg(feature = "overlapped-lists")] +impl<'de, 'a, 'm, R> Drop for MapValueSeqAccess<'de, 'a, 'm, R> +where + R: XmlRead<'de>, +{ + fn drop(&mut self) { + self.map.de.start_replay(self.checkpoint); + } } impl<'de, 'a, 'm, R> SeqAccess<'de> for MapValueSeqAccess<'de, 'a, 'm, R> diff --git a/src/de/mod.rs b/src/de/mod.rs index af1d709e..0421e661 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -483,6 +483,14 @@ where self.reader.next() } + /// Returns the mark after which all events, skipped by [`Self::skip()`] call, + /// should be replayed after calling [`Self::start_replay()`]. + #[cfg(feature = "overlapped-lists")] + #[inline] + fn skip_checkpoint(&self) -> usize { + self.write.len() + } + /// 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 access to the skipped @@ -530,8 +538,8 @@ where Ok(()) } - /// Moves all buffered events to the end of [`Self::write`] buffer and swaps - /// read and write buffers. + /// Moves buffered events, skipped after given `checkpoint` from [`Self::write`] + /// skip buffer to [`Self::read`] buffer. /// /// After calling this method, [`Self::peek()`] and [`Self::next()`] starts /// return events that was skipped previously by calling [`Self::skip()`], @@ -541,9 +549,15 @@ where /// 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 start_replay(&mut self, checkpoint: usize) { + if checkpoint == 0 { + self.write.append(&mut self.read); + std::mem::swap(&mut self.read, &mut self.write); + } else { + let mut read = self.write.split_off(checkpoint); + read.append(&mut self.read); + self.read = read; + } } fn next_start(&mut self) -> Result>, DeError> { @@ -828,10 +842,7 @@ where where V: Visitor<'de>, { - let seq = visitor.visit_seq(seq::TopLevelSeqAccess::new(self)?); - #[cfg(feature = "overlapped-lists")] - self.start_replay(); - seq + visitor.visit_seq(seq::TopLevelSeqAccess::new(self)?) } fn deserialize_map(self, visitor: V) -> Result @@ -1024,6 +1035,10 @@ mod tests { assert_eq!(de.next().unwrap(), Start(BytesStart::new("root"))); assert_eq!(de.peek().unwrap(), &Start(BytesStart::new("inner"))); + // Mark that start_replay() should begin replay from this point + let checkpoint = de.skip_checkpoint(); + assert_eq!(checkpoint, 0); + // Should skip first tree de.skip().unwrap(); assert_eq!(de.read, vec![]); @@ -1060,7 +1075,7 @@ mod tests { // // // - de.start_replay(); + de.start_replay(checkpoint); assert_eq!( de.read, vec![ @@ -1074,6 +1089,10 @@ mod tests { assert_eq!(de.write, vec![]); assert_eq!(de.next().unwrap(), Start(BytesStart::new("inner"))); + // Mark that start_replay() should begin replay from this point + let checkpoint = de.skip_checkpoint(); + assert_eq!(checkpoint, 0); + // Skip `#text` node and consume after it de.skip().unwrap(); assert_eq!( @@ -1105,7 +1124,7 @@ mod tests { // // // - de.start_replay(); + de.start_replay(checkpoint); assert_eq!( de.read, vec![ @@ -1119,6 +1138,7 @@ mod tests { assert_eq!(de.next().unwrap(), Start(BytesStart::new("target"))); assert_eq!(de.next().unwrap(), End(BytesEnd::new("target"))); assert_eq!(de.next().unwrap(), End(BytesEnd::new("root"))); + assert_eq!(de.next().unwrap(), Eof); } /// Checks that `read_to_end()` behaves correctly after `skip()` @@ -1144,6 +1164,10 @@ mod tests { assert_eq!(de.next().unwrap(), Start(BytesStart::new("root"))); + // Mark that start_replay() should begin replay from this point + let checkpoint = de.skip_checkpoint(); + assert_eq!(checkpoint, 0); + // Skip the tree de.skip().unwrap(); assert_eq!(de.read, vec![]); @@ -1189,7 +1213,7 @@ mod tests { // and after that stream that messages: // // - de.start_replay(); + de.start_replay(checkpoint); assert_eq!( de.read, vec![ @@ -1206,6 +1230,206 @@ mod tests { de.read_to_end(QName(b"skip")).unwrap(); assert_eq!(de.next().unwrap(), End(BytesEnd::new("root"))); + assert_eq!(de.next().unwrap(), Eof); + } + + /// Checks that replay replayes only part of events + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn partial_replay() { + let mut de = Deserializer::from_str( + r#" + + + + + + + + + + + "#, + ); + + // Initial conditions - both are empty + assert_eq!(de.read, vec![]); + assert_eq!(de.write, vec![]); + + assert_eq!(de.next().unwrap(), Start(BytesStart::new("root"))); + + // start_replay() should start replay from this point + let checkpoint1 = de.skip_checkpoint(); + assert_eq!(checkpoint1, 0); + + // Should skip first and second elements + de.skip().unwrap(); // skipped-1 + de.skip().unwrap(); // skipped-2 + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + + //////////////////////////////////////////////////////////////////////////////////////// + + assert_eq!(de.next().unwrap(), Start(BytesStart::new("inner"))); + assert_eq!(de.peek().unwrap(), &Start(BytesStart::new("skipped-3"))); + assert_eq!( + de.read, + vec![ + // This comment here to keep the same formatting of both arrays + // otherwise rustfmt suggest one-line it + Start(BytesStart::new("skipped-3")), + ] + ); + assert_eq!( + de.write, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + + // start_replay() should start replay from this point + let checkpoint2 = de.skip_checkpoint(); + assert_eq!(checkpoint2, 4); + + // Should skip third and forth elements + de.skip().unwrap(); // skipped-3 + de.skip().unwrap(); // skipped-4 + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + // checkpoint 1 + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + // checkpoint 2 + Start(BytesStart::new("skipped-3")), + End(BytesEnd::new("skipped-3")), + Start(BytesStart::new("skipped-4")), + End(BytesEnd::new("skipped-4")), + ] + ); + assert_eq!(de.next().unwrap(), Start(BytesStart::new("target-2"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("target-2"))); + assert_eq!(de.peek().unwrap(), &End(BytesEnd::new("inner"))); + assert_eq!( + de.read, + vec![ + // This comment here to keep the same formatting of both arrays + // otherwise rustfmt suggest one-line it + End(BytesEnd::new("inner")), + ] + ); + assert_eq!( + de.write, + vec![ + // checkpoint 1 + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + // checkpoint 2 + Start(BytesStart::new("skipped-3")), + End(BytesEnd::new("skipped-3")), + Start(BytesStart::new("skipped-4")), + End(BytesEnd::new("skipped-4")), + ] + ); + + // Start replay events from checkpoint 2 + de.start_replay(checkpoint2); + assert_eq!( + de.read, + vec![ + Start(BytesStart::new("skipped-3")), + End(BytesEnd::new("skipped-3")), + Start(BytesStart::new("skipped-4")), + End(BytesEnd::new("skipped-4")), + End(BytesEnd::new("inner")), + ] + ); + assert_eq!( + de.write, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + + // Replayed events + assert_eq!(de.next().unwrap(), Start(BytesStart::new("skipped-3"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("skipped-3"))); + assert_eq!(de.next().unwrap(), Start(BytesStart::new("skipped-4"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("skipped-4"))); + + assert_eq!(de.next().unwrap(), End(BytesEnd::new("inner"))); + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + + //////////////////////////////////////////////////////////////////////////////////////// + + // New events + assert_eq!(de.next().unwrap(), Start(BytesStart::new("target-1"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("target-1"))); + + assert_eq!(de.read, vec![]); + assert_eq!( + de.write, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + + // Start replay events from checkpoint 1 + de.start_replay(checkpoint1); + assert_eq!( + de.read, + vec![ + Start(BytesStart::new("skipped-1")), + End(BytesEnd::new("skipped-1")), + Start(BytesStart::new("skipped-2")), + End(BytesEnd::new("skipped-2")), + ] + ); + assert_eq!(de.write, vec![]); + + // Replayed events + assert_eq!(de.next().unwrap(), Start(BytesStart::new("skipped-1"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("skipped-1"))); + assert_eq!(de.next().unwrap(), Start(BytesStart::new("skipped-2"))); + assert_eq!(de.next().unwrap(), End(BytesEnd::new("skipped-2"))); + + assert_eq!(de.read, vec![]); + assert_eq!(de.write, vec![]); + + // New events + assert_eq!(de.next().unwrap(), End(BytesEnd::new("root"))); + assert_eq!(de.next().unwrap(), Eof); } /// Checks that limiting buffer size works correctly diff --git a/src/de/seq.rs b/src/de/seq.rs index 091b968b..00ad240e 100644 --- a/src/de/seq.rs +++ b/src/de/seq.rs @@ -82,6 +82,12 @@ where /// When feature `overlapped-lists` is activated, all tags, that not pass /// this check, will be skipped. filter: TagFilter<'de>, + + /// Checkpoint after which all skipped events should be returned. All events, + /// that was skipped before creating this checkpoint, will still stay buffered + /// and will not be returned + #[cfg(feature = "overlapped-lists")] + checkpoint: usize, } impl<'a, 'de, R> TopLevelSeqAccess<'de, 'a, R> @@ -96,7 +102,23 @@ where } else { TagFilter::Exclude(&[]) }; - Ok(Self { de, filter }) + Ok(Self { + #[cfg(feature = "overlapped-lists")] + checkpoint: de.skip_checkpoint(), + + de, + filter, + }) + } +} + +#[cfg(feature = "overlapped-lists")] +impl<'de, 'a, R> Drop for TopLevelSeqAccess<'de, 'a, R> +where + R: XmlRead<'de>, +{ + fn drop(&mut self) { + self.de.start_replay(self.checkpoint); } } diff --git a/tests/serde-de.rs b/tests/serde-de.rs index 96cf9f5f..1b6deebc 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -765,6 +765,40 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + outer: [List; 3], + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[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 + ), + } + } } /// In those tests non-sequential field is defined in the struct @@ -839,6 +873,41 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + node: (), + outer: [List; 3], + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[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 + ), + } + } } /// In those tests non-sequential field is defined in the struct @@ -913,6 +982,41 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + outer: [List; 3], + node: (), + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[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 + ), + } + } } /// In those tests two lists are deserialized simultaneously. @@ -972,6 +1076,42 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Pair { + outer: [List; 3], + element: [(); 2], + } + + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[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 + ), + } + } } /// Deserialization of primitives slightly differs from deserialization @@ -1300,6 +1440,47 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + outer: Vec, + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + outer: vec![ + List { item: vec![()] }, + List { item: vec![()] }, + List { item: vec![()] }, + ], + } + ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `outer`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `outer`")), got {:?}"#, + e + ), + } + } } /// In those tests non-sequential field is defined in the struct @@ -1395,6 +1576,49 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + node: (), + outer: Vec, + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + node: (), + outer: vec![ + List { item: vec![()] }, + List { item: vec![()] }, + List { item: vec![()] }, + ], + } + ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `outer`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `outer`")), got {:?}"#, + e + ), + } + } } /// In those tests non-sequential field is defined in the struct @@ -1490,6 +1714,49 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + outer: Vec, + node: (), + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + outer: vec![ + List { item: vec![()] }, + List { item: vec![()] }, + List { item: vec![()] }, + ], + node: (), + } + ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `outer`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `outer`")), got {:?}"#, + e + ), + } + } } /// In those tests two lists are deserialized simultaneously. @@ -1560,6 +1827,48 @@ mod seq { ), } } + + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Pair { + outer: Vec, + element: Vec<()>, + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Pair { + outer: vec![ + List { item: vec![()] }, + List { item: vec![()] }, + List { item: vec![()] }, + ], + element: vec![()], + } + ); + + #[cfg(not(feature = "overlapped-lists"))] + match data { + Err(DeError::Custom(e)) => assert_eq!(e, "duplicate field `outer`"), + e => panic!( + r#"Expected Err(Custom("duplicate field `outer`")), got {:?}"#, + e + ), + } + } } /// Deserialization of primitives slightly differs from deserialization @@ -1824,6 +2133,19 @@ mod seq { Other, } + #[derive(Debug, PartialEq, Deserialize)] + #[serde(rename_all = "kebab-case")] + enum Choice4 { + One { + inner: [(); 1], + }, + Two { + inner: [(); 1], + }, + #[serde(other)] + Other, + } + /// This module contains tests where size of the list have a compile-time size mod fixed_size { use super::*; @@ -2048,32 +2370,78 @@ mod seq { ), } } - } - - /// In those tests non-sequential field is defined in the struct - /// after sequential, so it will be deserialized after the list. - /// That struct should be deserialized from the XML where these - /// fields comes in an arbitrary order - mod field_after_list { - use super::*; - use pretty_assertions::assert_eq; - - #[derive(Debug, PartialEq, Deserialize)] - struct Root { - #[serde(rename = "$value")] - item: [Choice; 3], - node: (), - } + /// Test for https://github.com/tafia/quick-xml/issues/435 #[test] - fn before() { - let data: Root = from_str( + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + node: (), + #[serde(rename = "$value")] + item: [Choice4; 3], + } + + let data = from_str::( r#" + - - - + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + node: (), + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); + + #[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 + ), + } + } + } + + /// In those tests non-sequential field is defined in the struct + /// after sequential, so it will be deserialized after the list. + /// That struct should be deserialized from the XML where these + /// fields comes in an arbitrary order + mod field_after_list { + use super::*; + use pretty_assertions::assert_eq; + + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + #[serde(rename = "$value")] + item: [Choice; 3], + node: (), + } + + #[test] + fn before() { + let data: Root = from_str( + r#" + + + + + "#, ) @@ -2144,6 +2512,52 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + #[serde(rename = "$value")] + item: [Choice4; 3], + node: (), + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + 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 + ), + } + } } /// In those tests two lists are deserialized simultaneously. @@ -2216,77 +2630,169 @@ mod seq { ); } - /// A list with fixed-name elements are mixed with a list with variable-name - /// elements in an XML, and the first element is a fixed-name one - #[test] - fn overlapped_fixed_before() { - let data = from_str::( - r#" - - - - - - - - "#, - ); + mod overlapped { + use super::*; + use pretty_assertions::assert_eq; - #[cfg(feature = "overlapped-lists")] - assert_eq!( - data.unwrap(), - Pair { - item: [Choice::One, Choice::Two, Choice::Other("three".into())], - element: [(), ()], + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + #[serde(rename = "$value")] + item: [Choice4; 3], + element: [(); 2], + } + + /// A list with fixed-name elements are mixed with a list with variable-name + /// elements in an XML, and the first element is a fixed-name one + #[test] + fn fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - ); + } - #[cfg(not(feature = "overlapped-lists"))] - match data { - Err(DeError::Custom(e)) => { - assert_eq!(e, "invalid length 1, expected an array of length 2") + /// 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 fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - 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 = from_str::( - r#" - - - - - - - - "#, - ); + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + element: [(); 2], + } + ); - #[cfg(feature = "overlapped-lists")] - assert_eq!( - 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 + ), } - ); + } - #[cfg(not(feature = "overlapped-lists"))] - match data { - Err(DeError::Custom(e)) => { - assert_eq!(e, "invalid length 1, expected an array of length 3") + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + element: [(); 2], + } + ); + + #[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 + ), } - e => panic!( - r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, - e - ), } } } @@ -2356,77 +2862,169 @@ mod seq { ); } - /// A list with fixed-name elements are mixed with a list with variable-name - /// elements in an XML, and the first element is a fixed-name one - #[test] - fn overlapped_fixed_before() { - let data = from_str::( - r#" - - - - - - - - "#, - ); + mod overlapped { + use super::*; + use pretty_assertions::assert_eq; - #[cfg(feature = "overlapped-lists")] - assert_eq!( - data.unwrap(), - Pair { - item: [Choice::One, Choice::Two, Choice::Other("three".into())], - element: [(), ()], + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + element: [(); 2], + #[serde(rename = "$value")] + item: [Choice4; 3], + } + + /// A list with fixed-name elements are mixed with a list with variable-name + /// elements in an XML, and the first element is a fixed-name one + #[test] + fn fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - ); + } - #[cfg(not(feature = "overlapped-lists"))] - match data { - Err(DeError::Custom(e)) => { - assert_eq!(e, "invalid length 1, expected an array of length 2") + /// 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 fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - 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 = from_str::( - r#" - - - - - - - - "#, - ); + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + element: [(); 2], + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); - #[cfg(feature = "overlapped-lists")] - assert_eq!( - 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 + ), } - ); + } - #[cfg(not(feature = "overlapped-lists"))] - match data { - Err(DeError::Custom(e)) => { - assert_eq!(e, "invalid length 1, expected an array of length 3") + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + element: [(); 2], + item: [ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); + + #[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 + ), } - e => panic!( - r#"Expected Err(Custom("invalid length 1, expected an array of length 3")), got {:?}"#, - e - ), } } } @@ -2867,6 +3465,52 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + node: (), + #[serde(rename = "$value")] + item: Vec, + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + node: (), + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); + + #[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 + ), + } + } } /// In those tests non-sequential field is defined in the struct @@ -2961,6 +3605,52 @@ mod seq { ), } } + + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn overlapped_with_nested_list() { + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + #[serde(rename = "$value")] + item: Vec, + node: (), + } + + let data = from_str::( + r#" + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + 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 + ), + } + } } /// In those tests two lists are deserialized simultaneously. @@ -3033,73 +3723,177 @@ mod seq { ); } - /// A list with fixed-name elements are mixed with a list with variable-name - /// elements in an XML, and the first element is a fixed-name one - #[test] - fn overlapped_fixed_before() { - let data = from_str::( - r#" - - - - - - - - "#, - ); + mod overlapped { + use super::*; + use pretty_assertions::assert_eq; - #[cfg(feature = "overlapped-lists")] - assert_eq!( - data.unwrap(), - Pair { - item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], - element: vec![(), ()], + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + #[serde(rename = "$value")] + item: Vec, + element: Vec<()>, + } + + /// A list with fixed-name elements are mixed with a list with variable-name + /// elements in an XML, and the first element is a fixed-name one + #[test] + fn fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - ); + } - #[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 fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), + } } - } - /// 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 = from_str::( - r#" - - - - - - - - "#, - ); + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + element: vec![(); 2], + } + ); - #[cfg(feature = "overlapped-lists")] - assert_eq!( - 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 + ), } - ); + } - #[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 - ), + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + element: vec![(); 2], + } + ); + + #[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 + ), + } } } } @@ -3169,73 +3963,177 @@ mod seq { ); } - /// A list with fixed-name elements are mixed with a list with variable-name - /// elements in an XML, and the first element is a fixed-name one - #[test] - fn overlapped_fixed_before() { - let data = from_str::( - r#" - - - - - - - - "#, - ); + mod overlapped { + use super::*; + use pretty_assertions::assert_eq; - #[cfg(feature = "overlapped-lists")] - assert_eq!( - data.unwrap(), - Pair { - element: vec![(), ()], - item: vec![Choice::One, Choice::Two, Choice::Other("three".into())], + #[derive(Debug, PartialEq, Deserialize)] + struct Root { + element: Vec<()>, + #[serde(rename = "$value")] + item: Vec, + } + + /// A list with fixed-name elements are mixed with a list with variable-name + /// elements in an XML, and the first element is a fixed-name one + #[test] + fn fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), } - ); + } - #[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 fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + 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 + ), + } } - } - /// 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 = from_str::( - r#" - - - - - - - - "#, - ); + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_before() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + element: vec![(); 2], + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); - #[cfg(feature = "overlapped-lists")] - assert_eq!( - 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 + ), } - ); + } - #[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 - ), + /// Test for https://github.com/tafia/quick-xml/issues/435 + #[test] + fn with_nested_list_fixed_after() { + let data = from_str::( + r#" + + + + + + + + "#, + ); + + #[cfg(feature = "overlapped-lists")] + assert_eq!( + data.unwrap(), + Root { + element: vec![(); 2], + item: vec![ + Choice4::One { inner: [()] }, + Choice4::Two { inner: [()] }, + Choice4::Other, + ], + } + ); + + #[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 + ), + } } } }