From fe7472c51853e7f21529715e1b1bad6e205d68fc Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 15 Aug 2023 20:29:23 +0500 Subject: [PATCH 01/15] Inline calls of `ser_name` and `serialize_*_variant` in all `serialize_*_variant`s The code was simply copied with adaptation of parameter names. This is required, because we want to change representation of internal `enum` fields to match the current `$text` representation, but keep representation of top-level enums unchanged (because there are no field for them that could be renamed to `$value`) That means that enum Enum { Unit, } would be represented differently when used as a top-level type or as a type of field: Unit We need to change default in-field representation to be compatible with serde changes since >=1.0.181. To get the old representation we need to rename the field to `$value`. Actually, this is even more correct way, because I noticed, that currently serializer and deserializer worked differently and a `de` module documentation contains an error, which was unnoticed, because that code snippet is not tested. That will be fixed in next commits. --- src/se/mod.rs | 88 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index bbd3ce2e..bf951304 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -79,7 +79,8 @@ pub(crate) mod key; pub(crate) mod simple_type; use self::content::ContentSerializer; -use self::element::ElementSerializer; +use self::element::{ElementSerializer, Tuple}; +use crate::de::TEXT_KEY; use crate::errors::serialize::DeError; use crate::writer::Indentation; use serde::ser::{self, Serialize}; @@ -662,11 +663,29 @@ 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) + let ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; + 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)?; + ser.ser.write_empty(name) + } } fn serialize_newtype_struct( @@ -680,12 +699,24 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_newtype_variant( self, name: &'static str, - variant_index: u32, + _variant_index: u32, variant: &'static str, value: &T, ) -> Result { - self.ser_name(name)? - .serialize_newtype_variant(name, variant_index, variant, value) + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; + if variant == TEXT_KEY { + value.serialize(ser.ser.into_simple_type_serializer())?; + Ok(()) + } else { + ser.key = XmlName::try_from(variant)?; + value.serialize(ser) + } } fn serialize_seq(self, len: Option) -> Result { @@ -707,12 +738,26 @@ 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) + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; + if variant == TEXT_KEY { + ser.ser + .into_simple_type_serializer() + .serialize_tuple_struct(name, len) + .map(Tuple::Text) + } else { + ser.key = XmlName::try_from(variant)?; + ser.serialize_tuple_struct(name, len).map(Tuple::Element) + } } fn serialize_map(self, len: Option) -> Result { @@ -730,11 +775,28 @@ 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) + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; + if variant == TEXT_KEY { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::$text` as text content value", + name + ) + .into(), + )) + } else { + ser.key = XmlName::try_from(variant)?; + ser.serialize_struct(name, len) + } } } From 2a1f2cf6a7ad47093736e879d454630f6fb932e3 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 14 Sep 2023 19:54:43 +0500 Subject: [PATCH 02/15] Reduce scope of created ElementSerializers This commit simply moves pieces of code from one place to another --- src/se/mod.rs | 55 ++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index bf951304..60ba0caf 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -666,13 +666,6 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { _variant_index: u32, variant: &'static str, ) -> Result { - let ser = ElementSerializer { - ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, - }; if variant == TEXT_KEY { // We should write some text but we don't known what text to write Err(DeError::Unsupported( @@ -684,7 +677,7 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { )) } else { let name = XmlName::try_from(variant)?; - ser.ser.write_empty(name) + self.ser.write_empty(name) } } @@ -703,17 +696,17 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { variant: &'static str, value: &T, ) -> Result { - let mut ser = ElementSerializer { - ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, - }; if variant == TEXT_KEY { - value.serialize(ser.ser.into_simple_type_serializer())?; + value.serialize(self.ser.into_simple_type_serializer())?; Ok(()) } else { + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; ser.key = XmlName::try_from(variant)?; value.serialize(ser) } @@ -742,19 +735,19 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { variant: &'static str, len: usize, ) -> Result { - let mut ser = ElementSerializer { - ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, - }; if variant == TEXT_KEY { - ser.ser + self.ser .into_simple_type_serializer() .serialize_tuple_struct(name, len) .map(Tuple::Text) } else { + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; ser.key = XmlName::try_from(variant)?; ser.serialize_tuple_struct(name, len).map(Tuple::Element) } @@ -779,13 +772,6 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { variant: &'static str, len: usize, ) -> Result { - let mut ser = ElementSerializer { - ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, - }; if variant == TEXT_KEY { Err(DeError::Unsupported( format!( @@ -795,6 +781,13 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { .into(), )) } else { + let mut ser = ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(name)?, + }, + }; ser.key = XmlName::try_from(variant)?; ser.serialize_struct(name, len) } From 85b745e80045eb18c72aa67c9aa98538874c32cb Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 14 Sep 2023 20:10:05 +0500 Subject: [PATCH 03/15] Immediately create ElementSerializers with the correct key After inlining we could notice that some unnecessary work are done. This commit slightly changes the behavior, because construction of XmlName checks the allowed names to be correspond to XML 1.1 rules for names. This is not a problem, because we do not want to check names that will not be used in the generated XML. --- src/se/mod.rs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index 60ba0caf..64b8a4e5 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -691,7 +691,7 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { fn serialize_newtype_variant( self, - name: &'static str, + _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, @@ -700,14 +700,10 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { value.serialize(self.ser.into_simple_type_serializer())?; Ok(()) } else { - let mut ser = ElementSerializer { + let ser = ElementSerializer { ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, + key: XmlName::try_from(variant)?, }; - ser.key = XmlName::try_from(variant)?; value.serialize(ser) } } @@ -741,14 +737,10 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { .serialize_tuple_struct(name, len) .map(Tuple::Text) } else { - let mut ser = ElementSerializer { + let ser = ElementSerializer { ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, + key: XmlName::try_from(variant)?, }; - ser.key = XmlName::try_from(variant)?; ser.serialize_tuple_struct(name, len).map(Tuple::Element) } } @@ -781,14 +773,10 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { .into(), )) } else { - let mut ser = ElementSerializer { + let ser = ElementSerializer { ser: self.ser, - key: match self.root_tag { - Some(key) => key, - None => XmlName::try_from(name)?, - }, + key: XmlName::try_from(variant)?, }; - ser.key = XmlName::try_from(variant)?; ser.serialize_struct(name, len) } } From a6d2b35959737fa877ee448bec850d59b5867aff Mon Sep 17 00:00:00 2001 From: Mingun Date: Sat, 16 Sep 2023 22:26:49 +0500 Subject: [PATCH 04/15] Directly define associated types of the root serializer That way actual types will be shown in the documentation. --- src/se/mod.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index 64b8a4e5..b45ba683 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -79,7 +79,7 @@ pub(crate) mod key; pub(crate) mod simple_type; use self::content::ContentSerializer; -use self::element::{ElementSerializer, Tuple}; +use self::element::{ElementSerializer, Map, Struct, Tuple}; use crate::de::TEXT_KEY; use crate::errors::serialize::DeError; use crate::writer::Indentation; @@ -609,16 +609,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)); From 3a2114d109d4d9017f99da9eb38014c8e67569b4 Mon Sep 17 00:00:00 2001 From: Mingun Date: Fri, 13 Oct 2023 21:41:07 +0500 Subject: [PATCH 05/15] Rename some test modules The new names more precisely reflects their purpose --- tests/serde-se.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 0ea01a72..12fae194 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -433,7 +433,7 @@ mod without_root { "); /// Test serialization of the specially named variant `$text` - mod text { + mod text_variant { use super::*; use pretty_assertions::assert_eq; @@ -485,8 +485,8 @@ mod without_root { /// Deserialization is not possible because we cannot choose with what /// field we should associate the XML node that we see. To do that we /// mark field by a special name `$value` ([`VALUE_KEY`]) and that is - /// tested in the `in_struct_value` module. - mod in_struct { + /// tested in the `value_field` module. + mod normal_field { use super::*; use pretty_assertions::assert_eq; @@ -559,9 +559,9 @@ mod without_root { "); } - /// The same tests as in `in_struct`, but enum at the second nesting + /// The same tests as in `normal_field`, but enum at the second nesting /// level. - mod in_struct2 { + mod normal_field2 { use super::*; use pretty_assertions::assert_eq; @@ -660,8 +660,8 @@ mod without_root { "); } - /// The same tests as in `in_struct`, but enum field renamed to `$value`. - mod in_struct_value { + /// The same tests as in `normal_field`, but enum field renamed to `$value`. + mod value_field { use super::*; use pretty_assertions::assert_eq; @@ -742,8 +742,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 +840,10 @@ 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 { + mod text_field { use super::*; use pretty_assertions::assert_eq; @@ -899,10 +899,10 @@ mod without_root { " Date: Sun, 15 Oct 2023 02:22:38 +0500 Subject: [PATCH 06/15] Move tests of enum with special `$text` and `$value` fields in enum to ContentSerializer tests In the next commits representation of enums will change and ElementSerializer won't anymore support serialization of enum struct variants. In order to not lost tests move them into ContentSerializer. It is also not meaningless (for `$value`), since it is ContentSerializer which is responsible for serializing `$value` fields and struct variants will be possible to serialize only in `$value` fields, i. e. they will be serialized by ContentSerializer. For the `$text` fields tests is moved only to keep them. --- src/se/content.rs | 534 +++++++++++++++++++++++++++++++++++++++++++++- src/se/element.rs | 530 +-------------------------------------------- 2 files changed, 534 insertions(+), 530 deletions(-) diff --git a/src/se/content.rs b/src/se/content.rs index 376f55ac..c065cc0c 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -394,6 +394,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 +403,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 +467,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 +490,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 +622,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 +648,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 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")); + } + + /// `$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 +930,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 +1063,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 +1089,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 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")); + } + + /// `$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..520a86b9 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -713,7 +713,7 @@ mod tests { /// 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 @@ -966,139 +966,12 @@ mod tests { } => 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")); - } } /// 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,128 +1198,6 @@ 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"); - - 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 { @@ -1677,7 +1428,7 @@ mod tests { /// 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 @@ -1942,151 +1693,12 @@ mod tests { } => 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")); - } } /// 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 +1936,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 { From 46c30b4593c74ac2a2c110a829ad4d5eb1a10b48 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 17 Sep 2023 01:51:55 +0500 Subject: [PATCH 07/15] Made doc tests for enums runnable Actually, our documentation was incorrect because test is failing. All other commits in this branch will have a goal to fix that. failures: src\de\mod.rs - de (line 1434) --- src/de/mod.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index 248d82c2..fbe6618c 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1431,33 +1431,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 +1488,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 +1515,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 +1539,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 +1573,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)] From 191aa1917b5d8c92516cd4f63ffd32143e43c846 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 8 Oct 2023 23:15:49 +0500 Subject: [PATCH 08/15] Add new tests for representation of `$text` variants of enums in different kind of fields failures (10): without_root::enum_::externally_tagged::text_variant::normal_field::newtype without_root::enum_::externally_tagged::text_variant::normal_field::tuple without_root::enum_::externally_tagged::text_variant::normal_field::tuple_with_spaces without_root::enum_::externally_tagged::text_variant::normal_field::unit without_root::enum_::externally_tagged::text_variant::text_field::unit without_root::enum_::externally_tagged::text_variant::value_field::newtype without_root::enum_::externally_tagged::text_variant::value_field::struct_ without_root::enum_::externally_tagged::text_variant::value_field::tuple without_root::enum_::externally_tagged::text_variant::value_field::tuple_with_spaces without_root::enum_::externally_tagged::text_variant::value_field::unit --- tests/serde-se.rs | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 12fae194..3733a5d7 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -475,6 +475,156 @@ 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|`42` | + /// |Tuple |`42 answer`| + /// |Struct |Err(Unsupported) | + 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 } => ""); + 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 enum struct variant `Struct::$text` as text content value"), + "` 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 an attribute or text content value"), + " Unsupported("cannot serialize enum tuple variant `Tuple::$text` as an attribute or text content value"), + " Unsupported("cannot serialize enum struct variant `Struct::$text` as an attribute or text content value"), + " Date: Tue, 10 Oct 2023 22:30:28 +0500 Subject: [PATCH 09/15] Allow to serialize `$text` variants in `$value` fields and document ContentSerializer ContentSerializer is used to write inner values of tags; the `$value` name disables writing surrounding tags from fields. (review with "ignore whitespace changes" option on) failures (5): without_root::enum_::externally_tagged::text_variant::normal_field::newtype without_root::enum_::externally_tagged::text_variant::normal_field::tuple without_root::enum_::externally_tagged::text_variant::normal_field::tuple_with_spaces without_root::enum_::externally_tagged::text_variant::normal_field::unit without_root::enum_::externally_tagged::text_variant::text_field::unit Fixed (5): without_root::enum_::externally_tagged::text_variant::value_field::newtype without_root::enum_::externally_tagged::text_variant::value_field::struct_ without_root::enum_::externally_tagged::text_variant::value_field::tuple without_root::enum_::externally_tagged::text_variant::value_field::tuple_with_spaces without_root::enum_::externally_tagged::text_variant::value_field::unit --- src/se/content.rs | 121 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 36 deletions(-) diff --git a/src/se/content.rs b/src/se/content.rs index c065cc0c..3b851ce5 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) + } } } From 01650eef4e6b6484ecf2371adf76a121597bd45f Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 10 Oct 2023 21:37:58 +0500 Subject: [PATCH 10/15] Change expected enum representation in tests according to new policy New representation policy is compatible with serde>=1.0.181 and also more correct and consistent with general mapping rules. General rules of new policy: - 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 field name. The tables below summarizes the representation policy. For examples in fields only content at place of `...` is shown: ``` ... ``` The struct which contains field looks like: ``` struct Container { field: Enum, #[serde(rename = "$text")] text: Enum, #[serde(rename = "$value")] value: Enum, } ``` Normal enum variant ------------------- ### Old (before this commit) representation |Kind |Top-level, in normal and `$value` fields |In `$text` field| |-------|-----------------------------------------|----------------| |Unit |`` |_(empty)_ | |Newtype|`42` |`42` | |Tuple |`42answer` |`42 answer` | |Struct |`42answer`|Err(Unsupported)| ### New (since this commit) |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 -------------------- `` in top-level serialization is the name of top-level element, serialization is impossible if it is not defined in the serializer. ### Old (before this commit) representation |Kind |In `$value` field |In normal field |In `$text` field| |-------|-------------------------------|--------------------------|----------------| |Unit |_(empty)_ |`` |_(empty)_ | |Newtype|`42` |`42` |`42` | |Tuple |`42 answer` |`42 answer`|`42 answer` | |Struct |Err(Unsupported) |Err(Unsupported) |Err(Unsupported)| ### New (since this commit) |Kind |Top-level and in `$value` field|In normal field |In `$text` field| |-------|-------------------------------|--------------------------|----------------| |Unit |_(empty)_ |`` |_(empty)_ | |Newtype|`42` |Err(Unsupported) |Err(Unsupported)| |Tuple |`42 answer` |Err(Unsupported) |Err(Unsupported)| |Struct |Err(Unsupported) |Err(Unsupported) |Err(Unsupported)| failures (28): --lib (14) se::element::tests::expand_empty_elements::enum_unit se::element::tests::expand_empty_elements::enum_unit_escaped se::element::tests::with_indent::attributes::enum_ se::element::tests::with_indent::enum_newtype se::element::tests::with_indent::enum_struct se::element::tests::with_indent::enum_tuple se::element::tests::with_indent::enum_unit se::element::tests::with_indent::enum_unit_escaped se::element::tests::without_indent::attributes::enum_ se::element::tests::without_indent::enum_newtype se::element::tests::without_indent::enum_struct se::element::tests::without_indent::enum_tuple se::element::tests::without_indent::enum_unit se::element::tests::without_indent::enum_unit_escaped serde-se (14): without_root::enum_::externally_tagged::normal_field2::empty_struct without_root::enum_::externally_tagged::normal_field2::newtype without_root::enum_::externally_tagged::normal_field2::struct_ without_root::enum_::externally_tagged::normal_field2::tuple without_root::enum_::externally_tagged::normal_field2::unit without_root::enum_::externally_tagged::normal_field::empty_struct without_root::enum_::externally_tagged::normal_field::newtype without_root::enum_::externally_tagged::normal_field::struct_ without_root::enum_::externally_tagged::normal_field::tuple without_root::enum_::externally_tagged::normal_field::unit without_root::enum_::externally_tagged::text_variant::normal_field::newtype without_root::enum_::externally_tagged::text_variant::normal_field::tuple without_root::enum_::externally_tagged::text_variant::normal_field::unit without_root::enum_::externally_tagged::text_variant::text_field::unit --- src/se/element.rs | 100 +++++--------------- tests/serde-se.rs | 235 ++++++++++++++++++---------------------------- 2 files changed, 111 insertions(+), 224 deletions(-) diff --git a/src/se/element.rs b/src/se/element.rs index 520a86b9..45037d64 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -670,12 +670,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 +689,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,12 +703,8 @@ 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 @@ -1216,12 +1211,8 @@ mod tests { 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"#); + 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 { @@ -1385,12 +1376,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\ @@ -1404,9 +1395,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 \ @@ -1419,12 +1409,8 @@ 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 @@ -1960,16 +1946,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 { @@ -2052,39 +2030,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("") => ""); @@ -2092,8 +2037,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/tests/serde-se.rs b/tests/serde-se.rs index 3733a5d7..58bd8196 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,7 +440,16 @@ mod without_root { answer\ "); - /// Test serialization of the specially named variant `$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; @@ -488,12 +505,16 @@ mod without_root { /// /// Enum representation: /// - /// |Kind |In normal field `field` | - /// |-------|--------------------------| - /// |Unit |`` | - /// |Newtype|`42` | - /// |Tuple |`42 answer`| - /// |Struct |Err(Unsupported) | + /// |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}; @@ -502,19 +523,14 @@ mod without_root { // `Root::field` contains text content, and because text content is empty, // `` is written serialize_as!(unit: Root { field: Unit::Text } => ""); - serialize_as!(newtype: + err!(newtype: Root { field: Newtype::Text("newtype text") } - => "newtype text"); - serialize_as!(tuple: + => Unsupported("cannot serialize enum newtype variant `Newtype::$text`"), + " "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"); + => Unsupported("cannot serialize enum tuple variant `Tuple::$text`"), + "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; @@ -993,6 +927,15 @@ mod without_root { /// The same tests as in `normal_field`, but enum field renamed to `$text`. /// /// Text representation of enum is possible only for unit variants. + /// + /// 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; From 11fa348e9fd087f5d38f8d3d4c115eba40a4b32a Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 16 Oct 2023 01:50:07 +0500 Subject: [PATCH 11/15] Change serialization of enums in ElementSerializer and document it Now only unit variants can be serialized, all other returns error failures (4): without_root::enum_::externally_tagged::normal_field2::unit without_root::enum_::externally_tagged::normal_field::unit without_root::enum_::externally_tagged::text_variant::normal_field::unit without_root::enum_::externally_tagged::text_variant::text_field::unit Fixed (24): --lib (14, all) se::element::tests::expand_empty_elements::enum_unit se::element::tests::expand_empty_elements::enum_unit_escaped se::element::tests::with_indent::attributes::enum_ se::element::tests::with_indent::enum_newtype se::element::tests::with_indent::enum_struct se::element::tests::with_indent::enum_tuple se::element::tests::with_indent::enum_unit se::element::tests::with_indent::enum_unit_escaped se::element::tests::without_indent::attributes::enum_ se::element::tests::without_indent::enum_newtype se::element::tests::without_indent::enum_struct se::element::tests::without_indent::enum_tuple se::element::tests::without_indent::enum_unit se::element::tests::without_indent::enum_unit_escaped serde-se (10): without_root::enum_::externally_tagged::normal_field2::empty_struct without_root::enum_::externally_tagged::normal_field2::newtype without_root::enum_::externally_tagged::normal_field2::struct_ without_root::enum_::externally_tagged::normal_field2::tuple without_root::enum_::externally_tagged::normal_field::empty_struct without_root::enum_::externally_tagged::normal_field::newtype without_root::enum_::externally_tagged::normal_field::struct_ without_root::enum_::externally_tagged::normal_field::tuple without_root::enum_::externally_tagged::text_variant::normal_field::newtype without_root::enum_::externally_tagged::text_variant::normal_field::tuple --- src/se/element.rs | 131 ++++++++++++++++++++++++++++------------------ tests/serde-se.rs | 2 +- 2 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/se/element.rs b/src/se/element.rs index 45037d64..c39d5122 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -7,12 +7,13 @@ use crate::se::key::QNameSerializer; use crate::se::simple_type::{QuoteTarget, SimpleSeq, SimpleTypeSerializer}; 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 +24,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 +69,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 +135,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 +161,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 +199,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 +242,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 +274,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(()) } diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 58bd8196..f8c43d07 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -536,7 +536,7 @@ mod without_root { float: 42.0, string: "answer" }} - => Unsupported("cannot serialize enum struct variant `Struct::$text` as text content value"), + => Unsupported("cannot serialize enum struct variant `Struct::$text`"), " Date: Fri, 13 Oct 2023 23:08:17 +0500 Subject: [PATCH 12/15] Change deserialization of enums failures (1): without_root::enum_::externally_tagged::text_variant::text_field::unit Fixed (5): without_root::enum_::externally_tagged::normal_field2::unit without_root::enum_::externally_tagged::normal_field::unit without_root::enum_::externally_tagged::text_variant::normal_field::unit --- src/de/map.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) 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 From 478e19a836bab054c38237214a30f1312d3ac672 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 10 Oct 2023 23:36:51 +0500 Subject: [PATCH 13/15] Fix serialization of `$text` variants in `$text` fields Fixed (1): without_root::enum_::externally_tagged::text_variant::text_field::uni --- src/se/content.rs | 20 ++--- src/se/element.rs | 43 +++++----- src/se/mod.rs | 1 + src/se/simple_type.rs | 21 ++++- src/se/text.rs | 192 ++++++++++++++++++++++++++++++++++++++++++ tests/serde-se.rs | 34 ++++---- 6 files changed, 262 insertions(+), 49 deletions(-) create mode 100644 src/se/text.rs diff --git a/src/se/content.rs b/src/se/content.rs index 3b851ce5..984689f0 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -781,7 +781,7 @@ pub(super) 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"); @@ -798,7 +798,7 @@ pub(super) 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: @@ -807,21 +807,21 @@ pub(super) 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_: 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")); + => 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 an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } /// `$value` field inside a struct variant of an enum @@ -1234,7 +1234,7 @@ pub(super) 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"); @@ -1251,7 +1251,7 @@ pub(super) 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: @@ -1260,21 +1260,21 @@ pub(super) 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_: 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")); + => 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 an attribute or text content value")); + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); } /// `$value` field inside a struct variant of an enum diff --git a/src/se/element.rs b/src/se/element.rs index c39d5122..afad2a97 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -5,6 +5,7 @@ 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::{ Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, @@ -438,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 { @@ -821,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"); @@ -838,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: @@ -847,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 @@ -948,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"); @@ -965,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: @@ -974,21 +975,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")); } } @@ -1527,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"); @@ -1544,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: @@ -1553,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 @@ -1666,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"); @@ -1683,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: @@ -1692,21 +1693,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")); } } diff --git a/src/se/mod.rs b/src/se/mod.rs index b45ba683..839187f3 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -77,6 +77,7 @@ mod content; mod element; pub(crate) mod key; pub(crate) mod simple_type; +mod text; use self::content::ContentSerializer; use self::element::{ElementSerializer, Map, Struct, Tuple}; 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/tests/serde-se.rs b/tests/serde-se.rs index f8c43d07..4b7fd43e 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -627,18 +627,18 @@ mod without_root { 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 an attribute or text content value"), + => Unsupported("cannot serialize enum newtype variant `Newtype::$text` as text content value"), " Unsupported("cannot serialize enum tuple variant `Tuple::$text` as an attribute or text content value"), + => Unsupported("cannot serialize enum tuple variant `Tuple::$text` as text content value"), " Unsupported("cannot serialize enum struct variant `Struct::$text` as an attribute or text content value"), + => Unsupported("cannot serialize enum struct variant `Struct::$text` as text content value"), " "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"), " Date: Fri, 13 Oct 2023 21:24:47 +0500 Subject: [PATCH 14/15] Replace chapter "Enum::Unit Variants As a Text" with "Enum Representations" --- src/de/mod.rs | 128 +++++++++++++++++++------------------------ src/serde_helpers.rs | 44 +++++++++++++-- 2 files changed, 96 insertions(+), 76 deletions(-) diff --git a/src/de/mod.rs b/src/de/mod.rs index fbe6618c..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 //! ===================================================== //! @@ -1733,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 //! ----------------------- @@ -1819,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/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 From 78b7bc089b402b7f8de2c6f2180b77b591477f16 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 15 Aug 2023 22:15:38 +0500 Subject: [PATCH 15/15] Restore compatibility with serde >=1.0.181 --- Cargo.toml | 3 +-- Changelog.md | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) 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