diff options
author | Jooyung Han <jooyung@google.com> | 2021-07-15 19:01:25 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-07-15 19:01:25 +0000 |
commit | 054c65f3a29f08c2514b9277139430c0d6c39bb9 (patch) | |
tree | 33bde6c515a7bd7b50de72cedb31a280a14d37a1 | |
parent | 242244901480917fe3d564dad6e48d664a78ebc4 (diff) | |
parent | aa251e43eb2bb8f02e5106efd4fb5a9f8168bdd4 (diff) | |
download | serde-xml-rs-054c65f3a29f08c2514b9277139430c0d6c39bb9.tar.gz |
Import serde-xml-rs 0.4.1 am: 1e046a8595 am: ecb08a10cc am: fc2bfac46b am: aa251e43eb
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/serde-xml-rs/+/1767647
Change-Id: Id697721de05be6bf7d29ff4b1886574eade3ec9f
-rw-r--r-- | Android.bp | 34 | ||||
-rw-r--r-- | Cargo.toml | 18 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | METADATA | 17 | ||||
-rw-r--r-- | MODULE_LICENSE_MIT | 0 | ||||
-rw-r--r-- | OWNERS | 2 | ||||
-rw-r--r-- | README.md | 62 | ||||
-rw-r--r-- | cargo2android.json | 9 | ||||
-rw-r--r-- | rustfmt.toml | 1 | ||||
-rw-r--r-- | src/de/map.rs | 126 | ||||
-rw-r--r-- | src/de/mod.rs | 367 | ||||
-rw-r--r-- | src/de/seq.rs | 70 | ||||
-rw-r--r-- | src/de/var.rs | 84 | ||||
-rw-r--r-- | src/error.rs | 104 | ||||
-rw-r--r-- | src/lib.rs | 55 | ||||
-rw-r--r-- | src/ser/mod.rs | 419 | ||||
-rw-r--r-- | src/ser/var.rs | 103 | ||||
-rw-r--r-- | tests/failures.rs | 52 | ||||
-rw-r--r-- | tests/migrated.rs | 1086 | ||||
-rw-r--r-- | tests/readme.rs | 6 | ||||
-rw-r--r-- | tests/round_trip.rs | 110 | ||||
-rw-r--r-- | tests/test.rs | 161 |
22 files changed, 2907 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..ee9f639 --- /dev/null +++ b/Android.bp @@ -0,0 +1,34 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library { + name: "libserde_xml_rs", + host_supported: true, + crate_name: "serde_xml_rs", + srcs: ["src/lib.rs"], + edition: "2015", + rustlibs: [ + "liblog_rust", + "libserde", + "libthiserror", + "libxml", + ], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], +} + +// dependent_library ["feature_list"] +// cfg-if-1.0.0 +// log-0.4.14 "std" +// proc-macro2-1.0.27 "default,proc-macro" +// quote-1.0.9 "default,proc-macro" +// serde-1.0.126 "default,std" +// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote" +// thiserror-1.0.26 +// thiserror-impl-1.0.26 +// unicode-xid-0.2.2 "default" +// xml-rs-0.8.3 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fa4d9b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[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.4.1" + +[dependencies] +log = "0.4" +serde = "1.0" +xml-rs = "0.8.0" +thiserror = "1.0" + +[dev-dependencies] +serde_derive = "1.0" +simple_logger = "1.0.1" +docmatic = "0.1.2" @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Ingvar Stepanyan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..a1b4d08 --- /dev/null +++ b/METADATA @@ -0,0 +1,17 @@ +name: "serde-xml-rs" +description: + "xml-rs based deserializer for Serde" + +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/serde_xml_rs" + } + url { + type: GIT + value: "https://github.com/RReverser/serde-xml-rs" + } + version: "0.4.1" + last_upgrade_date { year: 2021 month: 6 day: 21 } + license_type: NOTICE +}
\ No newline at end of file diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1,2 @@ +include platform/prebuilts/rust:/OWNERS + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2e530c --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# serde-xml-rs + +[![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+) + +## 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: + +```rust +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_xml_rs; + +use serde_xml_rs::from_reader; + +#[derive(Debug, Deserialize)] +struct Item { + pub name: String, + pub source: String +} + +#[derive(Debug, Deserialize)] +struct Project { + pub name: String, + + #[serde(rename = "Item", default)] + pub items: Vec<Item> +} + +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 + +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`: + +```rust,ignore +struct Foo { + pub abc: String, + #[serde(rename = "$value")] + pub body: String, +} +``` + +## 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 new file mode 100644 index 0000000..42b7833 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,9 @@ +{ + "apex-available": [ + "//apex_available:platform", + "com.android.virt" + ], + "dependencies": true, + "device": true, + "run": true +}
\ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..8c795ae --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +match_block_trailing_comma = true diff --git a/src/de/map.rs b/src/de/map.rs new file mode 100644 index 0000000..ed1d0bb --- /dev/null +++ b/src/de/map.rs @@ -0,0 +1,126 @@ +use std::io::Read; + +use serde::de::{self, IntoDeserializer, Unexpected}; +use xml::attribute::OwnedAttribute; +use xml::reader::XmlEvent; + +use Deserializer; +use error::{Error, Result}; + +pub struct MapAccess<'a, R: 'a + Read> { + attrs: ::std::vec::IntoIter<OwnedAttribute>, + next_value: Option<String>, + de: &'a mut Deserializer<R>, + 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 { + MapAccess { + attrs: attrs.into_iter(), + next_value: None, + de: de, + inner_value: inner_value, + } + } +} + +impl<'de, 'a, R: 'a + Read> de::MapAccess<'de> for MapAccess<'a, R> { + 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); + match self.attrs.next() { + Some(OwnedAttribute { name, value }) => { + self.next_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::Characters(_) => seed.deserialize("$value".into_deserializer()).map(Some), + _ => Ok(None), + }, + } + } + + fn next_value_seed<V: de::DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value> { + match self.next_value.take() { + Some(value) => seed.deserialize(AttrValueDeserializer(value)), + None => { + if !self.inner_value { + if let XmlEvent::StartElement { .. } = *self.de.peek()? { + self.de.set_map_value(); + } + } + let result = seed.deserialize(&mut *self.de)?; + Ok(result) + }, + } + } + + fn size_hint(&self) -> Option<usize> { + self.attrs.size_hint().1 + } +} + +struct AttrValueDeserializer(String); + +macro_rules! deserialize_type_attr { + ($deserialize:ident => $visit:ident) => { + fn $deserialize<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + visitor.$visit(self.0.parse()?) + } + } +} + +impl<'de> de::Deserializer<'de> for AttrValueDeserializer { + type Error = Error; + + fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + visitor.visit_string(self.0) + } + + deserialize_type_attr!(deserialize_i8 => visit_i8); + deserialize_type_attr!(deserialize_i16 => visit_i16); + deserialize_type_attr!(deserialize_i32 => visit_i32); + deserialize_type_attr!(deserialize_i64 => visit_i64); + deserialize_type_attr!(deserialize_u8 => visit_u8); + deserialize_type_attr!(deserialize_u16 => visit_u16); + deserialize_type_attr!(deserialize_u32 => visit_u32); + deserialize_type_attr!(deserialize_u64 => visit_u64); + deserialize_type_attr!(deserialize_f32 => visit_f32); + deserialize_type_attr!(deserialize_f64 => visit_f64); + + fn deserialize_enum<V: de::Visitor<'de>>( + self, + _name: &str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> { + visitor.visit_enum(self.0.into_deserializer()) + } + + fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + visitor.visit_some(self) + } + + fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + 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")), + } + } + + forward_to_deserialize_any! { + char str string unit seq bytes map unit_struct newtype_struct tuple_struct + struct identifier tuple ignored_any byte_buf + } +} diff --git a/src/de/mod.rs b/src/de/mod.rs new file mode 100644 index 0000000..94077c5 --- /dev/null +++ b/src/de/mod.rs @@ -0,0 +1,367 @@ +use std::io::Read; + +use serde::de::{self, Unexpected}; +use xml::name::OwnedName; +use xml::reader::{EventReader, ParserConfig, XmlEvent}; + +use self::map::MapAccess; +use self::seq::SeqAccess; +use self::var::EnumAccess; +use error::{Error, Result}; + +mod map; +mod seq; +mod var; + +/// A convenience method for deserialize some object from a string. +/// +/// ```rust +/// # #[macro_use] +/// # extern crate serde_derive; +/// # extern crate serde; +/// # extern crate serde_xml_rs; +/// # use serde_xml_rs::from_str; +/// #[derive(Debug, Deserialize, PartialEq)] +/// struct Item { +/// name: String, +/// source: String, +/// } +/// # fn main() { +/// let s = r##"<item name="hello" source="world.rs" />"##; +/// let item: Item = from_str(s).unwrap(); +/// assert_eq!(item, Item { name: "hello".to_string(),source: "world.rs".to_string()}); +/// # } +/// ``` +pub fn from_str<'de, T: de::Deserialize<'de>>(s: &str) -> Result<T> { + from_reader(s.as_bytes()) +} + +/// A convenience method for deserialize some object from a reader. +/// +/// ```rust +/// # #[macro_use] +/// # extern crate serde_derive; +/// # extern crate serde; +/// # extern crate serde_xml_rs; +/// # use serde_xml_rs::from_reader; +/// #[derive(Debug, Deserialize, PartialEq)] +/// struct Item { +/// name: String, +/// source: String, +/// } +/// # fn main() { +/// let s = r##"<item name="hello" source="world.rs" />"##; +/// let item: Item = from_reader(s.as_bytes()).unwrap(); +/// assert_eq!(item, Item { name: "hello".to_string(),source: "world.rs".to_string()}); +/// # } +/// ``` +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> { + depth: usize, + reader: EventReader<R>, + peeked: Option<XmlEvent>, + is_map_value: bool, +} + +impl<'de, R: Read> Deserializer<R> { + pub fn new(reader: EventReader<R>) -> Self { + Deserializer { + depth: 0, + reader: reader, + peeked: None, + is_map_value: false, + } + } + + pub fn new_from_reader(reader: R) -> Self { + let config = ParserConfig::new() + .trim_whitespace(true) + .whitespace_to_characters(true) + .cdata_to_characters(true) + .ignore_comments(true) + .coalesce_characters(true); + + 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) + }) + } + + 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), + } + } + } + + fn next(&mut self) -> Result<XmlEvent> { + let next = if let Some(peeked) = self.peeked.take() { + peeked + } else { + self.inner_next()? + }; + match next { + XmlEvent::StartElement { .. } => { + self.depth += 1; + } + XmlEvent::EndElement { .. } => { + self.depth -= 1; + } + _ => {} + } + debug!("Fetched {:?}", next); + Ok(next) + } + + fn set_map_value(&mut self) { + self.is_map_value = true; + } + + pub fn unset_map_value(&mut self) -> bool { + ::std::mem::replace(&mut self.is_map_value, false) + } + + fn read_inner_value<V: de::Visitor<'de>, T, F: FnOnce(&mut Self) -> Result<T>>( + &mut self, + f: F, + ) -> Result<T> { + if self.unset_map_value() { + debug_expect!(self.next(), Ok(XmlEvent::StartElement { name, .. }) => { + let result = f(self)?; + self.expect_end_element(name)?; + Ok(result) + }) + } else { + f(self) + } + } + + fn expect_end_element(&mut self, start_name: OwnedName) -> Result<()> { + expect!(self.next()?, XmlEvent::EndElement { name, .. } => { + if name == start_name { + Ok(()) + } else { + Err(Error::Custom { field: format!( + "End tag </{}> didn't match the start tag <{}>", + name.local_name, + start_name.local_name + ) }) + } + }) + } + + fn prepare_parse_type<V: de::Visitor<'de>>(&mut self) -> Result<String> { + if let XmlEvent::StartElement { .. } = *self.peek()? { + self.set_map_value() + } + self.read_inner_value::<V, String, _>(|this| { + if let XmlEvent::EndElement { .. } = *this.peek()? { + return Err(Error::UnexpectedToken { + token: "EndElement".into(), + found: "Characters".into(), + }); + } + + expect!(this.next()?, XmlEvent::Characters(s) => { + return Ok(s) + }) + }) + } +} + +macro_rules! deserialize_type { + ($deserialize:ident => $visit:ident) => { + fn $deserialize<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + let value = self.prepare_parse_type::<V>()?.parse()?; + visitor.$visit(value) + } + } +} + +impl<'de, 'a, R: Read> de::Deserializer<'de> for &'a mut Deserializer<R> { + type Error = Error; + + forward_to_deserialize_any! { + identifier + } + + fn deserialize_struct<V: de::Visitor<'de>>( + self, + _name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> { + self.unset_map_value(); + expect!(self.next()?, XmlEvent::StartElement { name, attributes, .. } => { + let map_value = visitor.visit_map(MapAccess::new( + self, + attributes, + fields.contains(&"$value") + ))?; + self.expect_end_element(name)?; + Ok(map_value) + }) + } + + deserialize_type!(deserialize_i8 => visit_i8); + deserialize_type!(deserialize_i16 => visit_i16); + deserialize_type!(deserialize_i32 => visit_i32); + deserialize_type!(deserialize_i64 => visit_i64); + deserialize_type!(deserialize_u8 => visit_u8); + deserialize_type!(deserialize_u16 => visit_u16); + deserialize_type!(deserialize_u32 => visit_u32); + deserialize_type!(deserialize_u64 => visit_u64); + deserialize_type!(deserialize_f32 => visit_f32); + deserialize_type!(deserialize_f64 => visit_f64); + + fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + if let XmlEvent::StartElement { .. } = *self.peek()? { + self.set_map_value() + } + self.read_inner_value::<V, V::Value, _>(|this| { + if let XmlEvent::EndElement { .. } = *this.peek()? { + return visitor.visit_bool(false); + } + expect!(this.next()?, XmlEvent::Characters(s) => { + match s.as_str() { + "true" | "1" => visitor.visit_bool(true), + "false" | "0" => visitor.visit_bool(false), + _ => Err(de::Error::invalid_value(Unexpected::Str(&s), &"a boolean")), + } + + }) + }) + } + + fn deserialize_char<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.deserialize_string(visitor) + } + + fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.deserialize_string(visitor) + } + + fn deserialize_bytes<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.deserialize_string(visitor) + } + + fn deserialize_byte_buf<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.deserialize_string(visitor) + } + + fn deserialize_unit<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + if let XmlEvent::StartElement { .. } = *self.peek()? { + self.set_map_value() + } + self.read_inner_value::<V, V::Value, _>( + |this| expect!(this.peek()?, &XmlEvent::EndElement { .. } => visitor.visit_unit()), + ) + } + + fn deserialize_unit_struct<V: de::Visitor<'de>>( + self, + _name: &'static str, + visitor: V, + ) -> Result<V::Value> { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct<V: de::Visitor<'de>>( + self, + _name: &'static str, + visitor: V, + ) -> Result<V::Value> { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple_struct<V: de::Visitor<'de>>( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result<V::Value> { + self.deserialize_tuple(len, visitor) + } + + fn deserialize_tuple<V: de::Visitor<'de>>(self, len: usize, visitor: V) -> Result<V::Value> { + visitor.visit_seq(SeqAccess::new(self, Some(len))) + } + + fn deserialize_enum<V: de::Visitor<'de>>( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> { + self.read_inner_value::<V, V::Value, _>(|this| visitor.visit_enum(EnumAccess::new(this))) + } + + fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + if let XmlEvent::StartElement { .. } = *self.peek()? { + self.set_map_value() + } + self.read_inner_value::<V, V::Value, _>(|this| { + if let XmlEvent::EndElement { .. } = *this.peek()? { + return visitor.visit_str(""); + } + expect!(this.next()?, XmlEvent::Characters(s) => { + visitor.visit_string(s) + }) + }) + } + + fn deserialize_seq<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + visitor.visit_seq(SeqAccess::new(self, None)) + } + + fn deserialize_map<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.unset_map_value(); + expect!(self.next()?, XmlEvent::StartElement { name, attributes, .. } => { + let map_value = visitor.visit_map(MapAccess::new(self, attributes, false))?; + self.expect_end_element(name)?; + Ok(map_value) + }) + } + + fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + match *self.peek()? { + XmlEvent::EndElement { .. } => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_ignored_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + self.unset_map_value(); + let depth = self.depth; + loop { + self.next()?; + if self.depth == depth { + break; + } + } + visitor.visit_unit() + } + + fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> { + match *self.peek()? { + XmlEvent::StartElement { .. } => self.deserialize_map(visitor), + XmlEvent::EndElement { .. } => self.deserialize_unit(visitor), + _ => self.deserialize_string(visitor), + } + } +} diff --git a/src/de/seq.rs b/src/de/seq.rs new file mode 100644 index 0000000..7e88d0c --- /dev/null +++ b/src/de/seq.rs @@ -0,0 +1,70 @@ +use std::io::Read; + +use serde::de; +use xml::reader::XmlEvent; + +use de::Deserializer; +use error::{Error, Result}; + +pub struct SeqAccess<'a, R: 'a + Read> { + de: &'a mut Deserializer<R>, + max_size: Option<usize>, + expected_name: Option<String>, +} + +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() { + debug_expect!(de.peek(), Ok(&XmlEvent::StartElement { ref name, .. }) => { + Some(name.local_name.clone()) + }) + } else { + None + }; + SeqAccess { + de: de, + max_size: max_size, + expected_name: expected_name, + } + } +} + +impl<'de, 'a, R: 'a + Read> de::SeqAccess<'de> for SeqAccess<'a, R> { + type Error = Error; + + fn next_element_seed<T: de::DeserializeSeed<'de>>( + &mut self, + seed: T, + ) -> Result<Option<T::Value>> { + match self.max_size.as_mut() { + Some(&mut 0) => { + return Ok(None); + }, + Some(max_size) => { + *max_size -= 1; + }, + 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(); + } + seed.deserialize(&mut *self.de).map(Some) + } else { + Ok(None) + } + } + + fn size_hint(&self) -> Option<usize> { + self.max_size + } +} diff --git a/src/de/var.rs b/src/de/var.rs new file mode 100644 index 0000000..dbb400f --- /dev/null +++ b/src/de/var.rs @@ -0,0 +1,84 @@ +use std::io::Read; + +use serde::de::{self, Deserializer as SerdeDeserializer, IntoDeserializer}; +use xml::name::OwnedName; +use xml::reader::XmlEvent; + +use de::Deserializer; +use error::{Error, Result}; + +pub struct EnumAccess<'a, R: 'a + Read> { + de: &'a mut Deserializer<R>, +} + +impl<'a, R: 'a + Read> EnumAccess<'a, R> { + pub fn new(de: &'a mut Deserializer<R>) -> Self { + EnumAccess { de: de } + } +} + +impl<'de, 'a, R: 'a + Read> de::EnumAccess<'de> for EnumAccess<'a, R> { + type Error = Error; + type Variant = VariantAccess<'a, R>; + + fn variant_seed<V: de::DeserializeSeed<'de>>( + self, + seed: V, + ) -> Result<(V::Value, VariantAccess<'a, R>)> { + let name = expect!( + self.de.peek()?, + + &XmlEvent::Characters(ref name) | + &XmlEvent::StartElement { name: OwnedName { local_name: ref name, .. }, .. } => { + seed.deserialize(name.as_str().into_deserializer()) + } + )?; + self.de.set_map_value(); + Ok((name, VariantAccess::new(self.de))) + } +} + +pub struct VariantAccess<'a, R: 'a + Read> { + de: &'a mut Deserializer<R>, +} + +impl<'a, R: 'a + Read> VariantAccess<'a, R> { + pub fn new(de: &'a mut Deserializer<R>) -> Self { + VariantAccess { de: de } + } +} + +impl<'de, 'a, R: 'a + Read> de::VariantAccess<'de> for VariantAccess<'a, R> { + type Error = Error; + + fn unit_variant(self) -> Result<()> { + self.de.unset_map_value(); + 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")) + }, + XmlEvent::Characters(_) => Ok(()), + _ => unreachable!(), + } + } + + fn newtype_variant_seed<T: de::DeserializeSeed<'de>>(self, seed: T) -> Result<T::Value> { + seed.deserialize(&mut *self.de) + } + + fn tuple_variant<V: de::Visitor<'de>>(self, len: usize, visitor: V) -> Result<V::Value> { + self.de.deserialize_tuple(len, visitor) + } + + fn struct_variant<V: de::Visitor<'de>>( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> { + self.de.deserialize_map(visitor) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8d6e4ad --- /dev/null +++ b/src/error.rs @@ -0,0 +1,104 @@ +use serde::de::Error as DeError; +use serde::ser::Error as SerError; +use std::fmt::Display; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Expected token {token}, found {found}")] + UnexpectedToken { token: String, found: String }, + #[error("custom: {field}")] + Custom { field: String }, + #[error("unsupported operation: '{operation}'")] + UnsupportedOperation { operation: String }, + + #[error("IO error: {source}")] + Io { + #[from] + source: ::std::io::Error, + }, + + #[error("FromUtf8Error: {source}")] + FromUtf8Error { + #[from] + source: ::std::string::FromUtf8Error, + }, + + #[error("ParseIntError: {source}")] + ParseIntError { + #[from] + source: ::std::num::ParseIntError, + }, + + #[error("ParseFloatError: {source}")] + ParseFloatError { + #[from] + source: ::std::num::ParseFloatError, + }, + + #[error("ParseBoolError: {source}")] + ParseBoolError { + #[from] + source: ::std::str::ParseBoolError, + }, + + #[error("Syntax: {source}")] + Syntax { + #[from] + source: ::xml::reader::Error, + }, +} + +pub type Result<T> = std::result::Result<T, Error>; + +macro_rules! expect { + ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { + match $actual { + $($expected)|+ => $if_ok, + actual => Err($crate::Error::UnexpectedToken { + token: stringify!($($expected)|+).to_string(), + found: format!("{:?}",actual) + }) as Result<_> + } + } +} + +#[cfg(debug_assertions)] +macro_rules! debug_expect { + ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { + match $actual { + $($expected)|+ => $if_ok, + actual => panic!( + "Internal error: Expected token {}, found {:?}", + stringify!($($expected)|+), + actual + ) + } + } +} + +#[cfg(not(debug_assertions))] +macro_rules! debug_expect { + ($actual: expr, $($expected: pat)|+ => $if_ok: expr) => { + match $actual { + $($expected)|+ => $if_ok, + _ => unreachable!() + } + } +} + +impl DeError for Error { + fn custom<T: Display>(msg: T) -> Self { + Error::Custom { + field: msg.to_string(), + } + } +} + +impl SerError for Error { + fn custom<T: Display>(msg: T) -> Self { + Error::Custom { + field: msg.to_string(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1f93ed8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,55 @@ +//! +//! +//! # Examples +//! +//! ```rust +//! extern crate serde; +//! extern crate serde_xml_rs; +//! +//! #[macro_use] +//! extern crate serde_derive; +//! +//! use serde_xml_rs::{from_str, to_string}; +//! +//! #[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 item: Item = from_str(src).unwrap(); +//! assert_eq!(item, should_be); +//! +//! let reserialized_item = to_string(&item).unwrap(); +//! assert_eq!(src, reserialized_item); +//! } +//! ``` + +#[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; +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 xml::reader::{EventReader, ParserConfig}; diff --git a/src/ser/mod.rs b/src/ser/mod.rs new file mode 100644 index 0000000..bf97b1f --- /dev/null +++ b/src/ser/mod.rs @@ -0,0 +1,419 @@ +use std::fmt::Display; +use std::io::Write; + +use serde::ser::{self, Impossible, Serialize}; + +use self::var::{Map, Struct}; +use error::{Error, Result}; + +mod var; + +/// A convenience method for serializing some object to a buffer. +/// +/// # Examples +/// +/// ```rust +/// # #[macro_use] +/// # extern crate serde_derive; +/// # extern crate serde; +/// # extern crate serde_xml_rs; +/// # use serde_xml_rs::to_writer; +/// #[derive(Serialize)] +/// struct Person { +/// name: String, +/// age: u32, +/// } +/// +/// # fn main() { +/// let mut buffer = Vec::new(); +/// let joe = Person {name: "Joe".to_string(), age: 42}; +/// +/// to_writer(&mut buffer, &joe).unwrap(); +/// +/// let serialized = String::from_utf8(buffer).unwrap(); +/// println!("{}", serialized); +/// # } +/// ``` +pub fn to_writer<W: Write, S: Serialize>(writer: W, value: &S) -> Result<()> { + let mut ser = Serializer::new(writer); + value.serialize(&mut ser) +} + +/// A convenience method for serializing some object to a string. +/// +/// # Examples +/// +/// ```rust +/// # #[macro_use] +/// # extern crate serde_derive; +/// # extern crate serde; +/// # extern crate serde_xml_rs; +/// # use serde_xml_rs::to_string; +/// #[derive(Serialize)] +/// struct Person { +/// name: String, +/// age: u32, +/// } +/// +/// # fn main() { +/// +/// let joe = Person {name: "Joe".to_string(), age: 42}; +/// let serialized = to_string(&joe).unwrap(); +/// println!("{}", serialized); +/// # } +/// ``` +pub fn to_string<S: Serialize>(value: &S) -> Result<String> { + // Create a buffer and serialize our nodes into it + let mut writer = Vec::with_capacity(128); + to_writer(&mut writer, value)?; + + // We then check that the serialized string is the same as what we expect + let string = String::from_utf8(writer)?; + Ok(string) +} + +/// An XML `Serializer`. +pub struct Serializer<W> +where + W: Write, +{ + writer: W, +} + +impl<W> Serializer<W> +where + W: Write, +{ + pub fn new(writer: W) -> Self { + Self { writer: writer } + } + + fn write_primitive<P: Display>(&mut self, primitive: P) -> Result<()> { + write!(self.writer, "{}", primitive)?; + Ok(()) + } + + fn write_wrapped<S: Serialize>(&mut self, tag: &str, value: S) -> Result<()> { + write!(self.writer, "<{}>", tag)?; + value.serialize(&mut *self)?; + write!(self.writer, "</{}>", tag)?; + Ok(()) + } +} + +#[allow(unused_variables)] +impl<'w, W> ser::Serializer for &'w mut Serializer<W> +where + W: Write, +{ + type Ok = (); + type Error = Error; + + type SerializeSeq = Impossible<Self::Ok, Self::Error>; + type SerializeTuple = Impossible<Self::Ok, Self::Error>; + type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>; + type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>; + type SerializeMap = Map<'w, W>; + type SerializeStruct = Struct<'w, W>; + type SerializeStructVariant = Impossible<Self::Ok, Self::Error>; + + fn serialize_bool(self, v: bool) -> Result<Self::Ok> { + if v { + write!(self.writer, "true")?; + } else { + write!(self.writer, "false")?; + } + + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_i16(self, v: i16) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_i32(self, v: i32) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_i64(self, v: i64) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_u8(self, v: u8) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_u16(self, v: u16) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_u32(self, v: u32) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_u64(self, v: u64) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_f32(self, v: f32) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_f64(self, v: f64) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_char(self, v: char) -> Result<Self::Ok> { + self.write_primitive(v) + } + + fn serialize_str(self, value: &str) -> Result<Self::Ok> { + self.write_primitive(value) + } + + fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok> { + // TODO: I imagine you'd want to use base64 here. + // Not sure how to roundtrip effectively though... + Err(Error::UnsupportedOperation { + operation: "serialize_bytes".to_string(), + }) + } + + fn serialize_none(self) -> Result<Self::Ok> { + Ok(()) + } + + fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok> { + value.serialize(self) + } + + fn serialize_unit(self) -> Result<Self::Ok> { + self.serialize_none() + } + + fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok> { + self.write_wrapped(name, ()) + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result<Self::Ok> { + Err(Error::UnsupportedOperation { + operation: "serialize_unit_variant".to_string(), + }) + } + + fn serialize_newtype_struct<T: ?Sized + Serialize>( + self, + name: &'static str, + value: &T, + ) -> Result<Self::Ok> { + Err(Error::UnsupportedOperation { + operation: "serialize_newtype_struct".to_string(), + }) + } + + fn serialize_newtype_variant<T: ?Sized + Serialize>( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<Self::Ok> { + self.write_wrapped(variant, value) + } + + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> { + // TODO: Figure out how to constrain the things written to only be composites + Err(Error::UnsupportedOperation { + operation: "serialize_seq".to_string(), + }) + } + + fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> { + Err(Error::UnsupportedOperation { + operation: "serialize_tuple".to_string(), + }) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleStruct> { + Err(Error::UnsupportedOperation { + operation: "serialize_tuple_struct".to_string(), + }) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleVariant> { + Err(Error::UnsupportedOperation { + operation: "serialize_tuple_variant".to_string(), + }) + } + + fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> { + Ok(Map::new(self)) + } + + fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct> { + write!(self.writer, "<{}>", name)?; + Ok(Struct::new(self, name)) + } + + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<Self::SerializeStructVariant> { + Err(Error::UnsupportedOperation { + operation: "Result".to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::ser::{SerializeMap, SerializeStruct}; + use serde::Serializer as SerSerializer; + + #[test] + fn test_serialize_bool() { + let inputs = vec![(true, "true"), (false, "false")]; + + for (src, should_be) in inputs { + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + ser.serialize_bool(src).unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, should_be); + } + } + + #[test] + fn test_start_serialize_struct() { + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + let _ = ser.serialize_struct("foo", 0).unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, "<foo>"); + } + + #[test] + fn test_serialize_struct_field() { + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + let mut struct_ser = Struct::new(&mut ser, "baz"); + struct_ser.serialize_field("foo", "bar").unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, "<foo>bar</foo>"); + } + + #[test] + fn test_serialize_struct() { + #[derive(Serialize)] + struct Person { + name: String, + age: u32, + } + + let bob = Person { + name: "Bob".to_string(), + age: 42, + }; + let should_be = "<Person><name>Bob</name><age>42</age></Person>"; + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + bob.serialize(&mut ser).unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, should_be); + } + + #[test] + fn test_serialize_map_entries() { + let should_be = "<name>Bob</name><age>5</age>"; + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + let mut map = Map::new(&mut ser); + map.serialize_entry("name", "Bob").unwrap(); + map.serialize_entry("age", "5").unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, should_be); + } + + #[test] + fn test_serialize_enum() { + #[derive(Serialize)] + #[allow(dead_code)] + enum Node { + Boolean(bool), + Number(f64), + String(String), + } + + let mut buffer = Vec::new(); + let should_be = "<Boolean>true</Boolean>"; + + { + let mut ser = Serializer::new(&mut buffer); + let node = Node::Boolean(true); + node.serialize(&mut ser).unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + assert_eq!(got, should_be); + } + + #[test] + #[ignore] + fn serialize_a_list() { + let inputs = vec![1, 2, 3, 4]; + + let mut buffer = Vec::new(); + + { + let mut ser = Serializer::new(&mut buffer); + inputs.serialize(&mut ser).unwrap(); + } + + let got = String::from_utf8(buffer).unwrap(); + println!("{}", got); + panic!(); + } +} diff --git a/src/ser/var.rs b/src/ser/var.rs new file mode 100644 index 0000000..bb61472 --- /dev/null +++ b/src/ser/var.rs @@ -0,0 +1,103 @@ +use std::io::Write; + +use serde::ser::{self, Serialize}; + +use ser::Serializer; +use error::{Error, Result}; + +/// An implementation of `SerializeMap` for serializing to XML. +pub struct Map<'w, W> +where + W: 'w + Write, +{ + parent: &'w mut Serializer<W>, +} + +impl<'w, W> Map<'w, W> +where + W: 'w + Write, +{ + pub fn new(parent: &'w mut Serializer<W>) -> Map<'w, W> { + Map { parent } + } +} + +impl<'w, W> ser::SerializeMap for Map<'w, W> +where + W: 'w + Write, +{ + type Ok = (); + type Error = Error; + + fn serialize_key<T: ?Sized + Serialize>(&mut self, _: &T) -> Result<()> { + panic!("impossible to serialize the key on its own, please use serialize_entry()") + } + + fn serialize_value<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> { + value.serialize(&mut *self.parent) + } + + fn end(self) -> Result<Self::Ok> { + Ok(()) + } + + fn serialize_entry<K: ?Sized + Serialize, V: ?Sized + Serialize>( + &mut self, + key: &K, + value: &V, + ) -> Result<()> { + // TODO: Is it possible to ensure our key is never a composite type? + // Anything which isn't a "primitive" would lead to malformed XML here... + write!(self.parent.writer, "<")?; + key.serialize(&mut *self.parent)?; + write!(self.parent.writer, ">")?; + + value.serialize(&mut *self.parent)?; + + write!(self.parent.writer, "</")?; + key.serialize(&mut *self.parent)?; + write!(self.parent.writer, ">")?; + Ok(()) + } +} + +/// An implementation of `SerializeStruct` for serializing to XML. +pub struct Struct<'w, W> +where + W: 'w + Write, +{ + parent: &'w mut Serializer<W>, + name: &'w str, +} + +impl<'w, W> Struct<'w, W> +where + W: 'w + Write, +{ + pub fn new(parent: &'w mut Serializer<W>, name: &'w str) -> Struct<'w, W> { + Struct { parent, name } + } +} + +impl<'w, W> ser::SerializeStruct for Struct<'w, W> +where + W: 'w + Write, +{ + type Ok = (); + type Error = Error; + + fn serialize_field<T: ?Sized + Serialize>( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + write!(self.parent.writer, "<{}>", key)?; + value.serialize(&mut *self.parent)?; + write!(self.parent.writer, "</{}>", key)?; + Ok(()) + } + + fn end(self) -> Result<Self::Ok> { + write!(self.parent.writer, "</{}>", self.name).map_err(|e| e.into()) + } +} diff --git a/tests/failures.rs b/tests/failures.rs new file mode 100644 index 0000000..ca51f6b --- /dev/null +++ b/tests/failures.rs @@ -0,0 +1,52 @@ +#[macro_use] +extern crate serde_derive; +extern crate serde_xml_rs; + +#[macro_use] +extern crate log; +extern crate simple_logger; + +use serde_xml_rs::from_str; + +#[derive(Debug, Deserialize, PartialEq)] +struct Item { + name: String, + source: String, +} + +#[test] +fn simple_struct_from_attributes_should_fail() { + let _ = simple_logger::init(); + + let s = r##" + <item name="hello" source="world.rs /> + "##; + + let item: Result<Item, _> = from_str(s); + match item { + Ok(_) => assert!(false), + Err(e) => { + info!("simple_struct_from_attributes_should_fail(): {}", e); + assert!(true) + } + } +} + +#[test] +fn multiple_roots_attributes_should_fail() { + let _ = simple_logger::init(); + + let s = r##" + <item name="hello" source="world.rs" /> + <item name="hello source="world.rs" /> + "##; + + let item: Result<Vec<Item>, _> = from_str(s); + match item { + Ok(_) => assert!(false), + Err(e) => { + info!("multiple_roots_attributes_should_fail(): {}", e); + assert!(true) + } + } +} diff --git a/tests/migrated.rs b/tests/migrated.rs new file mode 100644 index 0000000..87d3e6a --- /dev/null +++ b/tests/migrated.rs @@ -0,0 +1,1086 @@ +#[macro_use] +extern crate serde_derive; + +extern crate log; +extern crate simple_logger; + +extern crate serde; +extern crate serde_xml_rs; + +use std::fmt::Debug; + +use serde::{de, ser}; +use serde_xml_rs::{from_str, Error}; + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +enum Animal { + Dog, + Frog(String), + Ant(Simple), + Cat { age: usize, name: String }, +} + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct Simple { + a: (), + b: usize, + c: String, + d: Option<String>, +} + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct Inner { + a: (), + b: (usize, String, i8), + c: Vec<String>, +} + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct Outer { + inner: Option<Inner>, +} + +fn test_parse_ok<'de, 'a, T>(errors: &[(&'a str, T)]) +where + T: PartialEq + Debug + ser::Serialize + de::Deserialize<'de>, +{ + for &(s, ref value) in errors { + let v: T = from_str(s).unwrap(); + assert_eq!(v, *value); + + // // Make sure we can deserialize into an `Element`. + // let xml_value: Element = from_str(s).unwrap(); + + // // Make sure we can deserialize from an `Element`. + // let v: T = from_value(xml_value.clone()).unwrap(); + // assert_eq!(v, *value); + } +} + +fn test_parse_err<'de, 'a, T>(errors: &[&'a str]) +where + T: PartialEq + Debug + ser::Serialize + de::Deserialize<'de>, +{ + for &s in errors { + assert!(match from_str::<T>(s) { + Err(Error::Syntax { source: _ }) => true, + _ => false, + }); + } +} + +fn test_parse_invalid<'de, 'a, T>(errors: &[&'a str]) +where + T: PartialEq + Debug + ser::Serialize + de::Deserialize<'de>, +{ + for &s in errors { + assert!(match from_str::<T>(s) { + Err(_) => true, + _ => false, + }); + } +} + +#[test] +fn test_namespaces() { + let _ = simple_logger::init(); + #[derive(PartialEq, Serialize, Deserialize, Debug)] + struct Envelope { + subject: String, + } + let s = r#" + <?xml version="1.0" encoding="UTF-8"?> + <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"> + <gesmes:subject>Reference rates</gesmes:subject> + </gesmes:Envelope>"#; + test_parse_ok(&[( + s, + Envelope { + subject: "Reference rates".to_string(), + }, + )]); +} + +#[test] +#[ignore] // FIXME +fn test_doctype() { + let _ = simple_logger::init(); + #[derive(PartialEq, Serialize, Deserialize, Debug)] + struct Envelope { + subject: String, + } + + test_parse_ok(&[ + ( + r#" + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE Envelope> + <Envelope> + <subject>Reference rates</subject> + </Envelope>"#, + Envelope { + subject: "Reference rates".to_string(), + }, + ), + ( + r#" + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE Envelope[]> + <Envelope> + <subject>Reference rates</subject> + </Envelope>"#, + Envelope { + subject: "Reference rates".to_string(), + }, + ), + ( + r#" + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE Envelope [ + <!ELEMENT subject (#PCDATA)> + ] > + <Envelope> + <subject>Reference rates</subject> + </Envelope>"#, + Envelope { + subject: "Reference rates".to_string(), + }, + ), + ]); +} + +#[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)] + struct Graphml { + #[serde(rename = "xsi:schemaLocation")] + schema_location: String, + } + let s = r#" + <?xml version="1.0" encoding="UTF-8"?> + <graphml xmlns="http://graphml.graphdrawing.org/xmlns" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns + http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> + + + </graphml>"#; + test_parse_ok(&[( + s, + Graphml { + schema_location: "http://graphml.graphdrawing.org/xmlns + http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd" + .to_string(), + }, + )]); +} + +#[test] +fn test_parse_string() { + let _ = simple_logger::init(); + + test_parse_ok(&[ + ( + "<bla>This is a String</bla>", + "This is a String".to_string(), + ), + ("<bla></bla>", "".to_string()), + ("<bla> </bla>", "".to_string()), + ("<bla><boom/></bla>", "<boom/>".to_string()), + ("<bla>♫</bla>", "♫".to_string()), + ("<bla>♫</bla>", "♫".to_string()), + ( + "<bla>♫<![CDATA[<cookies/>]]>♫</bla>", + "♫<cookies/>♫".to_string(), + ), + ]); +} + +#[test] +#[ignore] // FIXME +fn test_parse_string_not_trim() { + let _ = simple_logger::init(); + + test_parse_ok(&[("<bla> </bla>", " ".to_string())]); +} + +#[test] +#[ignore] // FIXME +fn test_parse_enum() { + use self::Animal::*; + let _ = simple_logger::init(); + + test_parse_ok(&[ + ("<Animal xsi:type=\"Dog\"/>", Dog), + ( + "<Animal xsi:type=\"Frog\">Quak</Animal>", + Frog("Quak".to_string()), + ), + ( + "<Animal xsi:type=\"Ant\"><a/><c>bla</c><b>15</b><d>Foo</d></Animal>", + Ant(Simple { + a: (), + b: 15, + c: "bla".to_string(), + d: Some("Foo".to_string()), + }), + ), + ( + "<Animal xsi:type=\"Ant\"><a/><c>bla</c><b>15</b></Animal>", + Ant(Simple { + a: (), + b: 15, + c: "bla".to_string(), + d: None, + }), + ), + ( + "<Animal xsi:type=\"Cat\"><age>42</age><name>Shere Khan</name></Animal>", + Cat { + age: 42, + name: "Shere Khan".to_string(), + }, + ), + ]); + + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct Helper { + x: Animal, + } + + test_parse_ok(&[ + ("<Helper><x xsi:type=\"Dog\"/></Helper>", Helper { x: Dog }), + ( + "<Helper><x xsi:type=\"Frog\">Quak</Animal></Helper>", + Helper { + x: Frog("Quak".to_string()), + }, + ), + ( + "<Helper><x xsi:type=\"Cat\"> + <age>42</age> + <name>Shere Khan</name> + </x></Helper>", + Helper { + x: Cat { + age: 42, + name: "Shere Khan".to_string(), + }, + }, + ), + ]); +} + +#[test] +fn test_parse_i64() { + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<bla>0</bla>", 0), + ("<bla>-2</bla>", -2), + ("<bla>-1234</bla>", -1234), + ("<bla> -1234 </bla>", -1234), + ]); +} + +#[test] +fn test_parse_u64() { + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<bla>0</bla>", 0), + ("<bla>1234</bla>", 1234), + ("<bla> 1234 </bla>", 1234), + ]); +} + +#[test] +fn test_parse_bool_element() { + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<bla>true</bla>", true), + ("<bla>false</bla>", false), + ("<bla> true </bla>", true), + ("<bla> false </bla>", false), + ("<bla>1</bla>", true), + ("<bla>0</bla>", false), + ]); + + test_parse_invalid::<bool>(&["<bla>verum</bla>"]); +} + +#[test] +fn test_parse_bool_attribute() { + #[derive(PartialEq, Debug, Deserialize, Serialize)] + struct Dummy { + foo: bool, + } + + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<bla foo=\"true\"/>", Dummy { foo: true }), + ("<bla foo=\"false\"/>", Dummy { foo: false }), + ("<bla foo=\"1\"/>", Dummy { foo: true }), + ("<bla foo=\"0\"/>", Dummy { foo: false }), + ]); + + test_parse_invalid::<Dummy>(&[ + "<bla foo=\"bar\"/>", + "<bla foo=\" true \"/>", + "<bla foo=\"10\"/>", + "<bla foo=\"\"/>", + "<bla/>", + ]); +} + +#[test] +fn test_parse_unit() { + let _ = simple_logger::init(); + test_parse_ok(&[("<bla/>", ())]); +} + +#[test] +fn test_parse_f64() { + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<bla>3.0</bla>", 3.0f64), + ("<bla>3.1</bla>", 3.1), + ("<bla>-1.2</bla>", -1.2), + ("<bla>0.4</bla>", 0.4), + ("<bla>0.4e5</bla>", 0.4e5), + ("<bla>0.4e15</bla>", 0.4e15), + ("<bla>0.4e-01</bla>", 0.4e-01), // precision troubles + ("<bla> 0.4e-01 </bla>", 0.4e-01), + ]); +} + +#[test] +fn test_parse_struct() { + let _ = simple_logger::init(); + + test_parse_ok(&[ + ( + "<Simple> + <c>abc</c> + <a/> + <b>2</b> + </Simple>", + Simple { + a: (), + b: 2, + c: "abc".to_string(), + d: None, + }, + ), + ( + "<Simple><!-- this is a comment --> + <c>abc</c> + <a/> + <b>2</b> + </Simple>", + Simple { + a: (), + b: 2, + c: "abc".to_string(), + d: None, + }, + ), + ( + "<Simple d=\"Foo\"><!-- this is a comment --> + <c>abc</c> + <a/> + <b>2</b> + </Simple>", + Simple { + a: (), + b: 2, + c: "abc".to_string(), + d: Some("Foo".to_string()), + }, + ), + ]); +} + +#[test] +fn test_option() { + let _ = simple_logger::init(); + test_parse_ok(&[ + ("<a/>", Some("".to_string())), + ("<a></a>", Some("".to_string())), + ("<a> </a>", Some("".to_string())), + ("<a>42</a>", Some("42".to_string())), + ]); +} + +#[test] +#[ignore] // FIXME +fn test_option_not_trim() { + let _ = simple_logger::init(); + test_parse_ok(&[("<a> </a>", Some(" ".to_string()))]); +} + +#[test] +fn test_amoskvin() { + let _ = simple_logger::init(); + #[derive(Debug, Deserialize, PartialEq, Serialize)] + struct Root { + foo: Vec<Foo>, + } + + #[derive(Debug, Deserialize, PartialEq, Serialize)] + struct Foo { + a: String, + b: Option<String>, + } + test_parse_ok(&[( + " +<root> +<foo> + <a>Hello</a> + <b>World</b> +</foo> +<foo> + <a>Hi</a> +</foo> +</root>", + Root { + foo: vec![ + Foo { + a: "Hello".to_string(), + b: Some("World".to_string()), + }, + Foo { + a: "Hi".to_string(), + b: None, + }, + ], + }, + )]); +} + +#[test] +#[ignore] // FIXME +fn test_nicolai86() { + let _ = simple_logger::init(); + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TheSender { + name: String, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct CurrencyCube { + currency: String, + rate: String, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[allow(non_snake_case)] + struct InnerCube { + Cube: Vec<CurrencyCube>, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[allow(non_snake_case)] + struct OuterCube { + Cube: Vec<InnerCube>, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[allow(non_snake_case)] + struct Envelope { + subject: String, + Sender: TheSender, + Cube: OuterCube, + } + test_parse_ok(&[ + ( + r#" + <?xml version="1.0" encoding="UTF-8"?> + <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"> + <gesmes:subject>Reference rates</gesmes:subject> + <gesmes:Sender> + <gesmes:name>European Central Bank</gesmes:name> + </gesmes:Sender> + <Cube> </Cube> + </gesmes:Envelope>"#, + Envelope { + subject: "Reference rates".to_string(), + Sender: TheSender { + name: "European Central Bank".to_string(), + }, + Cube: OuterCube { Cube: vec![] }, + }, + ), + ( + r#" + <?xml version="1.0" encoding="UTF-8"?> + <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"> + <gesmes:subject>Reference rates</gesmes:subject> + <gesmes:Sender> + <gesmes:name>European Central Bank</gesmes:name> + </gesmes:Sender> + <Cube><Cube> + <Cube currency='GBP' rate='0.81725'/> + <Cube currency='Latinum' rate='999999'/> + </Cube></Cube> + </gesmes:Envelope>"#, + Envelope { + subject: "Reference rates".to_string(), + Sender: TheSender { + name: "European Central Bank".to_string(), + }, + Cube: OuterCube { + Cube: vec![InnerCube { + Cube: vec![ + CurrencyCube { + currency: "GBP".to_string(), + rate: "0.81725".to_string(), + }, + CurrencyCube { + currency: "Latinum".to_string(), + rate: "999999".to_string(), + }, + ], + }], + }, + }, + ), + ]); +} + +#[test] +fn test_hugo_duncan2() { + let _ = simple_logger::init(); + let s = r#" + <?xml version="1.0" encoding="UTF-8"?> + <DescribeVpcsResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/"> + <requestId>8d521e9a-509e-4ef6-bbb7-9f1ac0d49cd1</requestId> + <vpcSet> + <item> + <vpcId>vpc-ba0d18d8</vpcId> + <state>available</state> + </item> + </vpcSet> + </DescribeVpcsResponse>"#; + #[derive(PartialEq, Debug, Serialize, Deserialize)] + #[allow(non_snake_case)] + struct VpcSet { + vpcId: String, + state: String, + } + + #[derive(PartialEq, Debug, Serialize)] + struct ItemVec<T>(Vec<T>); + + impl<'de, T: de::Deserialize<'de>> de::Deserialize<'de> for ItemVec<T> { + fn deserialize<D>(deserializer: D) -> Result<ItemVec<T>, D::Error> + where + D: de::Deserializer<'de>, + { + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct Helper<U> { + item: Vec<U>, + } + let h: Helper<_> = de::Deserialize::deserialize(deserializer)?; + Ok(ItemVec(h.item)) + } + } + #[derive(PartialEq, Debug, Serialize, Deserialize)] + #[allow(non_snake_case)] + struct DescribeVpcsResponse { + requestId: String, + vpcSet: ItemVec<VpcSet>, + } + test_parse_ok(&[( + s, + DescribeVpcsResponse { + requestId: "8d521e9a-509e-4ef6-bbb7-9f1ac0d49cd1".to_string(), + vpcSet: ItemVec(vec![VpcSet { + vpcId: "vpc-ba0d18d8".to_string(), + state: "available".to_string(), + }]), + }, + )]); +} + +#[test] +fn test_hugo_duncan() { + let _ = simple_logger::init(); + let s = " + <?xml version=\"1.0\" encoding=\"UTF-8\"?> + <DescribeInstancesResponse xmlns=\"http://ec2.amazonaws.com/doc/2014-10-01/\"> + <requestId>9474f558-10a5-42e8-84d1-f9ee181fe943</requestId> + <reservationSet/> + </DescribeInstancesResponse> + "; + #[derive(PartialEq, Debug, Serialize, Deserialize)] + #[allow(non_snake_case)] + struct DescribeInstancesResponse { + requestId: String, + reservationSet: (), + } + test_parse_ok(&[( + s, + DescribeInstancesResponse { + requestId: "9474f558-10a5-42e8-84d1-f9ee181fe943".to_string(), + reservationSet: (), + }, + )]); +} + +#[test] +fn test_parse_xml_value() { + let _ = simple_logger::init(); + #[derive(Eq, Debug, PartialEq, Deserialize, Serialize)] + struct Test { + #[serde(rename = "$value")] + myval: String, + } + test_parse_ok(&[( + "<Test>abc</Test>", + Test { + myval: "abc".to_string(), + }, + )]); +} + +#[test] +#[ignore] // FIXME +fn test_parse_complexstruct() { + let _ = simple_logger::init(); + + test_parse_ok(&[ + ( + "<Outer> + <inner> + <b>2</b> + <b>boom</b> + <b>88</b> + </inner> + </Outer>", + Outer { + inner: Some(Inner { + a: (), + b: (2, "boom".to_string(), 88), + c: vec![], + }), + }, + ), + ( + "<Outer> + <inner> + <c>abc</c> + <c>xyz</c> + <a/> + <b>2</b> + <b>boom</b> + <b>88</b> + </inner> + </Outer>", + Outer { + inner: Some(Inner { + a: (), + b: (2, "boom".to_string(), 88), + c: vec!["abc".to_string(), "xyz".to_string()], + }), + }, + ), + ("<Outer/>", Outer { inner: None }), + ]); +} + +#[test] +fn test_parse_attributes() { + let _ = simple_logger::init(); + + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct A { + a1: String, + #[serde(rename = "$value")] + a2: i32, + } + + test_parse_ok(&[( + r#"<A a1="What is the answer to the ultimate question?">42</A>"#, + A { + a1: "What is the answer to the ultimate question?".to_string(), + a2: 42, + }, + )]); + + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct B { + b1: String, + b2: i32, + } + + test_parse_ok(&[( + r#"<B b1="What is the answer to the ultimate question?" b2="42"/>"#, + B { + b1: "What is the answer to the ultimate question?".to_string(), + b2: 42, + }, + )]); + + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct C { + c1: B, + } + + test_parse_ok(&[ + ( + r#"<C><c1 b1="What is the answer to the ultimate question?" b2="42"/></C>"#, + C { + c1: B { + b1: "What is the answer to the ultimate question?".to_string(), + b2: 42, + }, + }, + ), + ( + r#"<C><c1 b1="What is the answer to the ultimate question?" b2="42"/> </C>"#, + C { + c1: B { + b1: "What is the answer to the ultimate question?".to_string(), + b2: 42, + }, + }, + ), + ( + r#"<C> <c1 b1="What is the answer to the ultimate question?" b2="42"> + </c1> </C>"#, + C { + c1: B { + b1: "What is the answer to the ultimate question?".to_string(), + b2: 42, + }, + }, + ), + ]); + + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct D { + d1: Option<A>, + } + test_parse_ok(&[( + r#"<D><d1 a1="What is the answer to the ultimate question?">42</d1></D>"#, + D { + d1: Some(A { + a1: "What is the answer to the ultimate question?".to_string(), + a2: 42, + }), + }, + )]); +} + +#[test] +#[ignore] // FIXME +fn test_parse_hierarchies() { + let _ = simple_logger::init(); + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct A { + a1: String, + a2: (String, String), + } + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct B { + b1: A, + b2: (A, A), + } + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct C { + c1: B, + c2: Vec<B>, + } + + test_parse_ok(&[ + ( + "<C><c1> + <b1> + <a1>No</a1> + <a2>Maybe</a2> + <a2>Yes</a2> + </b1> + <b2> + <a1>Red</a1> + <a2>Green</a2> + <a2>Blue</a2> + </b2> + <b2> + <a1>London</a1> + <a2>Berlin</a2> + <a2>Paris</a2> + </b2> + </c1></C>", + C { + c1: B { + b1: A { + a1: "No".to_string(), + a2: ("Maybe".to_string(), "Yes".to_string()), + }, + b2: ( + A { + a1: "Red".to_string(), + a2: ("Green".to_string(), "Blue".to_string()), + }, + A { + a1: "London".to_string(), + a2: ("Berlin".to_string(), "Paris".to_string()), + }, + ), + }, + c2: vec![], + }, + ), + ( + "<C><c1> + <b2> + <a2>Green</a2> + <a2>Blue</a2> + <a1>Red</a1> + </b2> + <b2> + <a2>Berlin</a2> + <a2>Paris</a2> + <a1>London</a1> + </b2> + <b1> + <a2>Maybe</a2> + <a2>Yes</a2> + <a1>No</a1> + </b1> + </c1></C>", + C { + c1: B { + b1: A { + a1: "No".to_string(), + a2: ("Maybe".to_string(), "Yes".to_string()), + }, + b2: ( + A { + a1: "Red".to_string(), + a2: ("Green".to_string(), "Blue".to_string()), + }, + A { + a1: "London".to_string(), + a2: ("Berlin".to_string(), "Paris".to_string()), + }, + ), + }, + c2: vec![], + }, + ), + ]); +} + +#[test] +fn unknown_field() { + #[derive(Deserialize, Debug, PartialEq, Eq, Serialize)] + struct A { + other: Vec<Other>, + } + + #[derive(Deserialize, Debug, PartialEq, Eq, Serialize)] + struct Other { + d: i32, + } + test_parse_ok(&[( + "<a> + <b> + <c>5</c> + </b> + <other> + <d>6</d> + </other> + </a>", + A { + other: vec![Other { d: 6 }], + }, + )]); +} + +// #[test] +// fn eoz() { +// use std::io::Read; +// let mut file = std::fs::File::open("Report_test.2.xml").unwrap(); +// let mut s = String::new(); +// file.read_to_string(&mut s).unwrap(); + +// let _xml_value: Element = from_str(&s).unwrap(); +// } + +#[test] +fn test_parse_unfinished() { + test_parse_err::<Simple>(&["<Simple> + <c>abc</c> + <a/> + <b>2</b> + <d/>"]); +} + +#[test] +fn test_things_qc_found() { + test_parse_err::<u32>(&["<\u{0}:/"]); +} + +#[test] +fn futile() { + let _ = simple_logger::init(); + #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] + struct Object { + id: u8, + name: String, + x: u8, + y: u8, + width: u8, + height: u8, + ellipse: Option<()>, + } + + test_parse_ok(&[ + ( + r###" + <object id="11" name="testEllipse" x="102" y="38" width="21" height="14"> + <ellipse/> + </object> + "###, + Object { + id: 11, + name: "testEllipse".to_owned(), + x: 102, + y: 38, + width: 21, + height: 14, + ellipse: Some(()), + }, + ), + ( + r###" + <object id="11" name="testEllipse" x="102" y="38" width="21" height="14"> + </object> + "###, + Object { + id: 11, + name: "testEllipse".to_owned(), + x: 102, + y: 38, + width: 21, + height: 14, + ellipse: None, + }, + ), + ]); +} + +#[test] +fn futile2() { + let _ = simple_logger::init(); + #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] + struct Null; + + #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] + struct Object { + field: Option<Null>, + }; + + #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] + struct Stuff { + stuff_field: Option<Object>, + }; + + test_parse_ok(&[ + ( + r###" + <object> + <field/> + </object> + "###, + Object { field: Some(Null) }, + ), + ( + r###" + <object> + </object> + "###, + Object { field: None }, + ), + ]); + + test_parse_ok(&[ + ( + r###" + <object> + <stuff_field/> + </object> + "###, + Stuff { + stuff_field: Some(Object { field: None }), + }, + ), + ( + r###" + <object> + <stuff_field> + <field/> + </stuff_field> + </object> + "###, + Stuff { + stuff_field: Some(Object { field: Some(Null) }), + }, + ), + ( + r###" + <object> + </object> + "###, + Stuff { stuff_field: None }, + ), + ( + r###" + <object/> + "###, + Stuff { stuff_field: None }, + ), + ]); +} + +#[test] +fn newtype_struct() { + #[derive(PartialEq, Debug, Serialize, Deserialize)] + struct Wrapper(String); + + test_parse_ok(&[( + r###"<wrapper>Content</wrapper>"###, + Wrapper("Content".into()), + )]); +} diff --git a/tests/readme.rs b/tests/readme.rs new file mode 100644 index 0000000..881d572 --- /dev/null +++ b/tests/readme.rs @@ -0,0 +1,6 @@ +extern crate docmatic; + +#[test] +fn test_readme() { + docmatic::assert_file("README.md"); +} diff --git a/tests/round_trip.rs b/tests/round_trip.rs new file mode 100644 index 0000000..54b3b53 --- /dev/null +++ b/tests/round_trip.rs @@ -0,0 +1,110 @@ +#[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}; + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct Item { + name: String, + source: String, +} + + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +enum Node { + Boolean(bool), + Identifier { value: String, index: u32 }, + EOF, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct Nodes { + #[serde(rename = "$value")] + items: Vec<Node>, +} + + +#[test] +fn basic_struct() { + let src = r#"<Item><name>Banana</name><source>Store</source></Item>"#; + let should_be = Item { + name: "Banana".to_string(), + source: "Store".to_string(), + }; + + let item: Item = from_str(src).unwrap(); + assert_eq!(item, should_be); + + let reserialized_item = to_string(&item).unwrap(); + assert_eq!(src, reserialized_item); +} + + +#[test] +#[ignore] +fn round_trip_list_of_enums() { + // Construct some inputs + let nodes = Nodes { + items: vec![ + Node::Boolean(true), + Node::Identifier { + value: "foo".to_string(), + index: 5, + }, + Node::EOF, + ], + }; + + let should_be = r#" + <Nodes> + <Boolean> + true + </Boolean> + <Identifier> + <value>foo</value> + <index>5</index> + </Identifier> + <EOF /> + </Nodes>"#; + + let serialized_nodes = to_string(&nodes).unwrap(); + assert_eq!(serialized_nodes, should_be); + + // Then turn it back into a `Nodes` struct and make sure it's the same + // as the original + let deserialized_nodes: Nodes = from_str(serialized_nodes.as_str()).unwrap(); + assert_eq!(deserialized_nodes, nodes); +} + +#[test] +fn whitespace_preserving_config() { + // Test a configuration which does not clip whitespace from tags + + let src = r#" + <Item> + <name> space banana </name> + <source> fantasy costco </source> + </Item>"#; + + let item_should_be = Item { + name: " space banana ".to_string(), + source: " fantasy costco ".to_string(), + }; + let config = ParserConfig::new() + .trim_whitespace(false) + .whitespace_to_characters(false); + let mut deserializer = + serde_xml_rs::Deserializer::new(EventReader::new_with_config(src.as_bytes(), config)); + + let item = Item::deserialize(&mut deserializer).unwrap(); + assert_eq!(item, item_should_be); + + // Space outside values is not preserved. + let serialized_should_be = + "<Item><name> space banana </name><source> fantasy costco </source></Item>"; + let reserialized_item = to_string(&item).unwrap(); + assert_eq!(reserialized_item, serialized_should_be); +} diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..4b8ec86 --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,161 @@ +#[macro_use] +extern crate serde_derive; +extern crate serde_xml_rs; + +extern crate log; +extern crate simple_logger; + +use serde_xml_rs::from_str; + +#[derive(Debug, Deserialize, PartialEq)] +struct Item { + name: String, + source: String, +} + +#[test] +fn simple_struct_from_attributes() { + let _ = simple_logger::init(); + + let s = r##" + <item name="hello" source="world.rs" /> + "##; + + let item: Item = from_str(s).unwrap(); + + assert_eq!( + item, + Item { + name: "hello".to_string(), + source: "world.rs".to_string(), + } + ); +} + +#[test] +fn multiple_roots_attributes() { + let _ = simple_logger::init(); + + let s = r##" + <item name="hello" source="world.rs" /> + <item name="hello" source="world.rs" /> + "##; + + let item: Vec<Item> = from_str(s).unwrap(); + + assert_eq!( + item, + vec![ + Item { + name: "hello".to_string(), + source: "world.rs".to_string(), + }, + Item { + name: "hello".to_string(), + source: "world.rs".to_string(), + }, + ] + ); +} + +#[test] +fn simple_struct_from_attribute_and_child() { + let _ = simple_logger::init(); + + let s = r##" + <item name="hello"> + <source>world.rs</source> + </item> + "##; + + let item: Item = from_str(s).unwrap(); + + assert_eq!( + item, + Item { + name: "hello".to_string(), + source: "world.rs".to_string(), + } + ); +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Project { + name: String, + + #[serde(rename = "item", default)] + items: Vec<Item>, +} + +#[test] +fn nested_collection() { + let _ = simple_logger::init(); + + let s = r##" + <project name="my_project"> + <item name="hello1" source="world1.rs" /> + <item name="hello2" source="world2.rs" /> + </project> + "##; + + let project: Project = from_str(s).unwrap(); + + assert_eq!( + project, + Project { + name: "my_project".to_string(), + items: vec![ + Item { + name: "hello1".to_string(), + source: "world1.rs".to_string(), + }, + Item { + name: "hello2".to_string(), + source: "world2.rs".to_string(), + }, + ], + } + ); +} + +#[derive(Debug, Deserialize, PartialEq)] +enum MyEnum { + A(String), + B { name: String, flag: bool }, + C, +} + +#[derive(Debug, Deserialize, PartialEq)] +struct MyEnums { + #[serde(rename = "$value")] + items: Vec<MyEnum>, +} + +#[test] +fn collection_of_enums() { + let _ = simple_logger::init(); + + let s = r##" + <enums> + <A>test</A> + <B name="hello" flag="true" /> + <C /> + </enums> + "##; + + let project: MyEnums = from_str(s).unwrap(); + + assert_eq!( + project, + MyEnums { + items: vec![ + MyEnum::A("test".to_string()), + MyEnum::B { + name: "hello".to_string(), + flag: true, + }, + MyEnum::C, + ], + } + ); +} |