aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Galenson <jgalenson@google.com>2021-09-30 22:20:54 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-09-30 22:20:54 +0000
commita18c0b482b4e41e3105e0f15207c289b515e3a5a (patch)
tree35331449d1933f6ca458c5d8d655db2d45bd5468
parent1c1285a57911df8964b787545c167b49884c87eb (diff)
parentbec962deb21f680a28408cca131d599a48c63c55 (diff)
downloadserde-xml-rs-a18c0b482b4e41e3105e0f15207c289b515e3a5a.tar.gz
Upgrade rust/crates/serde-xml-rs to 0.5.1 am: 14f3d85530 am: 0021904d8c am: daaac909a4 am: bec962deb2
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/serde-xml-rs/+/1833323 Change-Id: Iaae6aedb28bbfb794bfe1a204527040624d10592
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--.github/workflows/lint.yml55
-rw-r--r--.github/workflows/rust.yml22
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml16
-rw-r--r--Android.bp41
-rw-r--r--Cargo.toml42
-rw-r--r--Cargo.toml.orig19
-rw-r--r--METADATA18
-rw-r--r--README.md63
-rw-r--r--cargo2android.json4
-rw-r--r--rustfmt.toml1
-rw-r--r--src/de/buffer.rs169
-rw-r--r--src/de/map.rs65
-rw-r--r--src/de/mod.rs135
-rw-r--r--src/de/seq.rs111
-rw-r--r--src/de/var.rs45
-rw-r--r--src/error.rs3
-rw-r--r--src/lib.rs189
-rw-r--r--src/ser/mod.rs3
-rw-r--r--src/ser/var.rs8
-rw-r--r--tests/failures.rs19
-rw-r--r--tests/migrated.rs94
-rw-r--r--tests/readme.rs6
-rw-r--r--tests/round_trip.rs11
-rw-r--r--tests/test.rs246
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
diff --git a/Android.bp b/Android.bp
index 8816c03..770a90a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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"],
-}
diff --git a/Cargo.toml b/Cargo.toml
index fa4d9b9..08bafe4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/METADATA b/METADATA
index a1b4d08..2a22775 100644
--- a/METADATA
+++ b/METADATA
@@ -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
+ }
+}
diff --git a/README.md b/README.md
index f2e530c..ba23d1e 100644
--- a/README.md
+++ b/README.md
@@ -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 {
diff --git a/src/lib.rs b/src/lib.rs
index 1f93ed8..e1ef1df 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
+}