diff --git a/Cargo.toml b/Cargo.toml index 1f871430..73a9a9f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,7 @@ include = ["src/*", "LICENSE-MIT.md", "README.md"] [dependencies] document-features = { version = "0.2", optional = true } encoding_rs = { version = "0.8", optional = true } -# FIXME: remove upper bound when https://github.com/tafia/quick-xml/issues/630 is resolved -serde = { version = ">=1.0.100,<1.0.181", optional = true } +serde = { version = ">=1.0.100", optional = true } tokio = { version = "1.10", optional = true, default-features = false, features = ["io-util"] } memchr = "2.1" arbitrary = { version = "1", features = ["derive"], optional = true } diff --git a/Changelog.md b/Changelog.md index aa9ecb9b..8de5fa4e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,9 @@ MSRV bumped to 1.56! Crate now uses Rust 2021 edition. +Enum representation was changed (it was buggy anyway) to ensure compatibility with +serde >= 1.0.181 + ### New Features - [#545]: Resolve well-known namespaces (`xml` and `xmlns`) to their appropriate URIs. @@ -40,11 +43,13 @@ MSRV bumped to 1.56! Crate now uses Rust 2021 edition. (and newly added `ElementWriter::write_inner_content_async` of course). - [#662]: Get rid of some allocations during serde deserialization. - [#665]: Improve serialization of `xs:list`s when some elements serialized to an empty string. +- [#630]: Fixed compatibility with serde >= 1.0.181 [#545]: https://github.com/tafia/quick-xml/pull/545 [#567]: https://github.com/tafia/quick-xml/issues/567 [#580]: https://github.com/tafia/quick-xml/issues/580 [#619]: https://github.com/tafia/quick-xml/issues/619 +[#630]: https://github.com/tafia/quick-xml/issues/630 [#635]: https://github.com/tafia/quick-xml/pull/635 [#643]: https://github.com/tafia/quick-xml/pull/643 [#649]: https://github.com/tafia/quick-xml/pull/646 diff --git a/src/de/map.rs b/src/de/map.rs index 7abd45df..97e632da 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -613,7 +613,27 @@ where where V: Visitor<'de>, { - visitor.visit_enum(self) + if self.fixed_name { + match self.map.de.next()? { + // Handles UnitEnumVariant + DeEvent::Start(_) => { + // skip , read text after it and ensure that it is ended by + let text = self.map.de.read_text()?; + if text.is_empty() { + // Map empty text () to a special `$text` variant + visitor.visit_enum(SimpleTypeDeserializer::from_text(TEXT_KEY.into())) + } else { + visitor.visit_enum(SimpleTypeDeserializer::from_text(text)) + } + } + // SAFETY: we use that deserializer with `fixed_name == true` + // only from the `MapAccess::next_value_seed` and only when we + // peeked `Start` event + _ => unreachable!(), + } + } else { + visitor.visit_enum(self) + } } fn deserialize_any(self, visitor: V) -> Result diff --git a/src/de/mod.rs b/src/de/mod.rs index 248d82c2..a7190f6e 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -20,6 +20,9 @@ //! - [Choices (`xs:choice` XML Schema type)](#choices-xschoice-xml-schema-type) //! - [Sequences (`xs:all` and `xs:sequence` XML Schema types)](#sequences-xsall-and-xssequence-xml-schema-types) //! - [Composition Rules](#composition-rules) +//! - [Enum Representations](#enum-representations) +//! - [Normal enum variant](#normal-enum-variant) +//! - [`$text` enum variant](#text-enum-variant) //! - [Difference between `$text` and `$value` special names](#difference-between-text-and-value-special-names) //! - [`$text`](#text) //! - [`$value`](#value) @@ -29,7 +32,6 @@ //! - [Frequently Used Patterns](#frequently-used-patterns) //! - [`` lists](#element-lists) //! - [Overlapped (Out-of-Order) Elements](#overlapped-out-of-order-elements) -//! - [Enum::Unit Variants As a Text](#enumunit-variants-as-a-text) //! - [Internally Tagged Enums](#internally-tagged-enums) //! //! @@ -1351,6 +1353,58 @@ //! //! //! +//! Enum Representations +//! ==================== +//! +//! `quick-xml` represents enums differently in normal fields, `$text` fields and +//! `$value` fields. A normal representation is compatible with serde's adjacent +//! and internal tags feature -- tag for adjacently and internally tagged enums +//! are serialized using [`Serializer::serialize_unit_variant`] and deserialized +//! using [`Deserializer::deserialize_enum`]. +//! +//! Use those simple rules to remember, how enum would be represented in XML: +//! - In `$value` field the representation is always the same as top-level representation; +//! - In `$text` field the representation is always the same as in normal field, +//! but surrounding tags with field name are removed; +//! - In normal field the representation is always contains a tag with a field name. +//! +//! Normal enum variant +//! ------------------- +//! +//! To model an `xs:choice` XML construct use `$value` field. +//! To model a top-level `xs:choice` just use the enum type. +//! +//! |Kind |Top-level and in `$value` field |In normal field |In `$text` field | +//! |-------|-----------------------------------------|---------------------|---------------------| +//! |Unit |`` |`Unit`|`Unit` | +//! |Newtype|`42` |Err(Unsupported) |Err(Unsupported) | +//! |Tuple |`42answer` |Err(Unsupported) |Err(Unsupported) | +//! |Struct |`42answer`|Err(Unsupported) |Err(Unsupported) | +//! +//! `$text` enum variant +//! -------------------- +//! +//! |Kind |Top-level and in `$value` field |In normal field |In `$text` field | +//! |-------|-----------------------------------------|---------------------|---------------------| +//! |Unit |_(empty)_ |`` |_(empty)_ | +//! |Newtype|`42` |Err(Unsupported) [^1]|Err(Unsupported) [^2]| +//! |Tuple |`42 answer` |Err(Unsupported) [^3]|Err(Unsupported) [^4]| +//! |Struct |Err(Unsupported) |Err(Unsupported) |Err(Unsupported) | +//! +//! [^1]: If this serialize as `42` then it will be ambiguity during deserialization, +//! because it clash with `Unit` representation in normal field. +//! +//! [^2]: If this serialize as `42` then it will be ambiguity during deserialization, +//! because it clash with `Unit` representation in `$text` field. +//! +//! [^3]: If this serialize as `42 answer` then it will be ambiguity during deserialization, +//! because it clash with `Unit` representation in normal field. +//! +//! [^4]: If this serialize as `42 answer` then it will be ambiguity during deserialization, +//! because it clash with `Unit` representation in `$text` field. +//! +//! +//! //! Difference between `$text` and `$value` special names //! ===================================================== //! @@ -1431,33 +1485,54 @@ //! get their names from the field name. It cannot be deserialized, because `Enum` //! expects elements ``, `` or ``, but `AnyName` looked only for ``: //! -//! ```no_run +//! ``` //! # use serde::{Deserialize, Serialize}; +//! # use pretty_assertions::assert_eq; +//! # #[derive(PartialEq, Debug)] //! #[derive(Deserialize, Serialize)] //! enum Enum { A, B, C } //! +//! # #[derive(PartialEq, Debug)] //! #[derive(Deserialize, Serialize)] //! struct AnyName { -//! // +//! // A, B, or C //! field: Enum, //! } +//! # assert_eq!( +//! # quick_xml::se::to_string(&AnyName { field: Enum::A }).unwrap(), +//! # "A", +//! # ); +//! # assert_eq!( +//! # AnyName { field: Enum::B }, +//! # quick_xml::de::from_str("B").unwrap(), +//! # ); //! ``` //! //! If you rename field to `$value`, then `field` would be serialized as ``, //! `` or ``, depending on the its content. It is also possible to //! deserialize it from the same elements: //! -//! ```no_run +//! ``` //! # use serde::{Deserialize, Serialize}; -//! # #[derive(Deserialize, Serialize)] +//! # use pretty_assertions::assert_eq; +//! # #[derive(Deserialize, Serialize, PartialEq, Debug)] //! # enum Enum { A, B, C } //! # +//! # #[derive(PartialEq, Debug)] //! #[derive(Deserialize, Serialize)] //! struct AnyName { //! // , or //! #[serde(rename = "$value")] //! field: Enum, //! } +//! # assert_eq!( +//! # quick_xml::se::to_string(&AnyName { field: Enum::A }).unwrap(), +//! # "", +//! # ); +//! # assert_eq!( +//! # AnyName { field: Enum::B }, +//! # quick_xml::de::from_str("").unwrap(), +//! # ); //! ``` //! //! ### Primitives and sequences of primitives @@ -1467,6 +1542,7 @@ //! //! ``` //! # use serde::{Deserialize, Serialize}; +//! # use pretty_assertions::assert_eq; //! # use quick_xml::de::from_str; //! # use quick_xml::se::to_string; //! #[derive(Deserialize, Serialize, PartialEq, Debug)] @@ -1493,6 +1569,7 @@ //! //! ``` //! # use serde::{Deserialize, Serialize}; +//! # use pretty_assertions::assert_eq; //! # use quick_xml::de::from_str; //! # use quick_xml::se::to_string; //! #[derive(Deserialize, Serialize, PartialEq, Debug)] @@ -1516,6 +1593,7 @@ //! //! ``` //! # use serde::{Deserialize, Serialize}; +//! # use pretty_assertions::assert_eq; //! # use quick_xml::de::from_str; //! # use quick_xml::se::to_string; //! #[derive(Deserialize, Serialize, PartialEq, Debug)] @@ -1549,6 +1627,7 @@ //! //! ``` //! # use serde::{Deserialize, Serialize}; +//! # use pretty_assertions::assert_eq; //! # use quick_xml::de::from_str; //! # use quick_xml::se::to_string; //! #[derive(Deserialize, Serialize, PartialEq, Debug)] @@ -1708,75 +1787,6 @@ //! } //! ``` //! -//! Enum::Unit Variants As a Text -//! ----------------------------- -//! One frequent task and a typical mistake is to creation of mapping a text -//! content of some tag to a Rust `enum`. For example, for the XML: -//! -//! ```xml -//! -//! EnumValue -//! -//! ``` -//! one could create an _incorrect_ mapping -//! -//! ``` -//! # use serde::{Deserialize, Serialize}; -//! # -//! #[derive(Serialize, Deserialize)] -//! enum SomeEnum { -//! EnumValue, -//! # /* -//! ... -//! # */ -//! } -//! -//! #[derive(Serialize, Deserialize)] -//! #[serde(rename = "some-container")] -//! struct SomeContainer { -//! field: SomeEnum, -//! } -//! ``` -//! -//! Actually, those types will be serialized into: -//! ```xml -//! -//! -//! -//! ``` -//! and will not be able to be deserialized. -//! -//! You can easily see what's wrong if you think about attributes, which could -//! be defined in the `` tag: -//! ```xml -//! -//! EnumValue -//! -//! ``` -//! -//! After that you can find the correct solution, using the principles explained -//! above. You should wrap `SomeEnum` into wrapper struct under the [`$text`](#text) -//! name: -//! ``` -//! # use serde::{Serialize, Deserialize}; -//! # type SomeEnum = (); -//! #[derive(Serialize, Deserialize)] -//! struct Field { -//! // Use a special name `$text` to map field to the text content -//! #[serde(rename = "$text")] -//! content: SomeEnum, -//! } -//! -//! #[derive(Serialize, Deserialize)] -//! #[serde(rename = "some-container")] -//! struct SomeContainer { -//! field: Field, -//! } -//! ``` -//! -//! If you still want to keep your struct untouched, you can instead use the -//! helper module [`text_content`]. -//! //! //! Internally Tagged Enums //! ----------------------- @@ -1794,7 +1804,8 @@ //! [specification]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition //! [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with //! [#497]: https://github.com/tafia/quick-xml/issues/497 -//! [`text_content`]: crate::serde_helpers::text_content +//! [`Serializer::serialize_unit_variant`]: serde::Serializer::serialize_unit_variant +//! [`Deserializer::deserialize_enum`]: serde::Deserializer::deserialize_enum //! [Tagged enums]: https://serde.rs/enum-representations.html#internally-tagged //! [serde#1183]: https://github.com/serde-rs/serde/issues/1183 //! [serde#1495]: https://github.com/serde-rs/serde/issues/1495 diff --git a/src/se/content.rs b/src/se/content.rs index 376f55ac..984689f0 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -1,5 +1,6 @@ //! Contains serializer for content of an XML element +use crate::de::TEXT_KEY; use crate::errors::serialize::DeError; use crate::se::element::{ElementSerializer, Struct, Tuple}; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; @@ -22,19 +23,32 @@ macro_rules! write_primitive { //////////////////////////////////////////////////////////////////////////////////////////////////// -/// A serializer used to serialize content of the element. It does not write -/// surrounding tags. +/// A serializer used to serialize content of an element. It does not write +/// surrounding tags. Unlike the [`ElementSerializer`], this serializer serializes +/// enums using variant names as tag names, i. e. as `...` /// /// This serializer does the following: -/// - primitives (booleans, numbers and strings) serialized as naked strings -/// - `None` does not write anything -/// - sequences serialized without delimiters. `[1, 2, 3]` would be serialized as `123` -/// - units (`()`) and unit structs are not supported -/// - structs and maps are not supported -/// - unit variants serialized as self-closed `<${variant}/>` -/// - tuple variants serialized as sequences where each is wrapped in -/// `<${variant}>...` -/// - struct variants serialized wrapped `<${variant}>...` +/// - numbers converted to a decimal representation and serialized as naked strings; +/// - booleans serialized ether as `"true"` or `"false"`; +/// - strings and characters are serialized as naked strings; +/// - `None` does not write anything; +/// - `Some` and newtypes are serialized as an inner type using the same serializer; +/// - units (`()`) and unit structs does not write anything; +/// - sequences, tuples and tuple structs are serialized without delimiters. +/// `[1, 2, 3]` would be serialized as `123` (if not using indent); +/// - structs and maps are not supported ([`DeError::Unsupported`] is returned); +/// - enums: +/// - unit variants are serialized as self-closed ``; +/// - newtype variants are serialized as inner value wrapped in `...`; +/// - tuple variants are serialized as sequences where each element is wrapped +/// in `...`; +/// - struct variants are serialized as a sequence of fields wrapped in +/// `...`. Each field is serialized recursively using +/// either [`ElementSerializer`], `ContentSerializer` (`$value` fields), or +/// [`SimpleTypeSerializer`] (`$text` fields). In particular, the empty struct +/// is serialized as ``; +/// +/// Usage of empty tags depends on the [`Self::expand_empty_elements`] setting. /// /// The difference between this serializer and [`SimpleTypeSerializer`] is in how /// sequences and maps are serialized. Unlike `SimpleTypeSerializer` it supports @@ -202,15 +216,20 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { Ok(()) } - /// Checks `variant` for XML name validity and writes `<${variant}/>` + /// If `variant` is a special `$text` variant, then do nothing, otherwise + /// checks `variant` for XML name validity and writes ``. fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { - let name = XmlName::try_from(variant)?; - self.write_empty(name) + if variant == TEXT_KEY { + Ok(()) + } else { + let name = XmlName::try_from(variant)?; + self.write_empty(name) + } } fn serialize_newtype_struct( @@ -221,8 +240,9 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { value.serialize(self) } - /// Checks `variant` for XML name validity and writes `value` as new element - /// with name `variant`. + /// If `variant` is a special `$text` variant, then writes `value` as a `xs:simpleType`, + /// otherwise checks `variant` for XML name validity and writes `value` as a new + /// `` element. fn serialize_newtype_variant( self, _name: &'static str, @@ -230,10 +250,15 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { variant: &'static str, value: &T, ) -> Result { - value.serialize(ElementSerializer { - key: XmlName::try_from(variant)?, - ser: self, - }) + if variant == TEXT_KEY { + value.serialize(self.into_simple_type_serializer())?; + Ok(()) + } else { + value.serialize(ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }) + } } #[inline] @@ -255,6 +280,14 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { self.serialize_tuple(len) } + /// Serializes variant as a tuple with name `variant`, producing + /// + /// ```xml + /// + /// + /// + /// + /// ``` #[inline] fn serialize_tuple_variant( self, @@ -263,14 +296,17 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { variant: &'static str, len: usize, ) -> Result { - let ser = ElementSerializer { - key: XmlName::try_from(variant)?, - ser: self, - }; - // `ElementSerializer::serialize_tuple_variant` is the same as - // `ElementSerializer::serialize_tuple_struct`, except that it replaces `.key` - // to `variant` which is not required here - ser.serialize_tuple_struct(name, len).map(Tuple::Element) + if variant == TEXT_KEY { + self.into_simple_type_serializer() + .serialize_tuple_struct(name, len) + .map(Tuple::Text) + } else { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + ser.serialize_tuple_struct(name, len).map(Tuple::Element) + } } fn serialize_map(self, _len: Option) -> Result { @@ -290,6 +326,16 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { )) } + /// Serializes variant as an element with name `variant`, producing + /// + /// ```xml + /// + /// + /// + /// ``` + /// + /// If struct has no fields which is represented by nested elements or a text, + /// it may be serialized as self-closed element ``. #[inline] fn serialize_struct_variant( self, @@ -298,14 +344,17 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { variant: &'static str, len: usize, ) -> Result { - let ser = ElementSerializer { - key: XmlName::try_from(variant)?, - ser: self, - }; - // `ElementSerializer::serialize_struct_variant` is the same as - // `ElementSerializer::serialize_struct`, except that it replaces `.key` - // to `variant` which is not required here - ser.serialize_struct(name, len) + if variant == TEXT_KEY { + Err(DeError::Unsupported( + format!("cannot serialize `$text` struct variant of `{}` enum", name).into(), + )) + } else { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + ser.serialize_struct(name, len) + } } } @@ -394,6 +443,7 @@ pub(super) mod tests { pub val: (usize, usize), } + /// Struct with a special `$text` field #[derive(Debug, Serialize, PartialEq)] pub struct Text { pub before: &'static str, @@ -402,6 +452,7 @@ pub(super) mod tests { pub after: &'static str, } + /// Struct with a special `$value` field #[derive(Debug, Serialize, PartialEq)] pub struct Value { pub before: &'static str, @@ -465,12 +516,14 @@ pub(super) mod tests { #[derive(Debug, Serialize, PartialEq)] pub enum SpecialEnum { + /// Struct variant with a special `$text` field Text { before: &'static str, #[serde(rename = "$text")] content: T, after: &'static str, }, + /// Struct variant with a special `$value` field Value { before: &'static str, #[serde(rename = "$value")] @@ -486,7 +539,7 @@ pub(super) mod tests { /// Checks that given `$data` successfully serialized as `$expected` macro_rules! serialize_as { - ($name:ident: $data:expr => $expected:literal) => { + ($name:ident: $data:expr => $expected:expr) => { #[test] fn $name() { let mut buffer = String::new(); @@ -618,7 +671,7 @@ pub(super) mod tests { "); /// Special field name `$text` should be serialized as a text content - mod text { + mod text_field { use super::*; use pretty_assertions::assert_eq; @@ -644,6 +697,255 @@ pub(super) mod tests { "); } + /// `$text` field inside a struct variant of an enum + mod enum_with_text_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None => ""); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("") => ""); + + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + SpecialEnum::Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new() => ""); + text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + SpecialEnum::Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + SpecialEnum::Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("cannot serialize map as text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); + } + + /// `$value` field inside a struct variant of an enum + mod enum_with_value_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None => ""); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("") => ""); + + value!(unit: () => ""); + value!(unit_struct: Unit => ""); + value!(unit_struct_escaped: UnitEscaped => ""); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + SpecialEnum::Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); + value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\ + with\t\n\r spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + SpecialEnum::Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + SpecialEnum::Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + mod attributes { use super::*; use pretty_assertions::assert_eq; @@ -677,7 +979,7 @@ pub(super) mod tests { /// Checks that given `$data` successfully serialized as `$expected` macro_rules! serialize_as { - ($name:ident: $data:expr => $expected:literal) => { + ($name:ident: $data:expr => $expected:expr) => { #[test] fn $name() { let mut buffer = String::new(); @@ -810,7 +1112,7 @@ pub(super) mod tests { "); /// Special field name `$text` should be serialized as text content - mod text { + mod text_field { use super::*; use pretty_assertions::assert_eq; @@ -836,6 +1138,279 @@ pub(super) mod tests { "); } + /// `$text` field inside a struct variant of an enum + mod enum_with_text_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + SpecialEnum::Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + SpecialEnum::Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + SpecialEnum::Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("cannot serialize map as text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); + } + + /// `$value` field inside a struct variant of an enum + mod enum_with_value_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + SpecialEnum::Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\n \ + with\t\n\r spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + SpecialEnum::Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + SpecialEnum::Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + mod attributes { use super::*; use pretty_assertions::assert_eq; diff --git a/src/se/element.rs b/src/se/element.rs index f5a3e187..afad2a97 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -5,14 +5,16 @@ use crate::errors::serialize::DeError; use crate::se::content::ContentSerializer; use crate::se::key::QNameSerializer; use crate::se::simple_type::{QuoteTarget, SimpleSeq, SimpleTypeSerializer}; +use crate::se::text::TextSerializer; use crate::se::{Indent, XmlName}; use serde::ser::{ - Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, - SerializeTupleStruct, SerializeTupleVariant, Serializer, + Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, + SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, }; use serde::serde_if_integer128; use std::fmt::Write; +/// Writes simple type content between [`ElementSerializer::key`] tags. macro_rules! write_primitive { ($method:ident ( $ty:ty )) => { fn $method(self, value: $ty) -> Result { @@ -23,8 +25,39 @@ macro_rules! write_primitive { //////////////////////////////////////////////////////////////////////////////////////////////////// -/// A serializer used to serialize element with specified name. +/// A serializer used to serialize element with specified name. Unlike the [`ContentSerializer`], +/// this serializer never uses variant names of enum variants, and because of that +/// it is unable to serialize any enum values, except unit variants. +/// +/// This serializer is used for an ordinary fields in structs, which are not special +/// fields named `$text` ([`TEXT_KEY`]) or `$value` ([`VALUE_KEY`]). `$text` field +/// should be serialized using [`SimpleTypeSerializer`] and `$value` field should be +/// serialized using [`ContentSerializer`]. +/// +/// This serializer does the following: +/// - numbers converted to a decimal representation and serialized as `value`; +/// - booleans serialized ether as `true` or `false`; +/// - strings and characters are serialized as `value`. In particular, +/// an empty string is serialized as ``; +/// - `None` is serialized as ``; +/// - `Some` and newtypes are serialized as an inner type using the same serializer; +/// - units (`()`) and unit structs are serialized as ``; +/// - sequences, tuples and tuple structs are serialized as repeated `` tag. +/// In particular, empty sequence is serialized to nothing; +/// - structs are serialized as a sequence of fields wrapped in a `` tag. Each +/// field is serialized recursively using either `ElementSerializer`, [`ContentSerializer`] +/// (`$value` fields), or [`SimpleTypeSerializer`] (`$text` fields). +/// In particular, the empty struct is serialized as ``; +/// - maps are serialized as a sequence of entries wrapped in a `` tag. If key is +/// serialized to a special name, the same rules as for struct fields are applied. +/// In particular, the empty map is serialized as ``; +/// - enums: +/// - unit variants are serialized as `variant`; +/// - other variants are not supported ([`DeError::Unsupported`] is returned); +/// +/// Usage of empty tags depends on the [`ContentSerializer::expand_empty_elements`] setting. pub struct ElementSerializer<'w, 'k, W: Write> { + /// The inner serializer that contains the settings and mostly do the actual work pub ser: ContentSerializer<'w, 'k, W>, /// Tag name used to wrap serialized types except enum variants which uses the variant name pub(super) key: XmlName<'k>, @@ -37,7 +70,7 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { type SerializeSeq = Self; type SerializeTuple = Self; type SerializeTupleStruct = Self; - type SerializeTupleVariant = Tuple<'w, 'k, W>; + type SerializeTupleVariant = Impossible; type SerializeMap = Map<'w, 'k, W>; type SerializeStruct = Struct<'w, 'k, W>; type SerializeStructVariant = Struct<'w, 'k, W>; @@ -103,24 +136,21 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { self.ser.write_empty(self.key) } + /// Writes a tag with name [`Self::key`] and content of unit variant inside. + /// If variant is a special `$text` value, then empty tag `` is written. + /// Otherwise a `variant` is written. fn serialize_unit_variant( self, name: &'static str, - _variant_index: u32, + variant_index: u32, variant: &'static str, ) -> Result { if variant == TEXT_KEY { - // We should write some text but we don't known what text to write - Err(DeError::Unsupported( - format!( - "cannot serialize enum unit variant `{}::$text` as text content value", - name - ) - .into(), - )) + self.ser.write_empty(self.key) } else { - let name = XmlName::try_from(variant)?; - self.ser.write_empty(name) + self.ser.write_wrapped(self.key, |ser| { + ser.serialize_unit_variant(name, variant_index, variant) + }) } } @@ -132,20 +162,23 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { value.serialize(self) } + /// Always returns [`DeError::Unsupported`]. Newtype variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. + #[inline] fn serialize_newtype_variant( - mut self, - _name: &'static str, + self, + name: &'static str, _variant_index: u32, variant: &'static str, - value: &T, + _value: &T, ) -> Result { - if variant == TEXT_KEY { - value.serialize(self.ser.into_simple_type_serializer())?; - Ok(()) - } else { - self.key = XmlName::try_from(variant)?; - value.serialize(self) - } + Err(DeError::Unsupported( + format!( + "cannot serialize enum newtype variant `{}::{}`", + name, variant + ) + .into(), + )) } #[inline] @@ -167,23 +200,23 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { self.serialize_tuple(len) } + /// Always returns [`DeError::Unsupported`]. Tuple variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. #[inline] fn serialize_tuple_variant( - mut self, + self, name: &'static str, _variant_index: u32, variant: &'static str, - len: usize, + _len: usize, ) -> Result { - if variant == TEXT_KEY { - self.ser - .into_simple_type_serializer() - .serialize_tuple_struct(name, len) - .map(Tuple::Text) - } else { - self.key = XmlName::try_from(variant)?; - self.serialize_tuple_struct(name, len).map(Tuple::Element) - } + Err(DeError::Unsupported( + format!( + "cannot serialize enum tuple variant `{}::{}`", + name, variant + ) + .into(), + )) } fn serialize_map(self, _len: Option) -> Result { @@ -210,26 +243,23 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { }) } + /// Always returns [`DeError::Unsupported`]. Struct variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. #[inline] fn serialize_struct_variant( - mut self, + self, name: &'static str, _variant_index: u32, variant: &'static str, - len: usize, + _len: usize, ) -> Result { - if variant == TEXT_KEY { - Err(DeError::Unsupported( - format!( - "cannot serialize enum struct variant `{}::$text` as text content value", - name - ) - .into(), - )) - } else { - self.key = XmlName::try_from(variant)?; - self.serialize_struct(name, len) - } + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::{}`", + name, variant + ) + .into(), + )) } } @@ -245,7 +275,7 @@ impl<'w, 'k, W: Write> SerializeSeq for ElementSerializer<'w, 'k, W> { ser: self.ser.new_seq_element_serializer(), key: self.key, })?; - // Write indent for next element + // Write indent for the next element self.ser.write_indent = true; Ok(()) } @@ -409,7 +439,7 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> { }; if key == TEXT_KEY { - value.serialize(ser.into_simple_type_serializer())?; + value.serialize(TextSerializer(ser.into_simple_type_serializer()))?; } else if key == VALUE_KEY { value.serialize(ser)?; } else { @@ -670,12 +700,12 @@ mod tests { serialize_as!(unit_struct: Unit => ""); serialize_as!(unit_struct_escaped: UnitEscaped => ""); - serialize_as!(enum_unit: Enum::Unit => ""); - err!(enum_unit_escaped: Enum::UnitEscaped - => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); serialize_as!(newtype: Newtype(42) => "42"); - serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype`")); serialize_as!(seq: vec![1, 2, 3] => "1\ @@ -689,9 +719,8 @@ mod tests { serialize_as!(tuple_struct: Tuple("first", 42) => "first\ 42"); - serialize_as!(enum_tuple: Enum::Tuple("first", 42) - => "first\ - 42"); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple`")); serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) => "\ @@ -704,16 +733,12 @@ mod tests { 42\ 42\ "); - serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } - => "\ - answer\ - 42\ - 42\ - "); + err!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Struct`")); /// Special field name `$text` should be serialized as text content. /// Sequences serialized as an `xs:list` content - mod text { + mod text_field { use super::*; /// `$text` key in a map @@ -797,7 +822,7 @@ mod tests { content: Enum::Newtype(42), after: "answer", } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); @@ -814,7 +839,7 @@ mod tests { content: Enum::Tuple("first", 42), after: "answer", } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); // Complex types cannot be serialized in `$text` field err!(map: @@ -823,21 +848,21 @@ mod tests { content: BTreeMap::from([("_1", 2), ("_3", 4)]), after: "answer", } - => Unsupported("cannot serialize map as an attribute or text content value")); + => Unsupported("cannot serialize map as text content value")); err!(struct_: Text { before: "answer", content: Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); + => Unsupported("cannot serialize struct `Struct` as text content value")); err!(enum_struct: Text { before: "answer", content: Enum::Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } /// `$text` field inside a struct @@ -924,7 +949,7 @@ mod tests { content: Enum::Newtype(42), after: "answer", } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); @@ -941,7 +966,7 @@ mod tests { content: Enum::Tuple("first", 42), after: "answer", } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); // Complex types cannot be serialized in `$text` field err!(map: @@ -950,155 +975,28 @@ mod tests { content: BTreeMap::from([("_1", 2), ("_3", 4)]), after: "answer", } - => Unsupported("cannot serialize map as an attribute or text content value")); + => Unsupported("cannot serialize map as text content value")); err!(struct_: Text { before: "answer", content: Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); + => Unsupported("cannot serialize struct `Struct` as text content value")); err!(enum_struct: Text { before: "answer", content: Enum::Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); - } - - /// `$text` field inside a struct variant of an enum - mod enum_struct { - use super::*; - use pretty_assertions::assert_eq; - - macro_rules! text { - ($name:ident: $data:expr => $expected:literal) => { - serialize_as!($name: - SpecialEnum::Text { - before: "answer", - content: $data, - after: "answer", - } - => concat!( - "answer", - $expected, - "answer", - )); - }; - } - - text!(false_: false => "false"); - text!(true_: true => "true"); - - text!(i8_: -42i8 => "-42"); - text!(i16_: -4200i16 => "-4200"); - text!(i32_: -42000000i32 => "-42000000"); - text!(i64_: -42000000000000i64 => "-42000000000000"); - text!(isize_: -42000000000000isize => "-42000000000000"); - - text!(u8_: 42u8 => "42"); - text!(u16_: 4200u16 => "4200"); - text!(u32_: 42000000u32 => "42000000"); - text!(u64_: 42000000000000u64 => "42000000000000"); - text!(usize_: 42000000000000usize => "42000000000000"); - - serde_if_integer128! { - text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); - } - - text!(f32_: 4.2f32 => "4.2"); - text!(f64_: 4.2f64 => "4.2"); - - text!(char_non_escaped: 'h' => "h"); - text!(char_lt: '<' => "<"); - text!(char_gt: '>' => ">"); - text!(char_amp: '&' => "&"); - text!(char_apos: '\'' => "'"); - text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content - text!(char_space: ' ' => " "); - - text!(str_non_escaped: "non-escaped string" => "non-escaped string"); - text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); - - err!(bytes: - SpecialEnum::Text { - before: "answer", - content: Bytes(b"<\"escaped & bytes'>"), - after: "answer", - } - => Unsupported("`serialize_bytes` not supported yet")); - - text!(option_none: Option::<&str>::None => ""); - text!(option_some: Some("non-escaped string") => "non-escaped string"); - text!(option_some_empty_str: Some("") => ""); - - text!(unit: () => ""); - text!(unit_struct: Unit => ""); - text!(unit_struct_escaped: UnitEscaped => ""); - - text!(enum_unit: Enum::Unit => "Unit"); - text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); - - text!(newtype: Newtype(42) => "42"); - // We have no space where name of a variant can be stored - err!(enum_newtype: - SpecialEnum::Text { - before: "answer", - content: Enum::Newtype(42), - after: "answer", - } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); - - // Sequences are serialized separated by spaces, all spaces inside are escaped - text!(seq: vec![1, 2, 3] => "1 2 3"); - text!(seq_empty: Vec::::new() => ""); - text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'> \ - with spaces \ - 3"); - text!(tuple_struct: Tuple("first", 42) => "first 42"); - // We have no space where name of a variant can be stored - err!(enum_tuple: - SpecialEnum::Text { - before: "answer", - content: Enum::Tuple("first", 42), - after: "answer", - } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); - - // Complex types cannot be serialized in `$text` field - err!(map: - SpecialEnum::Text { - before: "answer", - content: BTreeMap::from([("_1", 2), ("_3", 4)]), - after: "answer", - } - => Unsupported("cannot serialize map as an attribute or text content value")); - err!(struct_: - SpecialEnum::Text { - before: "answer", - content: Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); - err!(enum_struct: - SpecialEnum::Text { - before: "answer", - content: Enum::Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } } /// Special field name `$value` should be serialized using name, provided /// by the type of value instead of a key. Sequences serialized as a list /// of tags with that name (each element can have their own name) - mod value { + mod value_field { use super::*; /// `$value` key in a map @@ -1325,180 +1223,54 @@ mod tests { 42\ "); } + } - /// `$value` field inside a struct variant of an enum - mod enum_struct { - use super::*; - use pretty_assertions::assert_eq; - - macro_rules! value { - ($name:ident: $data:expr => $expected:literal) => { - serialize_as!($name: - SpecialEnum::Value { - before: "answer", - content: $data, - after: "answer", - } - => concat!( - "answer", - $expected, - "answer", - )); - }; - } - - value!(false_: false => "false"); - value!(true_: true => "true"); - - value!(i8_: -42i8 => "-42"); - value!(i16_: -4200i16 => "-4200"); - value!(i32_: -42000000i32 => "-42000000"); - value!(i64_: -42000000000000i64 => "-42000000000000"); - value!(isize_: -42000000000000isize => "-42000000000000"); - - value!(u8_: 42u8 => "42"); - value!(u16_: 4200u16 => "4200"); - value!(u32_: 42000000u32 => "42000000"); - value!(u64_: 42000000000000u64 => "42000000000000"); - value!(usize_: 42000000000000usize => "42000000000000"); + mod attributes { + use super::*; + use pretty_assertions::assert_eq; - serde_if_integer128! { - value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); - } + serialize_as!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => r#""#); + serialize_as!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => r#"2"#); - value!(f32_: 4.2f32 => "4.2"); - value!(f64_: 4.2f64 => "4.2"); + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); - value!(char_non_escaped: 'h' => "h"); - value!(char_lt: '<' => "<"); - value!(char_gt: '>' => ">"); - value!(char_amp: '&' => "&"); - value!(char_apos: '\'' => "'"); - value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content - value!(char_space: ' ' => " "); + err!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Attributes`")); - value!(str_non_escaped: "non-escaped string" => "non-escaped string"); - value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; - err!(bytes: - SpecialEnum::Value { - before: "answer", - content: Bytes(b"<\"escaped & bytes'>"), - after: "answer", + serialize_as!(none: + OptionalAttributes { a: None, b: None } + => r#""#); + serialize_as!(some_empty_str: + OptionalAttributes { + a: Some(""), + b: Some(""), } - => Unsupported("`serialize_bytes` not supported yet")); - - value!(option_none: Option::<&str>::None => ""); - value!(option_some: Some("non-escaped string") => "non-escaped string"); - value!(option_some_empty_str: Some("") => ""); - - value!(unit: () => ""); - value!(unit_struct: Unit => ""); - value!(unit_struct_escaped: UnitEscaped => ""); - - value!(enum_unit: Enum::Unit => ""); - err!(enum_unit_escaped: - SpecialEnum::Value { - before: "answer", - content: Enum::UnitEscaped, - after: "answer", + => r#""#); + serialize_as!(some_non_empty: + OptionalAttributes { + a: Some("1"), + b: Some("2"), } - => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + => r#""#); + } + } - value!(newtype: Newtype(42) => "42"); - value!(enum_newtype: Enum::Newtype(42) => "42"); - - // Note that sequences of primitives serialized without delimiters! - value!(seq: vec![1, 2, 3] => "123"); - value!(seq_empty: Vec::::new() => ""); - value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'>\ - with\t\n\r spaces\ - 3"); - value!(tuple_struct: Tuple("first", 42) => "first42"); - value!(enum_tuple: Enum::Tuple("first", 42) - => "first\ - 42"); - - // We cannot wrap map or struct in any container and should not - // flatten it, so it is impossible to serialize maps and structs - err!(map: - SpecialEnum::Value { - before: "answer", - content: BTreeMap::from([("_1", 2), ("_3", 4)]), - after: "answer", - } - => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: - SpecialEnum::Value { - before: "answer", - content: Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); - value!(enum_struct: - Enum::Struct { key: "answer", val: (42, 42) } - => "\ - answer\ - 42\ - 42\ - "); - } - } - - mod attributes { - use super::*; - use pretty_assertions::assert_eq; - - serialize_as!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) - => r#""#); - serialize_as!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) - => r#"2"#); - - serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } - => r#""#); - serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } - => r#"42"#); - serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } - => r#"answer"#); - - serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } - => r#""#); - serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } - => r#"42"#); - serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } - => r#"answer"#); - - /// Test for https://github.com/tafia/quick-xml/issues/252 - mod optional { - use super::*; - use pretty_assertions::assert_eq; - - serialize_as!(none: - OptionalAttributes { a: None, b: None } - => r#""#); - serialize_as!(some_empty_str: - OptionalAttributes { - a: Some(""), - b: Some(""), - } - => r#""#); - serialize_as!(some_non_empty: - OptionalAttributes { - a: Some("1"), - b: Some("2"), - } - => r#""#); - } - } - - /// Test for https://github.com/tafia/quick-xml/issues/252 - mod optional { - use super::*; - use pretty_assertions::assert_eq; + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; serialize_as!(none: OptionalElements { a: None, b: None } @@ -1634,12 +1406,12 @@ mod tests { serialize_as!(unit_struct: Unit => ""); serialize_as!(unit_struct_escaped: UnitEscaped => ""); - serialize_as!(enum_unit: Enum::Unit => ""); - err!(enum_unit_escaped: Enum::UnitEscaped - => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); serialize_as!(newtype: Newtype(42) => "42"); - serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype`")); serialize_as!(seq: vec![1, 2, 3] => "1\n\ @@ -1653,9 +1425,8 @@ mod tests { serialize_as!(tuple_struct: Tuple("first", 42) => "first\n\ 42"); - serialize_as!(enum_tuple: Enum::Tuple("first", 42) - => "first\n\ - 42"); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple`")); serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) => "\n \ @@ -1668,16 +1439,12 @@ mod tests { 42\n \ 42\n\ "); - serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } - => "\n \ - answer\n \ - 42\n \ - 42\n\ - "); + err!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Struct`")); /// Special field name `$text` should be serialized as text content. /// Sequences serialized as an `xs:list` content - mod text { + mod text_field { use super::*; /// `$text` key in a map @@ -1761,7 +1528,7 @@ mod tests { content: Enum::Newtype(42), after: "answer", } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); @@ -1778,7 +1545,7 @@ mod tests { content: Enum::Tuple("first", 42), after: "answer", } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); // Complex types cannot be serialized in `$text` field err!(map: @@ -1787,21 +1554,21 @@ mod tests { content: BTreeMap::from([("_1", 2), ("_3", 4)]), after: "answer", } - => Unsupported("cannot serialize map as an attribute or text content value")); + => Unsupported("cannot serialize map as text content value")); err!(struct_: Text { before: "answer", content: Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); + => Unsupported("cannot serialize struct `Struct` as text content value")); err!(enum_struct: Text { before: "answer", content: Enum::Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } /// `$text` field inside a struct @@ -1900,7 +1667,7 @@ mod tests { content: Enum::Newtype(42), after: "answer", } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as text content value")); // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); @@ -1917,7 +1684,7 @@ mod tests { content: Enum::Tuple("first", 42), after: "answer", } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as text content value")); // Complex types cannot be serialized in `$text` field err!(map: @@ -1926,167 +1693,28 @@ mod tests { content: BTreeMap::from([("_1", 2), ("_3", 4)]), after: "answer", } - => Unsupported("cannot serialize map as an attribute or text content value")); + => Unsupported("cannot serialize map as text content value")); err!(struct_: Text { before: "answer", content: Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); + => Unsupported("cannot serialize struct `Struct` as text content value")); err!(enum_struct: Text { before: "answer", content: Enum::Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); - } - - /// `$text` field inside a struct variant of an enum - mod enum_struct { - use super::*; - use pretty_assertions::assert_eq; - - macro_rules! text { - ($name:ident: $data:expr) => { - serialize_as!($name: - SpecialEnum::Text { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; - ($name:ident: $data:expr => $expected:literal) => { - serialize_as!($name: - SpecialEnum::Text { - before: "answer", - content: $data, - after: "answer", - } - => concat!( - "\n answer\n ", - $expected, - "\n answer\n", - )); - }; - } - - text!(false_: false => "false"); - text!(true_: true => "true"); - - text!(i8_: -42i8 => "-42"); - text!(i16_: -4200i16 => "-4200"); - text!(i32_: -42000000i32 => "-42000000"); - text!(i64_: -42000000000000i64 => "-42000000000000"); - text!(isize_: -42000000000000isize => "-42000000000000"); - - text!(u8_: 42u8 => "42"); - text!(u16_: 4200u16 => "4200"); - text!(u32_: 42000000u32 => "42000000"); - text!(u64_: 42000000000000u64 => "42000000000000"); - text!(usize_: 42000000000000usize => "42000000000000"); - - serde_if_integer128! { - text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); - } - - text!(f32_: 4.2f32 => "4.2"); - text!(f64_: 4.2f64 => "4.2"); - - text!(char_non_escaped: 'h' => "h"); - text!(char_lt: '<' => "<"); - text!(char_gt: '>' => ">"); - text!(char_amp: '&' => "&"); - text!(char_apos: '\'' => "'"); - text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content - text!(char_space: ' ' => " "); - - text!(str_non_escaped: "non-escaped string" => "non-escaped string"); - text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); - - err!(bytes: - SpecialEnum::Text { - before: "answer", - content: Bytes(b"<\"escaped & bytes'>"), - after: "answer", - } - => Unsupported("`serialize_bytes` not supported yet")); - - text!(option_none: Option::<&str>::None); - text!(option_some: Some("non-escaped string") => "non-escaped string"); - text!(option_some_empty_str: Some("")); - - text!(unit: ()); - text!(unit_struct: Unit); - text!(unit_struct_escaped: UnitEscaped); - - text!(enum_unit: Enum::Unit => "Unit"); - text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); - - text!(newtype: Newtype(42) => "42"); - // We have no space where name of a variant can be stored - err!(enum_newtype: - SpecialEnum::Text { - before: "answer", - content: Enum::Newtype(42), - after: "answer", - } - => Unsupported("cannot serialize enum newtype variant `Enum::Newtype` as an attribute or text content value")); - - // Sequences are serialized separated by spaces, all spaces inside are escaped - text!(seq: vec![1, 2, 3] => "1 2 3"); - text!(seq_empty: Vec::::new()); - text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'> \ - with spaces \ - 3"); - text!(tuple_struct: Tuple("first", 42) => "first 42"); - // We have no space where name of a variant can be stored - err!(enum_tuple: - SpecialEnum::Text { - before: "answer", - content: Enum::Tuple("first", 42), - after: "answer", - } - => Unsupported("cannot serialize enum tuple variant `Enum::Tuple` as an attribute or text content value")); - - // Complex types cannot be serialized in `$text` field - err!(map: - SpecialEnum::Text { - before: "answer", - content: BTreeMap::from([("_1", 2), ("_3", 4)]), - after: "answer", - } - => Unsupported("cannot serialize map as an attribute or text content value")); - err!(struct_: - SpecialEnum::Text { - before: "answer", - content: Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); - err!(enum_struct: - SpecialEnum::Text { - before: "answer", - content: Enum::Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } } /// Special field name `$value` should be serialized using name, provided /// by the type of value instead of a key. Sequences serialized as a list /// of tags with that name (each element can have their own name) - mod value { + mod value_field { use super::*; /// `$value` key in a map @@ -2324,140 +1952,6 @@ mod tests { 42\n \ "); } - - /// `$value` field inside a struct variant of an enum - mod enum_struct { - use super::*; - use pretty_assertions::assert_eq; - - macro_rules! value { - ($name:ident: $data:expr) => { - serialize_as!($name: - SpecialEnum::Value { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; - ($name:ident: $data:expr => $expected:literal) => { - serialize_as!($name: - SpecialEnum::Value { - before: "answer", - content: $data, - after: "answer", - } - => concat!( - "\n answer\n ", - $expected, - "\n answer\n", - )); - }; - } - - value!(false_: false => "false"); - value!(true_: true => "true"); - - value!(i8_: -42i8 => "-42"); - value!(i16_: -4200i16 => "-4200"); - value!(i32_: -42000000i32 => "-42000000"); - value!(i64_: -42000000000000i64 => "-42000000000000"); - value!(isize_: -42000000000000isize => "-42000000000000"); - - value!(u8_: 42u8 => "42"); - value!(u16_: 4200u16 => "4200"); - value!(u32_: 42000000u32 => "42000000"); - value!(u64_: 42000000000000u64 => "42000000000000"); - value!(usize_: 42000000000000usize => "42000000000000"); - - serde_if_integer128! { - value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); - } - - value!(f32_: 4.2f32 => "4.2"); - value!(f64_: 4.2f64 => "4.2"); - - value!(char_non_escaped: 'h' => "h"); - value!(char_lt: '<' => "<"); - value!(char_gt: '>' => ">"); - value!(char_amp: '&' => "&"); - value!(char_apos: '\'' => "'"); - value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content - value!(char_space: ' ' => " "); - - value!(str_non_escaped: "non-escaped string" => "non-escaped string"); - value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); - - err!(bytes: - SpecialEnum::Value { - before: "answer", - content: Bytes(b"<\"escaped & bytes'>"), - after: "answer", - } - => Unsupported("`serialize_bytes` not supported yet")); - - value!(option_none: Option::<&str>::None); - value!(option_some: Some("non-escaped string") => "non-escaped string"); - value!(option_some_empty_str: Some("")); - - value!(unit: ()); - value!(unit_struct: Unit); - value!(unit_struct_escaped: UnitEscaped); - - value!(enum_unit: Enum::Unit => ""); - err!(enum_unit_escaped: - SpecialEnum::Value { - before: "answer", - content: Enum::UnitEscaped, - after: "answer", - } - => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); - - value!(newtype: Newtype(42) => "42"); - value!(enum_newtype: Enum::Newtype(42) => "42"); - - // Note that sequences of primitives serialized without delimiters! - value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); - value!(seq_empty: Vec::::new()); - value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'>\n \ - with\t\n\r spaces\n \ - 3"); - value!(tuple_struct: Tuple("first", 42) => "first\n 42"); - value!(enum_tuple: Enum::Tuple("first", 42) - => "first\n \ - 42"); - - // We cannot wrap map or struct in any container and should not - // flatten it, so it is impossible to serialize maps and structs - err!(map: - SpecialEnum::Value { - before: "answer", - content: BTreeMap::from([("_1", 2), ("_3", 4)]), - after: "answer", - } - => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: - SpecialEnum::Value { - before: "answer", - content: Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); - value!(enum_struct: - Enum::Struct { key: "answer", val: (42, 42) } - => "\n \ - answer\n \ - 42\n \ - 42\n \ - "); - } } mod attributes { @@ -2482,16 +1976,8 @@ mod tests { answer\n\ "); - serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } - => r#""#); - serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } - => "\n \ - 42\n\ - "); - serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } - => "\n \ - answer\n\ - "); + err!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Attributes`")); /// Test for https://github.com/tafia/quick-xml/issues/252 mod optional { @@ -2574,39 +2060,6 @@ mod tests { }; } - /// Checks that attempt to serialize given `$data` results to a - /// serialization error `$kind` with `$reason` - macro_rules! err { - ($name:ident: $data:expr => $kind:ident($reason:literal)) => { - #[test] - fn $name() { - let mut buffer = String::new(); - let ser = ElementSerializer { - ser: ContentSerializer { - writer: &mut buffer, - level: QuoteLevel::Full, - indent: Indent::None, - write_indent: false, - expand_empty_elements: false, - }, - key: XmlName("root"), - }; - - match $data.serialize(ser).unwrap_err() { - DeError::$kind(e) => assert_eq!(e, $reason), - e => panic!( - "Expected `{}({})`, found `{:?}`", - stringify!($kind), - $reason, - e - ), - } - // We can write something before fail - // assert_eq!(buffer, ""); - } - }; - } - serialize_as!(option_some_empty: Some("") => ""); serialize_as!(option_some_empty_str: Some("") => ""); @@ -2614,8 +2067,7 @@ mod tests { serialize_as!(unit_struct: Unit => ""); serialize_as!(unit_struct_escaped: UnitEscaped => ""); - serialize_as!(enum_unit: Enum::Unit => ""); - err!(enum_unit_escaped: Enum::UnitEscaped - => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); } } diff --git a/src/se/mod.rs b/src/se/mod.rs index bbd3ce2e..839187f3 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -77,9 +77,11 @@ mod content; mod element; pub(crate) mod key; pub(crate) mod simple_type; +mod text; use self::content::ContentSerializer; -use self::element::ElementSerializer; +use self::element::{ElementSerializer, Map, Struct, Tuple}; +use crate::de::TEXT_KEY; use crate::errors::serialize::DeError; use crate::writer::Indentation; use serde::ser::{self, Serialize}; @@ -608,16 +610,13 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { type Ok = (); type Error = DeError; - type SerializeSeq = as ser::Serializer>::SerializeSeq; - type SerializeTuple = as ser::Serializer>::SerializeTuple; - type SerializeTupleStruct = - as ser::Serializer>::SerializeTupleStruct; - type SerializeTupleVariant = - as ser::Serializer>::SerializeTupleVariant; - type SerializeMap = as ser::Serializer>::SerializeMap; - type SerializeStruct = as ser::Serializer>::SerializeStruct; - type SerializeStructVariant = - as ser::Serializer>::SerializeStructVariant; + type SerializeSeq = ElementSerializer<'w, 'r, W>; + type SerializeTuple = ElementSerializer<'w, 'r, W>; + type SerializeTupleStruct = ElementSerializer<'w, 'r, W>; + type SerializeTupleVariant = Tuple<'w, 'r, W>; + type SerializeMap = Map<'w, 'r, W>; + type SerializeStruct = Struct<'w, 'r, W>; + type SerializeStructVariant = Struct<'w, 'r, W>; forward!(serialize_bool(bool)); @@ -662,11 +661,22 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_unit_variant( self, name: &'static str, - variant_index: u32, + _variant_index: u32, variant: &'static str, ) -> Result { - self.ser_name(name)? - .serialize_unit_variant(name, variant_index, variant) + if variant == TEXT_KEY { + // We should write some text but we don't known what text to write + Err(DeError::Unsupported( + format!( + "cannot serialize enum unit variant `{}::$text` as text content value", + name + ) + .into(), + )) + } else { + let name = XmlName::try_from(variant)?; + self.ser.write_empty(name) + } } fn serialize_newtype_struct( @@ -679,13 +689,21 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_newtype_variant( self, - name: &'static str, - variant_index: u32, + _name: &'static str, + _variant_index: u32, variant: &'static str, value: &T, ) -> Result { - self.ser_name(name)? - .serialize_newtype_variant(name, variant_index, variant, value) + if variant == TEXT_KEY { + value.serialize(self.ser.into_simple_type_serializer())?; + Ok(()) + } else { + let ser = ElementSerializer { + ser: self.ser, + key: XmlName::try_from(variant)?, + }; + value.serialize(ser) + } } fn serialize_seq(self, len: Option) -> Result { @@ -707,12 +725,22 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_tuple_variant( self, name: &'static str, - variant_index: u32, + _variant_index: u32, variant: &'static str, len: usize, ) -> Result { - self.ser_name(name)? - .serialize_tuple_variant(name, variant_index, variant, len) + if variant == TEXT_KEY { + self.ser + .into_simple_type_serializer() + .serialize_tuple_struct(name, len) + .map(Tuple::Text) + } else { + let ser = ElementSerializer { + ser: self.ser, + key: XmlName::try_from(variant)?, + }; + ser.serialize_tuple_struct(name, len).map(Tuple::Element) + } } fn serialize_map(self, len: Option) -> Result { @@ -730,11 +758,24 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_struct_variant( self, name: &'static str, - variant_index: u32, + _variant_index: u32, variant: &'static str, len: usize, ) -> Result { - self.ser_name(name)? - .serialize_struct_variant(name, variant_index, variant, len) + if variant == TEXT_KEY { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::$text` as text content value", + name + ) + .into(), + )) + } else { + let ser = ElementSerializer { + ser: self.ser, + key: XmlName::try_from(variant)?, + }; + ser.serialize_struct(name, len) + } } } diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index dad26b1b..e396606d 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -7,7 +7,8 @@ use crate::errors::serialize::DeError; use crate::escapei::_escape; use crate::se::{Indent, QuoteLevel}; use serde::ser::{ - Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, + Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, + SerializeTupleVariant, Serializer, }; use serde::serde_if_integer128; use std::borrow::Cow; @@ -612,6 +613,24 @@ impl<'i, W: Write> SerializeTupleStruct for SimpleSeq<'i, W> { } } +impl<'i, W: Write> SerializeTupleVariant for SimpleSeq<'i, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] diff --git a/src/se/text.rs b/src/se/text.rs new file mode 100644 index 00000000..4dec138d --- /dev/null +++ b/src/se/text.rs @@ -0,0 +1,192 @@ +//! Contains serializer for a special `&text` field + +use crate::de::TEXT_KEY; +use crate::errors::serialize::DeError; +use crate::se::simple_type::{SimpleSeq, SimpleTypeSerializer}; +use serde::ser::{Impossible, Serialize, Serializer}; +use serde::serde_if_integer128; +use std::fmt::Write; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + #[inline] + fn $method(self, value: $ty) -> Result { + self.0.$method(value) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize a `$text` field of a struct or map. +/// +/// This serializer a very similar to [`SimpleTypeSerializer`], but different +/// from it in how it processes unit enum variants. Unlike [`SimpleTypeSerializer`] +/// this serializer does not write anything for the unit variant. +pub struct TextSerializer<'i, W: Write>(pub SimpleTypeSerializer<'i, W>); + +impl<'i, W: Write> Serializer for TextSerializer<'i, W> { + type Ok = W; + type Error = DeError; + + type SerializeSeq = SimpleSeq<'i, W>; + type SerializeTuple = SimpleSeq<'i, W>; + type SerializeTupleStruct = SimpleSeq<'i, W>; + type SerializeTupleVariant = SimpleSeq<'i, W>; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + write_primitive!(serialize_bool(bool)); + + write_primitive!(serialize_i8(i8)); + write_primitive!(serialize_i16(i16)); + write_primitive!(serialize_i32(i32)); + write_primitive!(serialize_i64(i64)); + + write_primitive!(serialize_u8(u8)); + write_primitive!(serialize_u16(u16)); + write_primitive!(serialize_u32(u32)); + write_primitive!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive!(serialize_i128(i128)); + write_primitive!(serialize_u128(u128)); + } + + write_primitive!(serialize_f32(f32)); + write_primitive!(serialize_f64(f64)); + + write_primitive!(serialize_char(char)); + write_primitive!(serialize_str(&str)); + write_primitive!(serialize_bytes(&[u8])); + + #[inline] + fn serialize_none(self) -> Result { + self.0.serialize_none() + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_unit(self) -> Result { + self.0.serialize_unit() + } + + #[inline] + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.0.serialize_unit_struct(name) + } + + #[inline] + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + if variant == TEXT_KEY { + Ok(self.0.writer) + } else { + self.0.serialize_unit_variant(name, variant_index, variant) + } + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum newtype variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } + + #[inline] + fn serialize_seq(self, len: Option) -> Result { + self.0.serialize_seq(len) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.0.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.0.serialize_tuple_struct(name, len) + } + + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum tuple variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } + + #[inline] + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "cannot serialize map as text content value".into(), + )) + } + + #[inline] + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("cannot serialize struct `{}` as text content value", name).into(), + )) + } + + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } +} diff --git a/src/serde_helpers.rs b/src/serde_helpers.rs index 779d733f..5e249870 100644 --- a/src/serde_helpers.rs +++ b/src/serde_helpers.rs @@ -199,11 +199,6 @@ macro_rules! impl_deserialize_for_internally_tagged_enum { /// [`#[serde(with = "...")]`][with], [`#[serde(deserialize_with = "...")]`][de-with] /// and [`#[serde(serialize_with = "...")]`][se-with]. /// -/// When you serialize unit variants of enums, they are serialized as an empty -/// element, like ``. If your enum consist only of unit variants, -/// it is frequently required to serialize them as string content of an -/// element, like `Unit`. To make this possible use this module. -/// /// ``` /// # use pretty_assertions::assert_eq; /// use quick_xml::de::from_str; @@ -258,6 +253,45 @@ macro_rules! impl_deserialize_for_internally_tagged_enum { /// ``` /// Read about the meaning of a special [`$text`] field. /// +/// In versions of quick-xml before 0.31.0 this module used to represent enum +/// unit variants as `EnumUnitVariant` instead of ``. +/// Since version 0.31.0 this is default representation of enums in normal fields, +/// and `` requires `$value` field: +/// +/// ``` +/// # use pretty_assertions::assert_eq; +/// use quick_xml::de::from_str; +/// use quick_xml::se::to_string; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize, PartialEq, Debug)] +/// enum SomeEnum { +/// // Default implementation serializes enum as an `` element +/// EnumValue, +/// # /* +/// ... +/// # */ +/// } +/// +/// #[derive(Serialize, Deserialize, PartialEq, Debug)] +/// #[serde(rename = "some-container")] +/// struct SomeContainer { +/// #[serde(rename = "$value")] +/// field: SomeEnum, +/// } +/// +/// let container = SomeContainer { +/// field: SomeEnum::EnumValue, +/// }; +/// let xml = "\ +/// \ +/// \ +/// "; +/// +/// assert_eq!(to_string(&container).unwrap(), xml); +/// assert_eq!(from_str::(xml).unwrap(), container); +/// ``` +/// /// [with]: https://serde.rs/field-attrs.html#with /// [de-with]: https://serde.rs/field-attrs.html#deserialize_with /// [se-with]: https://serde.rs/field-attrs.html#serialize_with diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 0ea01a72..4b7fd43e 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -369,6 +369,14 @@ mod without_root { mod enum_ { use super::*; + /// Enum representation: + /// + /// |Kind |Top-level and in `$value` field | + /// |-------|-----------------------------------------| + /// |Unit |`` | + /// |Newtype|`42` | + /// |Tuple |`42answer` | + /// |Struct |`42answer`| mod externally_tagged { use super::*; use pretty_assertions::assert_eq; @@ -432,8 +440,17 @@ mod without_root { answer\ "); - /// Test serialization of the specially named variant `$text` - mod text { + /// Test serialization of the specially named variant `$text`. + /// + /// Enum representation: + /// + /// |Kind |Top-level, in `$text` and `$value` fields| + /// |-------|-----------------------------------------| + /// |Unit |_(empty)_ | + /// |Newtype|`42` | + /// |Tuple |`42 answer` | + /// |Struct |Err(Unsupported) | + mod text_variant { use super::*; use pretty_assertions::assert_eq; @@ -475,6 +492,155 @@ mod without_root { string: "newtype text", } => Unsupported("cannot serialize enum struct variant `Struct::$text` as text content value")); + /// Tests the enum type that is type of field of a struct. + /// The tests above does not cover those variants, because we use + /// different serializers for enums on top level and which represents + /// a field. + /// + /// According to general rules for structs, we should write `` tag + /// for a `field` field. Because value of that field is an enum, we should + /// somehow know what the variant was written in order to deserialize it, + /// but `$text` variant say us that we should write enum content using + /// `xs:simpleType` serialization. + /// + /// Enum representation: + /// + /// |Kind |In normal field `field`| + /// |-------|-----------------------| + /// |Unit |`` | + /// |Newtype|Err(Unsupported) [^1] | + /// |Tuple |Err(Unsupported) [^1] | + /// |Struct |Err(Unsupported) | + /// + /// [^1]: Unfortunately, cannot be represented, because the possible representation + /// (`42` and `42 answer`) will clash with + /// representation of normal unit variant in normal field + mod normal_field { + use super::*; + use super::{Newtype, Struct, Tuple, Unit}; + use pretty_assertions::assert_eq; + + // `Root::field` contains text content, and because text content is empty, + // `` is written + serialize_as!(unit: Root { field: Unit::Text } => ""); + err!(newtype: + Root { field: Newtype::Text("newtype text") } + => Unsupported("cannot serialize enum newtype variant `Newtype::$text`"), + " Unsupported("cannot serialize enum tuple variant `Tuple::$text`"), + " Unsupported("cannot serialize enum struct variant `Struct::$text`"), + "` tags. + /// + /// Enum representation: + /// + /// |Kind |Top-level and in `$value` field| + /// |-------|-------------------------------| + /// |Unit |_(empty)_ | + /// |Newtype|`42` | + /// |Tuple |`42 answer` | + /// |Struct |Err(Unsupported) | + mod value_field { + use super::*; + use super::{Newtype, Struct, Tuple, Unit}; + use pretty_assertions::assert_eq; + + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct Root { + #[serde(rename = "$value")] + field: T, + } + + // Without #[serde(default)] on a field we cannot deserialize value + // back, because there is no signs in the XML that `field` was written. + // If we write the usual enum, then variant name would be written as + // a tag, but because variant is a `$text`, nothing is written + serialize_as_only!(unit: Root { field: Unit::Text } => ""); + serialize_as!(newtype: + Root { field: Newtype::Text("newtype text") } + => "newtype text"); + serialize_as!(tuple: + Root { field: Tuple::Text(42.0, "tuple-text".into()) } + => "42 tuple-text"); + // Note, that spaces in strings, even escaped, would represent + // the list item delimiters. Non-symmetric serialization follows + // tradition: the XmlBeans Java library have the same behavior. + // See also + serialize_as_only!(tuple_with_spaces: + Root { field: Tuple::Text(42.0, "tuple text".into()) } + => "42 tuple text"); + err!(struct_: + Root { field: Struct::Text { + float: 42.0, + string: "answer" + }} + => Unsupported("cannot serialize `$text` struct variant of `Struct` enum"), + " { + #[serde(rename = "$text")] + field: T, + } + + // Without #[serde(default)] on a field we cannot deserialize value + // back, because there is no signs in the XML that `field` was written. + // If we write the usual enum, then variant name would be written as + // a tag, but because variant is a `$text`, nothing is written + serialize_as_only!(unit: Root { field: Unit::Text } => ""); + err!(newtype: + Root { field: Newtype::Text("newtype text") } + => Unsupported("cannot serialize enum newtype variant `Newtype::$text` as text content value"), + " Unsupported("cannot serialize enum tuple variant `Tuple::$text` as text content value"), + " Unsupported("cannot serialize enum struct variant `Struct::$text` as text content value"), + "Unit`| + /// |Newtype|Err(Unsupported) | + /// |Tuple |Err(Unsupported) | + /// |Struct |Err(Unsupported) | + mod normal_field { use super::*; use pretty_assertions::assert_eq; - serialize_as_only!(unit: + serialize_as!(unit: Root { field: ExternallyTagged::Unit } => "\ - \ + Unit\ "); - serialize_as_only!(newtype: + err!(newtype: Root { field: ExternallyTagged::Newtype(true) } - => "\ - true\ - "); - serialize_as_only!(tuple: + => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype`"), + " "\ - 42\ - answer\ - "); - serialize_as_only!(struct_: + => Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple`"), + " "\ - \ - 42\ - answer\ - \ - "); - serialize_as_only!(nested_struct: - Root { field: ExternallyTagged::Holder { - nested: Nested { float: 42.0 }, - string: "answer", - }} - => "\ - \ - \ - 42\ - \ - answer\ - \ - "); - serialize_as_only!(flatten_struct: - Root { field: ExternallyTaggedWorkaround::Flatten { - nested: Nested { float: 42.0 }, - string: "answer", - }} - => "\ - \ - 42\ - answer\ - \ - "); - serialize_as_only!(empty_struct: + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct`"), + " "\ - \ - "); - serialize_as_only!(text: - Root { field: ExternallyTagged::Text { - float: 42.0, - string: "answer" - }} - => "\ - \ - 42\ - answer\ - \ - "); + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty`"), + " "\ \ - \ + Unit\ \ "); - serialize_as_only!(newtype: + err!(newtype: Root { field: Inner { inner: ExternallyTagged::Newtype(true) } } - => "\ - \ - true\ - \ - "); - serialize_as_only!(tuple: + => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype`"), + " "\ - \ - 42\ - answer\ - \ - "); - serialize_as_only!(struct_: + => Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple`"), + " "\ - \ - \ - 42\ - answer\ - \ - \ - "); - serialize_as_only!(nested_struct: - Root { field: Inner { inner: ExternallyTagged::Holder { - nested: Nested { float: 42.0 }, - string: "answer", - }}} - => "\ - \ - \ - \ - 42\ - \ - answer\ - \ - \ - "); - serialize_as_only!(flatten_struct: - Root { field: Inner { inner: ExternallyTaggedWorkaround::Flatten { - nested: Nested { float: 42.0 }, - string: "answer", - }}} - => "\ - \ - \ - 42\ - answer\ - \ - \ - "); - serialize_as_only!(empty_struct: + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct`"), + " "\ - \ - \ - \ - "); - serialize_as_only!(text: - Root { field: Inner { inner: ExternallyTagged::Text { - float: 42.0, - string: "answer" - }}} - => "\ - \ - \ - 42\ - answer\ - \ - \ - "); + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty`"), + "` | + /// |Newtype|`42` | + /// |Tuple |`42answer` | + /// |Struct |`42answer`| + mod value_field { use super::*; use pretty_assertions::assert_eq; @@ -742,8 +826,8 @@ mod without_root { "); } - /// The same tests as in `in_struct2`, but enum field renamed to `$value`. - mod in_struct_value2 { + /// The same tests as in `normal_field2`, but enum field renamed to `$value`. + mod value_field2 { use super::*; use pretty_assertions::assert_eq; @@ -840,10 +924,19 @@ mod without_root { "); } - /// The same tests as in `in_struct`, but enum field renamed to `$text`. + /// The same tests as in `normal_field`, but enum field renamed to `$text`. /// /// Text representation of enum is possible only for unit variants. - mod in_struct_text { + /// + /// Enum representation: + /// + /// |Kind |In `$text` field| + /// |-------|----------------| + /// |Unit |`Unit` | + /// |Newtype|Err(Unsupported)| + /// |Tuple |Err(Unsupported)| + /// |Struct |Err(Unsupported)| + mod text_field { use super::*; use pretty_assertions::assert_eq; @@ -858,25 +951,25 @@ mod without_root { => "Unit"); err!(newtype: Root { field: ExternallyTagged::Newtype(true) } - => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype` as an attribute or text content value"), + => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype` as text content value"), " Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple` as an attribute or text content value"), + => Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Holder` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Holder` as text content value"), " Unsupported("cannot serialize enum newtype variant `ExternallyTaggedWorkaround::Flatten` as an attribute or text content value"), + => Unsupported("cannot serialize enum newtype variant `ExternallyTaggedWorkaround::Flatten` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Text` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Text` as text content value"), " "Unit"); err!(newtype: Root { field: Inner { inner: ExternallyTagged::Newtype(true) } } - => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype` as an attribute or text content value"), + => Unsupported("cannot serialize enum newtype variant `ExternallyTagged::Newtype` as text content value"), " Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple` as an attribute or text content value"), + => Unsupported("cannot serialize enum tuple variant `ExternallyTagged::Tuple` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Struct` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Holder` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Holder` as text content value"), " Unsupported("cannot serialize enum newtype variant `ExternallyTaggedWorkaround::Flatten` as an attribute or text content value"), + => Unsupported("cannot serialize enum newtype variant `ExternallyTaggedWorkaround::Flatten` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Empty` as text content value"), " Unsupported("cannot serialize enum struct variant `ExternallyTagged::Text` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `ExternallyTagged::Text` as text content value"), "