diff --git a/Changelog.md b/Changelog.md index 1dd92753..f5c974f6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,9 @@ ### New Features +- [#907]: Allow to serialize and deserialize structs which contains only one `$text` field + as an attribute value or a text content value. + ### Bug Fixes ### Misc Changes @@ -25,6 +28,7 @@ of `NsReader`. Use `.resolver().bindings()` and `.resolver().resolve()` methods instead. - [#913]: `Attributes::has_nil` now accepts `NamespaceResolver` instead of `Reader`. +[#907]: https://github.com/tafia/quick-xml/pull/907 [#908]: https://github.com/tafia/quick-xml/pull/908 [#913]: https://github.com/tafia/quick-xml/pull/913 diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 5e615f14..7bcea1c9 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -3,13 +3,13 @@ //! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp //! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition -use crate::de::Text; +use crate::de::{Text, TEXT_KEY}; use crate::encoding::Decoder; use crate::errors::serialize::DeError; use crate::escape::unescape; use crate::utils::{trim_xml_spaces, CowRef}; use memchr::memchr; -use serde::de::value::UnitDeserializer; +use serde::de::value::{MapDeserializer, SeqAccessDeserializer, UnitDeserializer}; use serde::de::{ DeserializeSeed, Deserializer, EnumAccess, IntoDeserializer, SeqAccess, VariantAccess, Visitor, }; @@ -477,6 +477,17 @@ impl<'de, 'a> SeqAccess<'de> for ListIter<'de, 'a> { } } +/// serde<=1.0.213 does not implement `IntoDeserializer` for `SeqAccessDeserializer`, +/// which is required for `MapDeserializer`, so implement it for our type +impl<'de, 'a> IntoDeserializer<'de, DeError> for ListIter<'de, 'a> { + type Deserializer = SeqAccessDeserializer; + + #[inline] + fn into_deserializer(self) -> Self::Deserializer { + SeqAccessDeserializer::new(self) + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /// A deserializer for an xml probably escaped and encoded value of XSD [simple types]. @@ -605,6 +616,19 @@ impl<'de, 'a> SimpleTypeDeserializer<'de, 'a> { }, }) } + + #[inline] + fn as_seq_access(&self) -> Result, DeError> { + let content = match self.decode()? { + CowRef::Input(s) => Content::Input(s), + CowRef::Slice(s) => Content::Slice(s), + CowRef::Owned(s) => Content::Owned(s, 0), + }; + Ok(ListIter { + content: Some(content), + escaped: self.escaped, + }) + } } impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { @@ -685,15 +709,7 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { where V: Visitor<'de>, { - let content = match self.decode()? { - CowRef::Input(s) => Content::Input(s), - CowRef::Slice(s) => Content::Slice(s), - CowRef::Owned(s) => Content::Owned(s, 0), - }; - visitor.visit_seq(ListIter { - content: Some(content), - escaped: self.escaped, - }) + visitor.visit_seq(self.as_seq_access()?) } /// Representation of tuples the same as [sequences][Self::deserialize_seq]. @@ -720,7 +736,22 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { } unsupported!(deserialize_map); - unsupported!(deserialize_struct(&'static str, &'static [&'static str])); + + fn deserialize_struct( + self, + _name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if fields == [TEXT_KEY] { + let seq = self.as_seq_access()?; + return visitor.visit_map(MapDeserializer::new(std::iter::once((TEXT_KEY, seq)))); + } + self.deserialize_str(visitor) + } fn deserialize_enum( self, diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index 68a93071..7fa7b0a4 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -3,11 +3,13 @@ //! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp //! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition +use crate::de::TEXT_KEY; use crate::escape::escape_char; +use crate::se::text::TextSerializer; use crate::se::{QuoteLevel, SeError}; use crate::utils::CDataIterator; use serde::ser::{ - Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, + Impossible, Serialize, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, }; use std::fmt::{self, Write}; @@ -480,7 +482,7 @@ impl Serializer for SimpleTypeSerializer { type SerializeTupleStruct = SimpleSeq; type SerializeTupleVariant = Impossible; type SerializeMap = Impossible; - type SerializeStruct = Impossible; + type SerializeStruct = SimpleSeq; type SerializeStructVariant = Impossible; write_primitive!(); @@ -561,18 +563,18 @@ impl Serializer for SimpleTypeSerializer { )) } + #[inline] fn serialize_struct( self, - name: &'static str, + _name: &'static str, _len: usize, ) -> Result { - Err(SeError::Unsupported( - format!( - "cannot serialize struct `{}` as an attribute or text content value", - name - ) - .into(), - )) + Ok(SimpleSeq { + writer: self.writer, + target: self.target, + level: self.level, + is_empty: true, + }) } fn serialize_struct_variant( @@ -679,6 +681,37 @@ impl SerializeTupleVariant for SimpleSeq { } } +impl SerializeStruct for SimpleSeq { + type Ok = W; + type Error = SeError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + if self.is_empty && key == TEXT_KEY { + let ser = TextSerializer(SimpleTypeSerializer { + writer: &mut self.writer, + target: self.target, + level: self.level, + }); + value.serialize(ser)?; + self.is_empty = false; + Ok(()) + } else { + Err(SeError::Unsupported( + format!("only struct which contains exactly one `{TEXT_KEY}` field may be serialized as an attribute or text content value").into(), + )) + } + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] @@ -1260,7 +1293,7 @@ mod tests { err!(map: BTreeMap::from([(1, 2), (3, 4)]) => Unsupported("cannot serialize map as an attribute or text content value")); err!(struct_: Struct { key: "answer", val: 42 } - => Unsupported("cannot serialize struct `Struct` as an attribute or text content value")); + => Unsupported("only struct which contains exactly one `$text` field may be serialized as an attribute or text content value")); err!(enum_struct: Enum::Struct { key: "answer", val: 42 } => Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value")); } diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs index 6e06f55b..349b02b7 100644 --- a/tests/serde-issues.rs +++ b/tests/serde-issues.rs @@ -709,3 +709,50 @@ fn issue888() { } ); } + +/// Regression test for https://github.com/tafia/quick-xml/issues/906. +#[test] +fn issue906() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct AsElement { + #[serde(rename = "a-list")] + a_list: TextContent, + } + + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct AsAttribute { + #[serde(rename = "@a-list")] + a_list: TextContent, + } + + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct TextContent { + #[serde(rename = "$text")] + content: Vec, + #[serde(skip)] + ignored: (), + } + + let foo = AsElement { + a_list: TextContent { + content: vec!["A".to_string(), "B".to_string()], + ignored: (), + }, + }; + let bar = AsAttribute { + a_list: TextContent { + content: vec!["A".to_string(), "B".to_string()], + ignored: (), + }, + }; + + let buffer = to_string_with_root("test", &foo).unwrap(); + assert_eq!(buffer, "A B"); + let foo2: AsElement = from_str(&buffer).unwrap(); + assert_eq!(foo2, foo); + + let buffer = to_string_with_root("test", &bar).unwrap(); + assert_eq!(buffer, ""); + let bar2: AsAttribute = from_str(&buffer).unwrap(); + assert_eq!(bar2, bar); +}