diff options
author | Joel Galenson <jgalenson@google.com> | 2021-09-30 21:43:29 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-09-30 21:43:29 +0000 |
commit | 0021904d8c210bd07daab86f9420a22bfcc0801a (patch) | |
tree | 35331449d1933f6ca458c5d8d655db2d45bd5468 | |
parent | 377bc4d28d4e61745e35f072a90fea519f0ac512 (diff) | |
parent | 14f3d85530ce5afbe8b5e7471c4a6bc4396b2ff5 (diff) | |
download | serde-xml-rs-0021904d8c210bd07daab86f9420a22bfcc0801a.tar.gz |
Upgrade rust/crates/serde-xml-rs to 0.5.1 am: 14f3d85530
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/serde-xml-rs/+/1833323
Change-Id: I90a778a6d846d6cdc8bcf0c29937aabc4363770b
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | .github/workflows/lint.yml | 55 | ||||
-rw-r--r-- | .github/workflows/rust.yml | 22 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 16 | ||||
-rw-r--r-- | Android.bp | 41 | ||||
-rw-r--r-- | Cargo.toml | 42 | ||||
-rw-r--r-- | Cargo.toml.orig | 19 | ||||
-rw-r--r-- | METADATA | 18 | ||||
-rw-r--r-- | README.md | 63 | ||||
-rw-r--r-- | cargo2android.json | 4 | ||||
-rw-r--r-- | rustfmt.toml | 1 | ||||
-rw-r--r-- | src/de/buffer.rs | 169 | ||||
-rw-r--r-- | src/de/map.rs | 65 | ||||
-rw-r--r-- | src/de/mod.rs | 135 | ||||
-rw-r--r-- | src/de/seq.rs | 111 | ||||
-rw-r--r-- | src/de/var.rs | 45 | ||||
-rw-r--r-- | src/error.rs | 3 | ||||
-rw-r--r-- | src/lib.rs | 189 | ||||
-rw-r--r-- | src/ser/mod.rs | 3 | ||||
-rw-r--r-- | src/ser/var.rs | 8 | ||||
-rw-r--r-- | tests/failures.rs | 19 | ||||
-rw-r--r-- | tests/migrated.rs | 94 | ||||
-rw-r--r-- | tests/readme.rs | 6 | ||||
-rw-r--r-- | tests/round_trip.rs | 11 | ||||
-rw-r--r-- | tests/test.rs | 246 |
26 files changed, 1054 insertions, 338 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..61ebbbd --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "2a13dbd3f6ae77e67d7d329e7fce0b66f2d16d05" + } +} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..6f26d5c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,55 @@ +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + +name: Lint + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + fmt: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features + name: Clippy Output diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..3c13d1b --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b866092 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: rust +rust: + - stable + - beta + - nightly +cache: cargo +before_cache: + - cargo clean -p serde-xml-rs + - rm -rf target/ +env: + global: + - RUST_BACKTRACE=1 +matrix: + fast_finish: true + allow_failures: + - rust: nightly @@ -1,8 +1,6 @@ // This file is generated by cargo2android.py --config cargo2android.json. // Do not modify this file as changes will be overridden on upgrade. - - package { default_applicable_licenses: ["external_rust_crates_serde-xml-rs_license"], } @@ -25,8 +23,10 @@ rust_library { // has rustc warnings host_supported: true, crate_name: "serde_xml_rs", + cargo_env_compat: true, + cargo_pkg_version: "0.5.1", srcs: ["src/lib.rs"], - edition: "2015", + edition: "2018", rustlibs: [ "liblog_rust", "libserde", @@ -44,9 +44,11 @@ rust_defaults { crate_name: "serde_xml_rs", // has rustc warnings srcs: ["src/lib.rs"], + cargo_env_compat: true, + cargo_pkg_version: "0.5.1", test_suites: ["general-tests"], auto_gen_config: true, - edition: "2015", + edition: "2018", rustlibs: [ "liblog_rust", "libserde", @@ -68,34 +70,3 @@ rust_test { name: "serde-xml-rs_device_test_src_lib", defaults: ["serde-xml-rs_test_defaults"], } - -rust_defaults { - name: "serde-xml-rs_test_defaults_serde_xml_rs", - crate_name: "serde_xml_rs", - // has rustc warnings - srcs: ["tests/round_trip.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - edition: "2015", - rustlibs: [ - "liblog_rust", - "libserde", - "libserde_xml_rs", - "libthiserror", - "libxml_rust", - ], - proc_macros: ["libserde_derive"], -} - -rust_test_host { - name: "serde-xml-rs_host_test_tests_round_trip", - defaults: ["serde-xml-rs_test_defaults_serde_xml_rs"], - test_options: { - unit_test: true, - }, -} - -rust_test { - name: "serde-xml-rs_device_test_tests_round_trip", - defaults: ["serde-xml-rs_test_defaults_serde_xml_rs"], -} @@ -1,18 +1,38 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + [package] +edition = "2018" +name = "serde-xml-rs" +version = "0.5.1" authors = ["Ingvar Stepanyan <me@rreverser.com>"] description = "xml-rs based deserializer for Serde (compatible with 0.9+)" license = "MIT" -name = "serde-xml-rs" repository = "https://github.com/RReverser/serde-xml-rs" -version = "0.4.1" +[dependencies.log] +version = "0.4" + +[dependencies.serde] +version = "1.0" + +[dependencies.thiserror] +version = "1.0" + +[dependencies.xml-rs] +version = "0.8" +[dev-dependencies.docmatic] +version = "0.1" -[dependencies] -log = "0.4" -serde = "1.0" -xml-rs = "0.8.0" -thiserror = "1.0" +[dev-dependencies.serde_derive] +version = "1.0" -[dev-dependencies] -serde_derive = "1.0" -simple_logger = "1.0.1" -docmatic = "0.1.2" +[dev-dependencies.simple_logger] +version = "1.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..09ea859 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,19 @@ +[package] +authors = ["Ingvar Stepanyan <me@rreverser.com>"] +description = "xml-rs based deserializer for Serde (compatible with 0.9+)" +license = "MIT" +name = "serde-xml-rs" +repository = "https://github.com/RReverser/serde-xml-rs" +version = "0.5.1" +edition = "2018" + +[dependencies] +log = "0.4" +serde = "1.0" +xml-rs = "0.8" +thiserror = "1.0" + +[dev-dependencies] +serde_derive = "1.0" +simple_logger = "1.0" +docmatic = "0.1" @@ -1,17 +1,19 @@ name: "serde-xml-rs" -description: - "xml-rs based deserializer for Serde" - +description: "xml-rs based deserializer for Serde (compatible with 0.9+)" third_party { url { type: HOMEPAGE value: "https://crates.io/crates/serde_xml_rs" } url { - type: GIT - value: "https://github.com/RReverser/serde-xml-rs" + type: ARCHIVE + value: "https://static.crates.io/crates/serde-xml-rs/serde-xml-rs-0.5.1.crate" } - version: "0.4.1" - last_upgrade_date { year: 2021 month: 6 day: 21 } + version: "0.5.1" license_type: NOTICE -}
\ No newline at end of file + last_upgrade_date { + year: 2021 + month: 9 + day: 28 + } +} @@ -2,61 +2,32 @@ [![Build Status](https://travis-ci.org/RReverser/serde-xml-rs.svg?branch=master)](https://travis-ci.org/RReverser/serde-xml-rs) -xml-rs based deserializer for Serde (compatible with 0.9+) +`xml-rs` based deserializer for Serde (compatible with 1.0) -## Usage - -Use `serde_xml_rs::from_reader(...)` on any type that implements [`std::io::Read`](https://doc.rust-lang.org/std/io/trait.Read.html) as following: +## Example usage ```rust -#[macro_use] -extern crate serde_derive; -extern crate serde; -extern crate serde_xml_rs; - -use serde_xml_rs::from_reader; +use serde; +use serde_derive::{Deserialize, Serialize}; +use serde_xml_rs::{from_str, to_string}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] struct Item { - pub name: String, - pub source: String -} - -#[derive(Debug, Deserialize)] -struct Project { - pub name: String, - - #[serde(rename = "Item", default)] - pub items: Vec<Item> + name: String, + source: String, } fn main() { - let s = r##" - <Project name="my_project"> - <Item name="hello" source="world.rs" /> - </Project> - "##; - let project: Project = from_reader(s.as_bytes()).unwrap(); - println!("{:#?}", project); -} -``` - -Alternatively, you can use `serde_xml_rs::Deserializer` to create a deserializer from a preconfigured [`xml_rs::EventReader`](https://netvl.github.io/xml-rs/xml/reader/struct.EventReader.html). - -## Parsing the "value" of a tag + let src = r#"<Item><name>Banana</name><source>Store</source></Item>"#; + let should_be = Item { + name: "Banana".to_string(), + source: "Store".to_string(), + }; -If you have an input of the form `<foo abc="xyz">bar</foo>`, and you want to get at the`bar`, you can use the special name `$value`: + let item: Item = from_str(src).unwrap(); + assert_eq!(item, should_be); -```rust,ignore -struct Foo { - pub abc: String, - #[serde(rename = "$value")] - pub body: String, + let reserialized_item = to_string(&item).unwrap(); + assert_eq!(src, reserialized_item); } ``` - -## Parsed representations - -Deserializer tries to be as intuitive as possible. - -However, there are some edge cases where you might get unexpected errors, so it's best to check out [`tests`](tests/test.rs) for expectations. diff --git a/cargo2android.json b/cargo2android.json index 7dc0a63..31f8835 100644 --- a/cargo2android.json +++ b/cargo2android.json @@ -11,9 +11,9 @@ "test-blocklist": [ "tests/failures.rs", "tests/migrated.rs", - "tests/readme.rs", + "tests/round_trip.rs", "tests/test.rs" ], "tests": true, "run": true -}
\ No newline at end of file +} diff --git a/rustfmt.toml b/rustfmt.toml index 8c795ae..e69de29 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +0,0 @@ -match_block_trailing_comma = true diff --git a/src/de/buffer.rs b/src/de/buffer.rs new file mode 100644 index 0000000..2760acb --- /dev/null +++ b/src/de/buffer.rs @@ -0,0 +1,169 @@ +use crate::debug_expect; +use crate::error::Result; +use std::{collections::VecDeque, io::Read}; +use xml::reader::{EventReader, XmlEvent}; + +/// Retrieve XML events from an underlying reader. +pub trait BufferedXmlReader<R: Read> { + /// Get and "consume" the next event. + fn next(&mut self) -> Result<XmlEvent>; + + /// Get the next event without consuming. + fn peek(&mut self) -> Result<&XmlEvent>; + + /// Spawn a child buffer whose cursor starts at the same position as this buffer. + fn child_buffer<'a>(&'a mut self) -> ChildXmlBuffer<'a, R>; +} + +pub struct RootXmlBuffer<R: Read> { + reader: EventReader<R>, + buffer: VecDeque<CachedXmlEvent>, +} + +impl<R: Read> RootXmlBuffer<R> { + pub fn new(reader: EventReader<R>) -> Self { + RootXmlBuffer { + reader, + buffer: VecDeque::new(), + } + } +} + +impl<R: Read> BufferedXmlReader<R> for RootXmlBuffer<R> { + /// Consumed XML events in the root buffer are moved to the caller + fn next(&mut self) -> Result<XmlEvent> { + loop { + match self.buffer.pop_front() { + Some(CachedXmlEvent::Unused(ev)) => break Ok(ev), + Some(CachedXmlEvent::Used) => continue, + None => break next_significant_event(&mut self.reader), + } + } + } + + fn peek(&mut self) -> Result<&XmlEvent> { + get_from_buffer_or_reader(&mut self.buffer, &mut self.reader, &mut 0) + } + + fn child_buffer<'root>(&'root mut self) -> ChildXmlBuffer<'root, R> { + let RootXmlBuffer { reader, buffer } = self; + ChildXmlBuffer { + reader, + buffer, + cursor: 0, + } + } +} + +pub struct ChildXmlBuffer<'parent, R: Read> { + reader: &'parent mut EventReader<R>, + buffer: &'parent mut VecDeque<CachedXmlEvent>, + cursor: usize, +} + +impl<'parent, R: Read> ChildXmlBuffer<'parent, R> { + /// Advance the child buffer without marking an event as "used" + pub fn skip(&mut self) { + debug_assert!( + self.cursor < self.buffer.len(), + ".skip() only should be called after .peek()" + ); + + self.cursor += 1; + } +} + +impl<'parent, R: Read> BufferedXmlReader<R> for ChildXmlBuffer<'parent, R> { + /// Consumed XML events in a child buffer are marked as "used" + fn next(&mut self) -> Result<XmlEvent> { + loop { + match self.buffer.get_mut(self.cursor) { + Some(entry @ CachedXmlEvent::Unused(_)) => { + let taken = if self.cursor == 0 { + self.buffer.pop_front().unwrap() + } else { + std::mem::replace(entry, CachedXmlEvent::Used) + }; + + return debug_expect!(taken, CachedXmlEvent::Unused(ev) => Ok(ev)); + } + Some(CachedXmlEvent::Used) => { + debug_assert!( + self.cursor != 0, + "Event buffer should not start with 'used' slot (should have been popped)" + ); + self.cursor += 1; + continue; + } + None => { + debug_assert_eq!(self.buffer.len(), self.cursor); + + // Skip creation of buffer entry when consuming event straight away + return next_significant_event(&mut self.reader); + } + } + } + } + + fn peek(&mut self) -> Result<&XmlEvent> { + get_from_buffer_or_reader(self.buffer, self.reader, &mut self.cursor) + } + + fn child_buffer<'a>(&'a mut self) -> ChildXmlBuffer<'a, R> { + let ChildXmlBuffer { + reader, + buffer, + cursor, + } = self; + + ChildXmlBuffer { + reader, + buffer, + cursor: *cursor, + } + } +} + +#[derive(Debug)] +enum CachedXmlEvent { + Unused(XmlEvent), + Used, +} + +fn get_from_buffer_or_reader<'buf>( + buffer: &'buf mut VecDeque<CachedXmlEvent>, + reader: &mut EventReader<impl Read>, + index: &mut usize, +) -> Result<&'buf XmlEvent> { + // We should only be attempting to get an event already in the buffer, or the next event to place in the buffer + debug_assert!(*index <= buffer.len()); + + loop { + match buffer.get_mut(*index) { + Some(CachedXmlEvent::Unused(_)) => break, + Some(CachedXmlEvent::Used) => { + *index += 1; + } + None => { + let next = next_significant_event(reader)?; + buffer.push_back(CachedXmlEvent::Unused(next)); + } + } + } + + // Returning of borrowed data must be done after of loop/match due to current limitation of borrow checker + debug_expect!(buffer.get_mut(*index), Some(CachedXmlEvent::Unused(event)) => Ok(event)) +} + +/// Reads the next XML event from the underlying reader, skipping events we're not interested in. +fn next_significant_event(reader: &mut EventReader<impl Read>) -> Result<XmlEvent> { + loop { + match reader.next()? { + XmlEvent::StartDocument { .. } + | XmlEvent::ProcessingInstruction { .. } + | XmlEvent::Whitespace { .. } + | XmlEvent::Comment(_) => { /* skip */ } + other => return Ok(other), + } + } +} diff --git a/src/de/map.rs b/src/de/map.rs index ed1d0bb..0557d5e 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -1,57 +1,73 @@ use std::io::Read; use serde::de::{self, IntoDeserializer, Unexpected}; +use serde::forward_to_deserialize_any; use xml::attribute::OwnedAttribute; use xml::reader::XmlEvent; -use Deserializer; -use error::{Error, Result}; +use crate::error::{Error, Result}; +use crate::Deserializer; -pub struct MapAccess<'a, R: 'a + Read> { +use super::buffer::BufferedXmlReader; + +pub struct MapAccess<'a, R: Read, B: BufferedXmlReader<R>> { attrs: ::std::vec::IntoIter<OwnedAttribute>, - next_value: Option<String>, - de: &'a mut Deserializer<R>, + /// Cache of attribute value, populated when visitor calls `next_key_seed`; should be read & emptied straight after + /// by visitor call to `next_value_seed` + next_attr_value: Option<String>, + de: &'a mut Deserializer<R, B>, + /// Whether this `MapAccess` is to deserialize all inner contents of an outer element. inner_value: bool, } -impl<'a, R: 'a + Read> MapAccess<'a, R> { - pub fn new(de: &'a mut Deserializer<R>, attrs: Vec<OwnedAttribute>, inner_value: bool) -> Self { +impl<'a, R: 'a + Read, B: BufferedXmlReader<R>> MapAccess<'a, R, B> { + pub fn new( + de: &'a mut Deserializer<R, B>, + attrs: Vec<OwnedAttribute>, + inner_value: bool, + ) -> Self { MapAccess { attrs: attrs.into_iter(), - next_value: None, + next_attr_value: None, de: de, inner_value: inner_value, } } } -impl<'de, 'a, R: 'a + Read> de::MapAccess<'de> for MapAccess<'a, R> { +impl<'de, 'a, R: 'a + Read, B: BufferedXmlReader<R>> de::MapAccess<'de> for MapAccess<'a, R, B> { type Error = Error; fn next_key_seed<K: de::DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>> { - debug_assert_eq!(self.next_value, None); + debug_assert_eq!(self.next_attr_value, None); match self.attrs.next() { + // Read all attributes first Some(OwnedAttribute { name, value }) => { - self.next_value = Some(value); + self.next_attr_value = Some(value); seed.deserialize(name.local_name.into_deserializer()) .map(Some) - }, + } None => match *self.de.peek()? { - XmlEvent::StartElement { ref name, .. } => seed.deserialize( - if !self.inner_value { - name.local_name.as_str() - } else { - "$value" - }.into_deserializer(), - ).map(Some), + XmlEvent::StartElement { ref name, .. } => seed + .deserialize( + if !self.inner_value { + name.local_name.as_str() + } else { + "$value" + } + .into_deserializer(), + ) + .map(Some), XmlEvent::Characters(_) => seed.deserialize("$value".into_deserializer()).map(Some), + // Any other event: assume end of map values (actual check for `EndElement` done by the originating + // `Deserializer`) _ => Ok(None), }, } } fn next_value_seed<V: de::DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value> { - match self.next_value.take() { + match self.next_attr_value.take() { Some(value) => seed.deserialize(AttrValueDeserializer(value)), None => { if !self.inner_value { @@ -61,7 +77,7 @@ impl<'de, 'a, R: 'a + Read> de::MapAccess<'de> for MapAccess<'a, R> { } let result = seed.deserialize(&mut *self.de)?; Ok(result) - }, + } } } @@ -77,7 +93,7 @@ macro_rules! deserialize_type_attr { fn $deserialize<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { visitor.$visit(self.0.parse()?) } - } + }; } impl<'de> de::Deserializer<'de> for AttrValueDeserializer { @@ -115,7 +131,10 @@ impl<'de> de::Deserializer<'de> for AttrValueDeserializer { match self.0.as_str() { "true" | "1" => visitor.visit_bool(true), "false" | "0" => visitor.visit_bool(false), - _ => Err(de::Error::invalid_value(Unexpected::Str(&self.0), &"a boolean")), + _ => Err(de::Error::invalid_value( + Unexpected::Str(&self.0), + &"a boolean", + )), } } diff --git a/src/de/mod.rs b/src/de/mod.rs index 94077c5..21240c5 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1,14 +1,19 @@ -use std::io::Read; +use std::{io::Read, marker::PhantomData}; +use log::debug; use serde::de::{self, Unexpected}; +use serde::forward_to_deserialize_any; use xml::name::OwnedName; use xml::reader::{EventReader, ParserConfig, XmlEvent}; +use self::buffer::{BufferedXmlReader, ChildXmlBuffer, RootXmlBuffer}; use self::map::MapAccess; use self::seq::SeqAccess; use self::var::EnumAccess; -use error::{Error, Result}; +use crate::error::{Error, Result}; +use crate::{debug_expect, expect}; +mod buffer; mod map; mod seq; mod var; @@ -59,20 +64,31 @@ pub fn from_reader<'de, R: Read, T: de::Deserialize<'de>>(reader: R) -> Result<T T::deserialize(&mut Deserializer::new_from_reader(reader)) } -pub struct Deserializer<R: Read> { +type RootDeserializer<R> = Deserializer<R, RootXmlBuffer<R>>; +type ChildDeserializer<'parent, R> = Deserializer<R, ChildXmlBuffer<'parent, R>>; + +pub struct Deserializer< + R: Read, // Kept as type param to avoid type signature breaking-change + B: BufferedXmlReader<R> = RootXmlBuffer<R>, +> { + /// XML document nested element depth depth: usize, - reader: EventReader<R>, - peeked: Option<XmlEvent>, + buffered_reader: B, is_map_value: bool, + non_contiguous_seq_elements: bool, + marker: PhantomData<R>, } -impl<'de, R: Read> Deserializer<R> { +impl<'de, R: Read> RootDeserializer<R> { pub fn new(reader: EventReader<R>) -> Self { + let buffered_reader = RootXmlBuffer::new(reader); + Deserializer { + buffered_reader, depth: 0, - reader: reader, - peeked: None, is_map_value: false, + non_contiguous_seq_elements: false, + marker: PhantomData, } } @@ -87,34 +103,75 @@ impl<'de, R: Read> Deserializer<R> { Self::new(EventReader::new_with_config(reader, config)) } - fn peek(&mut self) -> Result<&XmlEvent> { - if self.peeked.is_none() { - self.peeked = Some(self.inner_next()?); - } - debug_expect!(self.peeked.as_ref(), Some(peeked) => { - debug!("Peeked {:?}", peeked); - Ok(peeked) - }) + /// Configures whether the deserializer should search all sibling elements when building a + /// sequence. Not required if all XML elements for sequences are adjacent. Disabled by + /// default. Enabling this option may incur additional memory usage. + /// + /// ```rust + /// # #[macro_use] + /// # extern crate serde_derive; + /// # extern crate serde; + /// # extern crate serde_xml_rs; + /// # use serde_xml_rs::from_reader; + /// # use serde::Deserialize; + /// #[derive(Debug, Deserialize, PartialEq)] + /// struct Foo { + /// bar: Vec<usize>, + /// baz: String, + /// } + /// # fn main() { + /// let s = r##" + /// <foo> + /// <bar>1</bar> + /// <bar>2</bar> + /// <baz>Hello, world</baz> + /// <bar>3</bar> + /// <bar>4</bar> + /// </foo> + /// "##; + /// let mut de = serde_xml_rs::Deserializer::new_from_reader(s.as_bytes()) + /// .non_contiguous_seq_elements(true); + /// let foo = Foo::deserialize(&mut de).unwrap(); + /// assert_eq!(foo, Foo { bar: vec![1, 2, 3, 4], baz: "Hello, world".to_string()}); + /// # } + /// ``` + pub fn non_contiguous_seq_elements(mut self, set: bool) -> Self { + self.non_contiguous_seq_elements = set; + self } +} - fn inner_next(&mut self) -> Result<XmlEvent> { - loop { - match self.reader.next()? { - XmlEvent::StartDocument { .. } - | XmlEvent::ProcessingInstruction { .. } - | XmlEvent::Whitespace { .. } - | XmlEvent::Comment(_) => { /* skip */ } - other => return Ok(other), - } +impl<'de, R: Read, B: BufferedXmlReader<R>> Deserializer<R, B> { + fn child<'a>(&'a mut self) -> Deserializer<R, ChildXmlBuffer<'a, R>> { + let Deserializer { + buffered_reader, + depth, + is_map_value, + non_contiguous_seq_elements, + .. + } = self; + + Deserializer { + buffered_reader: buffered_reader.child_buffer(), + depth: *depth, + is_map_value: *is_map_value, + non_contiguous_seq_elements: *non_contiguous_seq_elements, + marker: PhantomData, } } + /// Gets the next XML event without advancing the cursor. + fn peek(&mut self) -> Result<&XmlEvent> { + let peeked = self.buffered_reader.peek()?; + + debug!("Peeked {:?}", peeked); + Ok(peeked) + } + + /// Gets the XML event at the cursor and advances the cursor. fn next(&mut self) -> Result<XmlEvent> { - let next = if let Some(peeked) = self.peeked.take() { - peeked - } else { - self.inner_next()? - }; + let next = self.buffered_reader.next()?; + match next { XmlEvent::StartElement { .. } => { self.depth += 1; @@ -136,6 +193,10 @@ impl<'de, R: Read> Deserializer<R> { ::std::mem::replace(&mut self.is_map_value, false) } + /// If `self.is_map_value`: Performs the read operations specified by `f` on the inner content of an XML element. + /// `f` is expected to consume the entire inner contents of the element. The cursor will be moved to the end of the + /// element. + /// If `!self.is_map_value`: `f` will be performed without additional checks/advances for an outer XML element. fn read_inner_value<V: de::Visitor<'de>, T, F: FnOnce(&mut Self) -> Result<T>>( &mut self, f: F, @@ -190,10 +251,12 @@ macro_rules! deserialize_type { let value = self.prepare_parse_type::<V>()?.parse()?; visitor.$visit(value) } - } + }; } -impl<'de, 'a, R: Read> de::Deserializer<'de> for &'a mut Deserializer<R> { +impl<'de, 'a, R: Read, B: BufferedXmlReader<R>> de::Deserializer<'de> + for &'a mut Deserializer<R, B> +{ type Error = Error; forward_to_deserialize_any! { @@ -299,7 +362,9 @@ impl<'de, 'a, R: Read> de::Deserializer<'de> for &'a mut Deserializer<R> { } fn deserialize_tuple<V: de::Visitor<'de>>(self, len: usize, visitor: V) -> Result<V::Value> { - visitor.visit_seq(SeqAccess::new(self, Some(len))) + let child_deserializer = self.child(); + + visitor.visit_seq(SeqAccess::new(child_deserializer, Some(len))) } fn deserialize_enum<V: de::Visitor<'de>>( @@ -326,7 +391,9 @@ impl<'de, 'a, R: Read> de::Deserializer<'de> for &'a mut Deserializer<R> { } fn deserialize_seq<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { - visitor.visit_seq(SeqAccess::new(self, None)) + let child_deserializer = self.child(); + + visitor.visit_seq(SeqAccess::new(child_deserializer, None)) } fn deserialize_map<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { diff --git a/src/de/seq.rs b/src/de/seq.rs index 7e88d0c..56babe1 100644 --- a/src/de/seq.rs +++ b/src/de/seq.rs @@ -3,28 +3,42 @@ use std::io::Read; use serde::de; use xml::reader::XmlEvent; -use de::Deserializer; -use error::{Error, Result}; +use crate::de::ChildDeserializer; +use crate::debug_expect; +use crate::error::{Error, Result}; -pub struct SeqAccess<'a, R: 'a + Read> { - de: &'a mut Deserializer<R>, +pub struct SeqAccess<'a, R: Read> { + de: ChildDeserializer<'a, R>, max_size: Option<usize>, - expected_name: Option<String>, + seq_type: SeqType, +} + +pub enum SeqType { + /// Sequence is of elements with the same name. + ByElementName { + expected_name: String, + search_non_contiguous: bool, + }, + /// Sequence is of all elements/text at current depth. + AllMembers, } impl<'a, R: 'a + Read> SeqAccess<'a, R> { - pub fn new(de: &'a mut Deserializer<R>, max_size: Option<usize>) -> Self { - let expected_name = if de.unset_map_value() { + pub fn new(mut de: ChildDeserializer<'a, R>, max_size: Option<usize>) -> Self { + let seq_type = if de.unset_map_value() { debug_expect!(de.peek(), Ok(&XmlEvent::StartElement { ref name, .. }) => { - Some(name.local_name.clone()) + SeqType::ByElementName { + expected_name: name.local_name.clone(), + search_non_contiguous: de.non_contiguous_seq_elements + } }) } else { - None + SeqType::AllMembers }; SeqAccess { - de: de, - max_size: max_size, - expected_name: expected_name, + de, + max_size, + seq_type, } } } @@ -39,28 +53,65 @@ impl<'de, 'a, R: 'a + Read> de::SeqAccess<'de> for SeqAccess<'a, R> { match self.max_size.as_mut() { Some(&mut 0) => { return Ok(None); - }, + } Some(max_size) => { *max_size -= 1; - }, - None => {}, + } + None => {} } - let more = match (self.de.peek()?, self.expected_name.as_ref()) { - (&XmlEvent::StartElement { ref name, .. }, Some(expected_name)) => { - &name.local_name == expected_name - }, - (&XmlEvent::EndElement { .. }, None) | - (_, Some(_)) | - (&XmlEvent::EndDocument { .. }, _) => false, - (_, None) => true, - }; - if more { - if self.expected_name.is_some() { - self.de.set_map_value(); + + match &self.seq_type { + SeqType::ByElementName { + expected_name, + search_non_contiguous, + } => { + let mut local_depth = 0; + + loop { + let next_element = self.de.peek()?; + + match next_element { + XmlEvent::StartElement { name, .. } + if &name.local_name == expected_name && local_depth == 0 => + { + self.de.set_map_value(); + return seed.deserialize(&mut self.de).map(Some); + } + XmlEvent::StartElement { .. } => { + if *search_non_contiguous { + self.de.buffered_reader.skip(); + local_depth += 1; + } else { + return Ok(None); + } + } + XmlEvent::EndElement { .. } => { + if local_depth == 0 { + return Ok(None); + } else { + local_depth -= 1; + self.de.buffered_reader.skip(); + } + } + XmlEvent::EndDocument => { + return Ok(None); + } + _ => { + self.de.buffered_reader.skip(); + } + } + } + } + SeqType::AllMembers => { + let next_element = self.de.peek()?; + + match next_element { + XmlEvent::EndElement { .. } | XmlEvent::EndDocument => return Ok(None), + _ => { + return seed.deserialize(&mut self.de).map(Some); + } + } } - seed.deserialize(&mut *self.de).map(Some) - } else { - Ok(None) } } diff --git a/src/de/var.rs b/src/de/var.rs index dbb400f..6c05153 100644 --- a/src/de/var.rs +++ b/src/de/var.rs @@ -4,27 +4,30 @@ use serde::de::{self, Deserializer as SerdeDeserializer, IntoDeserializer}; use xml::name::OwnedName; use xml::reader::XmlEvent; -use de::Deserializer; -use error::{Error, Result}; +use crate::de::Deserializer; +use crate::error::{Error, Result}; +use crate::expect; -pub struct EnumAccess<'a, R: 'a + Read> { - de: &'a mut Deserializer<R>, +use super::buffer::BufferedXmlReader; + +pub struct EnumAccess<'a, R: Read, B: BufferedXmlReader<R>> { + de: &'a mut Deserializer<R, B>, } -impl<'a, R: 'a + Read> EnumAccess<'a, R> { - pub fn new(de: &'a mut Deserializer<R>) -> Self { +impl<'a, R: 'a + Read, B: BufferedXmlReader<R>> EnumAccess<'a, R, B> { + pub fn new(de: &'a mut Deserializer<R, B>) -> Self { EnumAccess { de: de } } } -impl<'de, 'a, R: 'a + Read> de::EnumAccess<'de> for EnumAccess<'a, R> { +impl<'de, 'a, R: 'a + Read, B: BufferedXmlReader<R>> de::EnumAccess<'de> for EnumAccess<'a, R, B> { type Error = Error; - type Variant = VariantAccess<'a, R>; + type Variant = VariantAccess<'a, R, B>; fn variant_seed<V: de::DeserializeSeed<'de>>( self, seed: V, - ) -> Result<(V::Value, VariantAccess<'a, R>)> { + ) -> Result<(V::Value, VariantAccess<'a, R, B>)> { let name = expect!( self.de.peek()?, @@ -38,17 +41,19 @@ impl<'de, 'a, R: 'a + Read> de::EnumAccess<'de> for EnumAccess<'a, R> { } } -pub struct VariantAccess<'a, R: 'a + Read> { - de: &'a mut Deserializer<R>, +pub struct VariantAccess<'a, R: Read, B: BufferedXmlReader<R>> { + de: &'a mut Deserializer<R, B>, } -impl<'a, R: 'a + Read> VariantAccess<'a, R> { - pub fn new(de: &'a mut Deserializer<R>) -> Self { +impl<'a, R: 'a + Read, B: BufferedXmlReader<R>> VariantAccess<'a, R, B> { + pub fn new(de: &'a mut Deserializer<R, B>) -> Self { VariantAccess { de: de } } } -impl<'de, 'a, R: 'a + Read> de::VariantAccess<'de> for VariantAccess<'a, R> { +impl<'de, 'a, R: 'a + Read, B: BufferedXmlReader<R>> de::VariantAccess<'de> + for VariantAccess<'a, R, B> +{ type Error = Error; fn unit_variant(self) -> Result<()> { @@ -56,11 +61,13 @@ impl<'de, 'a, R: 'a + Read> de::VariantAccess<'de> for VariantAccess<'a, R> { match self.de.next()? { XmlEvent::StartElement { name, attributes, .. - } => if attributes.is_empty() { - self.de.expect_end_element(name) - } else { - Err(de::Error::invalid_length(attributes.len(), &"0")) - }, + } => { + if attributes.is_empty() { + self.de.expect_end_element(name) + } else { + Err(de::Error::invalid_length(attributes.len(), &"0")) + } + } XmlEvent::Characters(_) => Ok(()), _ => unreachable!(), } diff --git a/src/error.rs b/src/error.rs index 8d6e4ad..5061b52 100644 --- a/src/error.rs +++ b/src/error.rs @@ -51,6 +51,7 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; +#[macro_export] macro_rules! expect { ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { match $actual { @@ -64,6 +65,7 @@ macro_rules! expect { } #[cfg(debug_assertions)] +#[macro_export] macro_rules! debug_expect { ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { match $actual { @@ -78,6 +80,7 @@ macro_rules! debug_expect { } #[cfg(not(debug_assertions))] +#[macro_export] macro_rules! debug_expect { ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { match $actual { @@ -1,14 +1,56 @@ +//! # Serde XML //! +//! XML is a flexible markup language that is still used for sharing data between applications or +//! for writing configuration files. //! -//! # Examples +//! Serde XML provides a way to convert between text and strongly-typed Rust data structures. //! -//! ```rust -//! extern crate serde; -//! extern crate serde_xml_rs; +//! ## Caveats +//! +//! The Serde framework was mainly designed with formats such as JSON or YAML in mind. +//! As opposed to XML, these formats have the advantage of a stricter syntax which makes it +//! possible to know what type a field is without relying on an accompanying schema, +//! and disallows repeating the same tag multiple times in the same object. +//! +//! For example, encoding the following document in YAML is not trivial. +//! +//! ```xml +//! <document> +//! <header>A header</header> +//! <section>First section</section> +//! <section>Second section</section> +//! <sidenote>A sidenote</sidenote> +//! <section>Third section</section> +//! <sidenote>Another sidenote</sidenote> +//! <section>Fourth section</section> +//! <footer>The footer</footer> +//! </document> +//! ``` //! -//! #[macro_use] -//! extern crate serde_derive; +//! One possibility is the following YAML document. //! +//! ```yaml +//! - header: A header +//! - section: First section +//! - section: Second section +//! - sidenote: A sidenote +//! - section: Third section +//! - sidenote: Another sidenote +//! - section: Fourth section +//! - footer: The footer +//! ``` +//! +//! Other notable differences: +//! - XML requires a named root node. +//! - XML has a namespace system. +//! - XML distinguishes between attributes, child tags and contents. +//! - In XML, the order of nodes is sometimes important. +//! +//! ## Basic example +//! +//! ```rust +//! use serde; +//! use serde_derive::{Deserialize, Serialize}; //! use serde_xml_rs::{from_str, to_string}; //! //! #[derive(Debug, Serialize, Deserialize, PartialEq)] @@ -31,25 +73,126 @@ //! assert_eq!(src, reserialized_item); //! } //! ``` +//! +//! ## Tag contents +//! +//! ```rust +//! # use serde; +//! # use serde_derive::{Deserialize, Serialize}; +//! # use serde_xml_rs::{from_str, to_string}; +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct Document { +//! content: Content +//! } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct Content { +//! #[serde(rename = "$value")] +//! value: String +//! } +//! +//! fn main() { +//! let src = r#"<document><content>Lorem ipsum</content></document>"#; +//! let document: Document = from_str(src).unwrap(); +//! assert_eq!(document.content.value, "Lorem ipsum"); +//! } +//! ``` +//! +//! ## Repeated tags +//! +//! ```rust +//! # use serde; +//! # use serde_derive::{Deserialize, Serialize}; +//! # use serde_xml_rs::{from_str, to_string}; +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct PlateAppearance { +//! #[serde(rename = "$value")] +//! events: Vec<Event> +//! } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! #[serde(rename_all = "kebab-case")] +//! enum Event { +//! Pitch(Pitch), +//! Runner(Runner), +//! } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct Pitch { +//! speed: u32, +//! r#type: PitchType, +//! outcome: PitchOutcome, +//! } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! enum PitchType { FourSeam, TwoSeam, Changeup, Cutter, Curve, Slider, Knuckle, Pitchout } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! enum PitchOutcome { Ball, Strike, Hit } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct Runner { +//! from: Base, to: Option<Base>, outcome: RunnerOutcome, +//! } +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! enum Base { First, Second, Third, Home } +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! enum RunnerOutcome { Steal, Caught, PickOff } +//! +//! fn main() { +//! let document = r#" +//! <plate-appearance> +//! <pitch speed="95" type="FourSeam" outcome="Ball" /> +//! <pitch speed="91" type="FourSeam" outcome="Strike" /> +//! <pitch speed="85" type="Changeup" outcome="Ball" /> +//! <runner from="First" to="Second" outcome="Steal" /> +//! <pitch speed="89" type="Slider" outcome="Strike" /> +//! <pitch speed="88" type="Curve" outcome="Hit" /> +//! </plate-appearance>"#; +//! let plate_appearance: PlateAppearance = from_str(document).unwrap(); +//! assert_eq!(plate_appearance.events[0], Event::Pitch(Pitch { speed: 95, r#type: PitchType::FourSeam, outcome: PitchOutcome::Ball })); +//! } +//! ``` +//! +//! ## Custom EventReader +//! +//! ```rust +//! use serde::Deserialize; +//! use serde_derive::{Deserialize, Serialize}; +//! use serde_xml_rs::{from_str, to_string, de::Deserializer}; +//! use xml::reader::{EventReader, ParserConfig}; +//! +//! #[derive(Debug, Serialize, Deserialize, PartialEq)] +//! struct Item { +//! name: String, +//! source: String, +//! } +//! +//! fn main() { +//! let src = r#"<Item><name> Banana </name><source>Store</source></Item>"#; +//! let should_be = Item { +//! name: " Banana ".to_string(), +//! source: "Store".to_string(), +//! }; +//! +//! let config = ParserConfig::new() +//! .trim_whitespace(false) +//! .whitespace_to_characters(true); +//! let event_reader = EventReader::new_with_config(src.as_bytes(), config); +//! let item = Item::deserialize(&mut Deserializer::new(event_reader)).unwrap(); +//! assert_eq!(item, should_be); +//! } +//! ``` +//! -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde; -extern crate xml; - -extern crate thiserror; - -#[cfg(test)] -#[macro_use] -extern crate serde_derive; - -#[macro_use] -mod error; pub mod de; +mod error; pub mod ser; -pub use de::{from_reader, from_str, Deserializer}; -pub use error::Error; -pub use ser::{to_string, to_writer, Serializer}; +pub use crate::de::{from_reader, from_str, Deserializer}; +pub use crate::error::Error; +pub use crate::ser::{to_string, to_writer, Serializer}; pub use xml::reader::{EventReader, ParserConfig}; diff --git a/src/ser/mod.rs b/src/ser/mod.rs index bf97b1f..a29ca6f 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -4,7 +4,7 @@ use std::io::Write; use serde::ser::{self, Impossible, Serialize}; use self::var::{Map, Struct}; -use error::{Error, Result}; +use crate::error::{Error, Result}; mod var; @@ -292,6 +292,7 @@ mod tests { use super::*; use serde::ser::{SerializeMap, SerializeStruct}; use serde::Serializer as SerSerializer; + use serde_derive::Serialize; #[test] fn test_serialize_bool() { diff --git a/src/ser/var.rs b/src/ser/var.rs index bb61472..e4ee0f0 100644 --- a/src/ser/var.rs +++ b/src/ser/var.rs @@ -2,13 +2,13 @@ use std::io::Write; use serde::ser::{self, Serialize}; -use ser::Serializer; -use error::{Error, Result}; +use crate::error::{Error, Result}; +use crate::ser::Serializer; /// An implementation of `SerializeMap` for serializing to XML. pub struct Map<'w, W> where - W: 'w + Write, + W: Write, { parent: &'w mut Serializer<W>, } @@ -64,7 +64,7 @@ where /// An implementation of `SerializeStruct` for serializing to XML. pub struct Struct<'w, W> where - W: 'w + Write, + W: Write, { parent: &'w mut Serializer<W>, name: &'w str, diff --git a/tests/failures.rs b/tests/failures.rs index ca51f6b..7a55811 100644 --- a/tests/failures.rs +++ b/tests/failures.rs @@ -1,12 +1,11 @@ -#[macro_use] -extern crate serde_derive; -extern crate serde_xml_rs; - -#[macro_use] -extern crate log; -extern crate simple_logger; - +use log::info; +use serde_derive::Deserialize; use serde_xml_rs::from_str; +use simple_logger::SimpleLogger; + +fn init_logger() { + let _ = SimpleLogger::new().init(); +} #[derive(Debug, Deserialize, PartialEq)] struct Item { @@ -16,7 +15,7 @@ struct Item { #[test] fn simple_struct_from_attributes_should_fail() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <item name="hello" source="world.rs /> @@ -34,7 +33,7 @@ fn simple_struct_from_attributes_should_fail() { #[test] fn multiple_roots_attributes_should_fail() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <item name="hello" source="world.rs" /> diff --git a/tests/migrated.rs b/tests/migrated.rs index 87d3e6a..e01d750 100644 --- a/tests/migrated.rs +++ b/tests/migrated.rs @@ -1,17 +1,14 @@ -#[macro_use] -extern crate serde_derive; - -extern crate log; -extern crate simple_logger; - -extern crate serde; -extern crate serde_xml_rs; - +use simple_logger::SimpleLogger; use std::fmt::Debug; use serde::{de, ser}; +use serde_derive::{Deserialize, Serialize}; use serde_xml_rs::{from_str, Error}; +fn init_logger() { + let _ = SimpleLogger::new().init(); +} + #[derive(PartialEq, Debug, Serialize, Deserialize)] enum Animal { Dog, @@ -83,7 +80,7 @@ where #[test] fn test_namespaces() { - let _ = simple_logger::init(); + init_logger(); #[derive(PartialEq, Serialize, Deserialize, Debug)] struct Envelope { subject: String, @@ -102,9 +99,8 @@ fn test_namespaces() { } #[test] -#[ignore] // FIXME fn test_doctype() { - let _ = simple_logger::init(); + init_logger(); #[derive(PartialEq, Serialize, Deserialize, Debug)] struct Envelope { subject: String, @@ -150,36 +146,6 @@ fn test_doctype() { } #[test] -fn test_doctype_fail() { - let _ = simple_logger::init(); - #[derive(PartialEq, Serialize, Deserialize, Debug)] - struct Envelope { - subject: String, - } - - test_parse_err::<Envelope>(&[ - r#" - <?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE Envelope [ - <!ELEMENT subject (#PCDATA)> - > - <Envelope> - <subject>Reference rates</subject> - </Envelope>"#, - r#" - <?xml version="1.0" encoding="UTF-8"?> - <Envelope> - <subject>Reference rates</subject> - - <!DOCTYPE Envelope [ - <!ELEMENT subject (#PCDATA)> - ]> - - </Envelope>"#, - ]) -} - -#[test] #[ignore] // FIXME fn test_forwarded_namespace() { #[derive(PartialEq, Serialize, Deserialize, Debug)] @@ -208,7 +174,7 @@ fn test_forwarded_namespace() { #[test] fn test_parse_string() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ( @@ -230,7 +196,7 @@ fn test_parse_string() { #[test] #[ignore] // FIXME fn test_parse_string_not_trim() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[("<bla> </bla>", " ".to_string())]); } @@ -239,7 +205,7 @@ fn test_parse_string_not_trim() { #[ignore] // FIXME fn test_parse_enum() { use self::Animal::*; - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<Animal xsi:type=\"Dog\"/>", Dog), @@ -304,7 +270,7 @@ fn test_parse_enum() { #[test] fn test_parse_i64() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<bla>0</bla>", 0), ("<bla>-2</bla>", -2), @@ -315,7 +281,7 @@ fn test_parse_i64() { #[test] fn test_parse_u64() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<bla>0</bla>", 0), ("<bla>1234</bla>", 1234), @@ -325,7 +291,7 @@ fn test_parse_u64() { #[test] fn test_parse_bool_element() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<bla>true</bla>", true), ("<bla>false</bla>", false), @@ -345,7 +311,7 @@ fn test_parse_bool_attribute() { foo: bool, } - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<bla foo=\"true\"/>", Dummy { foo: true }), ("<bla foo=\"false\"/>", Dummy { foo: false }), @@ -364,13 +330,13 @@ fn test_parse_bool_attribute() { #[test] fn test_parse_unit() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[("<bla/>", ())]); } #[test] fn test_parse_f64() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<bla>3.0</bla>", 3.0f64), ("<bla>3.1</bla>", 3.1), @@ -385,7 +351,7 @@ fn test_parse_f64() { #[test] fn test_parse_struct() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ( @@ -432,7 +398,7 @@ fn test_parse_struct() { #[test] fn test_option() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ("<a/>", Some("".to_string())), ("<a></a>", Some("".to_string())), @@ -444,13 +410,13 @@ fn test_option() { #[test] #[ignore] // FIXME fn test_option_not_trim() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[("<a> </a>", Some(" ".to_string()))]); } #[test] fn test_amoskvin() { - let _ = simple_logger::init(); + init_logger(); #[derive(Debug, Deserialize, PartialEq, Serialize)] struct Root { foo: Vec<Foo>, @@ -490,7 +456,7 @@ fn test_amoskvin() { #[test] #[ignore] // FIXME fn test_nicolai86() { - let _ = simple_logger::init(); + init_logger(); #[derive(Serialize, Deserialize, PartialEq, Debug)] struct TheSender { name: String, @@ -579,7 +545,7 @@ fn test_nicolai86() { #[test] fn test_hugo_duncan2() { - let _ = simple_logger::init(); + init_logger(); let s = r#" <?xml version="1.0" encoding="UTF-8"?> <DescribeVpcsResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/"> @@ -634,7 +600,7 @@ fn test_hugo_duncan2() { #[test] fn test_hugo_duncan() { - let _ = simple_logger::init(); + init_logger(); let s = " <?xml version=\"1.0\" encoding=\"UTF-8\"?> <DescribeInstancesResponse xmlns=\"http://ec2.amazonaws.com/doc/2014-10-01/\"> @@ -659,7 +625,7 @@ fn test_hugo_duncan() { #[test] fn test_parse_xml_value() { - let _ = simple_logger::init(); + init_logger(); #[derive(Eq, Debug, PartialEq, Deserialize, Serialize)] struct Test { #[serde(rename = "$value")] @@ -676,7 +642,7 @@ fn test_parse_xml_value() { #[test] #[ignore] // FIXME fn test_parse_complexstruct() { - let _ = simple_logger::init(); + init_logger(); test_parse_ok(&[ ( @@ -720,7 +686,7 @@ fn test_parse_complexstruct() { #[test] fn test_parse_attributes() { - let _ = simple_logger::init(); + init_logger(); #[derive(PartialEq, Debug, Serialize, Deserialize)] struct A { @@ -805,7 +771,7 @@ fn test_parse_attributes() { #[test] #[ignore] // FIXME fn test_parse_hierarchies() { - let _ = simple_logger::init(); + init_logger(); #[derive(PartialEq, Debug, Serialize, Deserialize)] struct A { a1: String, @@ -954,7 +920,7 @@ fn test_things_qc_found() { #[test] fn futile() { - let _ = simple_logger::init(); + init_logger(); #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] struct Object { id: u8, @@ -1003,7 +969,7 @@ fn futile() { #[test] fn futile2() { - let _ = simple_logger::init(); + init_logger(); #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] struct Null; diff --git a/tests/readme.rs b/tests/readme.rs deleted file mode 100644 index 881d572..0000000 --- a/tests/readme.rs +++ /dev/null @@ -1,6 +0,0 @@ -extern crate docmatic; - -#[test] -fn test_readme() { - docmatic::assert_file("README.md"); -} diff --git a/tests/round_trip.rs b/tests/round_trip.rs index 54b3b53..450a4b5 100644 --- a/tests/round_trip.rs +++ b/tests/round_trip.rs @@ -1,10 +1,6 @@ -#[macro_use] -extern crate serde_derive; -extern crate serde; -extern crate serde_xml_rs; - use serde::Deserialize; -use serde_xml_rs::{from_str, to_string, EventReader, ParserConfig}; +use serde_derive::{Deserialize, Serialize}; +use serde_xml_rs::{self, from_str, to_string, EventReader, ParserConfig}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Item { @@ -12,7 +8,6 @@ struct Item { source: String, } - #[derive(Debug, Serialize, Deserialize, PartialEq)] enum Node { Boolean(bool), @@ -26,7 +21,6 @@ struct Nodes { items: Vec<Node>, } - #[test] fn basic_struct() { let src = r#"<Item><name>Banana</name><source>Store</source></Item>"#; @@ -42,7 +36,6 @@ fn basic_struct() { assert_eq!(src, reserialized_item); } - #[test] #[ignore] fn round_trip_list_of_enums() { diff --git a/tests/test.rs b/tests/test.rs index 4b8ec86..52075b1 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,11 +1,11 @@ -#[macro_use] -extern crate serde_derive; -extern crate serde_xml_rs; +use serde::Deserialize; +use serde_derive::Deserialize; +use serde_xml_rs::{from_str, Deserializer}; +use simple_logger::SimpleLogger; -extern crate log; -extern crate simple_logger; - -use serde_xml_rs::from_str; +fn init_logger() { + let _ = SimpleLogger::new().init(); +} #[derive(Debug, Deserialize, PartialEq)] struct Item { @@ -15,7 +15,7 @@ struct Item { #[test] fn simple_struct_from_attributes() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <item name="hello" source="world.rs" /> @@ -34,7 +34,7 @@ fn simple_struct_from_attributes() { #[test] fn multiple_roots_attributes() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <item name="hello" source="world.rs" /> @@ -60,7 +60,7 @@ fn multiple_roots_attributes() { #[test] fn simple_struct_from_attribute_and_child() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <item name="hello"> @@ -89,7 +89,7 @@ struct Project { #[test] fn nested_collection() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <project name="my_project"> @@ -133,7 +133,7 @@ struct MyEnums { #[test] fn collection_of_enums() { - let _ = simple_logger::init(); + init_logger(); let s = r##" <enums> @@ -159,3 +159,225 @@ fn collection_of_enums() { } ); } + +#[test] +fn out_of_order_collection() { + #[derive(Debug, Deserialize, PartialEq)] + struct Collection { + a: Vec<A>, + b: Vec<B>, + c: C, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct A { + name: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct B { + name: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct C { + name: String, + } + + init_logger(); + + let in_xml = r#" + <collection> + <a name="a1" /> + <a name="a2" /> + <b name="b1" /> + <a name="a3" /> + <c name="c" /> + <b name="b2" /> + <a name="a4" /> + </collection> + "#; + + let should_be = Collection { + a: vec![ + A { name: "a1".into() }, + A { name: "a2".into() }, + A { name: "a3".into() }, + A { name: "a4".into() }, + ], + b: vec![B { name: "b1".into() }, B { name: "b2".into() }], + c: C { name: "c".into() }, + }; + + let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true); + let actual = Collection::deserialize(&mut de).unwrap(); + + assert_eq!(should_be, actual); +} + +#[test] +fn nested_out_of_order_collection() { + #[derive(Debug, Deserialize, PartialEq)] + struct OuterCollection { + a: A, + inner: Vec<InnerCollection>, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct InnerCollection { + b: Vec<B>, + c: Vec<C>, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct A { + name: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct B { + name: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct C { + name: String, + } + + init_logger(); + + let in_xml = r#" + <collection> + <inner> + <b name="b1" /> + <c name="c1" /> + <b name="b2" /> + <c name="c2" /> + </inner> + <a name="a" /> + <inner> + <c name="c3" /> + <b name="b3" /> + <c name="c4" /> + <b name="b4" /> + </inner> + </collection> + "#; + + let should_be = OuterCollection { + a: A { name: "a".into() }, + inner: vec![ + InnerCollection { + b: vec![B { name: "b1".into() }, B { name: "b2".into() }], + c: vec![C { name: "c1".into() }, C { name: "c2".into() }], + }, + InnerCollection { + b: vec![B { name: "b3".into() }, B { name: "b4".into() }], + c: vec![C { name: "c3".into() }, C { name: "c4".into() }], + }, + ], + }; + + let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true); + let actual = OuterCollection::deserialize(&mut de).unwrap(); + + assert_eq!(should_be, actual); +} + +#[test] +fn out_of_order_tuple() { + #[derive(Debug, Deserialize, PartialEq)] + struct Collection { + val: (A, B, C), + other: A, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct A { + name_a: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct B { + name_b: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct C { + name_c: String, + } + + init_logger(); + + let in_xml = r#" + <collection> + <val name_a="a1" /> + <val name_b="b" /> + <other name_a="a2" /> + <val name_c="c" /> + </collection> + "#; + + let should_be = Collection { + val: ( + A { + name_a: "a1".into(), + }, + B { name_b: "b".into() }, + C { name_c: "c".into() }, + ), + other: A { + name_a: "a2".into(), + }, + }; + + let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true); + let actual = Collection::deserialize(&mut de).unwrap(); + + assert_eq!(should_be, actual); +} + +/// Ensure that identically-named elements at different depths are not deserialized as if they were +/// at the same depth. +#[test] +fn nested_collection_repeated_elements() { + #[derive(Debug, Deserialize, PartialEq)] + struct OuterCollection { + a: Vec<A>, + inner: Inner, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct Inner { + a: A, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct A { + name: String, + } + + init_logger(); + + let in_xml = r#" + <collection> + <a name="a1" /> + <inner> + <a name="a2" /> + </inner> + <a name="a3" /> + </collection> + "#; + + let should_be = OuterCollection { + a: vec![A { name: "a1".into() }, A { name: "a3".into() }], + inner: Inner { + a: A { name: "a2".into() }, + }, + }; + + let mut de = Deserializer::new_from_reader(in_xml.as_bytes()).non_contiguous_seq_elements(true); + let actual = OuterCollection::deserialize(&mut de).unwrap(); + + assert_eq!(should_be, actual); +} |