diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 41db1273..bba14abe 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::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, }; @@ -727,7 +727,6 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { } unsupported!(deserialize_map); - unsupported!(deserialize_struct(&'static str, &'static [&'static str])); fn deserialize_enum( self, @@ -757,6 +756,33 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { { visitor.visit_unit() } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if fields == [TEXT_KEY] { + let content = match self.decode()? { + CowRef::Input(s) => Content::Input(s), + CowRef::Slice(s) => Content::Slice(s), + CowRef::Owned(s) => Content::Owned(s, 0), + }; + let list_iter = ListIter { + content: Some(content), + escaped: self.escaped, + }; + + let seq_deserializer = SeqAccessDeserializer::new(list_iter); + let der = MapDeserializer::new(std::iter::once((TEXT_KEY, seq_deserializer))); + return der.deserialize_map(visitor); + } + self.deserialize_str(visitor) + } } impl<'de, 'a> EnumAccess<'de> for SimpleTypeDeserializer<'de, 'a> { diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index 2eedb97b..b85f52d0 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 serde::serde_if_integer128; @@ -473,6 +475,34 @@ impl SimpleTypeSerializer { Ok(self.writer.write_fmt(args)?) } } +impl<'w, W: Write> SerializeStruct for SimpleSeq { + type Ok = W; + type Error = SeError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + if key == TEXT_KEY { + let ser = TextSerializer(SimpleTypeSerializer { + writer: &mut self.writer, + target: self.target, + level: self.level, + }); + value.serialize(ser)?; + Ok(()) + } else { + Err(SeError::Unsupported( + format!("only `{TEXT_KEY}` field is supported in text content").into(), + )) + } + } + + fn end(self) -> Result { + SerializeSeq::end(self) + } +} impl Serializer for SimpleTypeSerializer { type Ok = W; @@ -483,7 +513,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!(); @@ -564,14 +594,24 @@ impl Serializer for SimpleTypeSerializer { )) } + #[inline] fn serialize_struct( self, name: &'static str, - _len: usize, + len: usize, ) -> Result { + if len == 1 { + let seq = SimpleSeq { + writer: self.writer, + target: self.target, + level: self.level, + is_empty: true, + }; + return Ok(seq); + } Err(SeError::Unsupported( format!( - "cannot serialize struct `{}` as an attribute or text content value", + "cannot serialize struct `{}` having more than one field as an attribute or text content value", name ) .into(), @@ -1267,7 +1307,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("cannot serialize struct `Struct` having more than one field 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..6bf0cb65 100644 --- a/tests/serde-issues.rs +++ b/tests/serde-issues.rs @@ -709,3 +709,46 @@ 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)] + pub struct TextContent { + #[serde(default, rename = "$text")] + content: Vec, + } + let foo = AsElement { + a_list: TextContent { + content: vec!["A".to_string(), "B".to_string()], + }, + }; + let bar = AsAttribute { + a_list: TextContent { + content: vec!["A".to_string(), "B".to_string()], + }, + }; + + let buffer = to_string_with_root("test", &foo).unwrap(); + std::assert_eq!(buffer, "A B"); + let foo2: AsElement = from_str(&buffer).unwrap(); + std::assert_eq!(foo2, foo); + + let buffer = to_string_with_root("test", &bar).unwrap(); + std::assert_eq!(buffer, ""); + + let bar2: AsAttribute = from_str(&buffer).unwrap(); + std::assert_eq!(bar2, bar); +}