diff --git a/src/de/mod.rs b/src/de/mod.rs index a60f49b4..6ee04fa4 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1577,15 +1577,24 @@ //! uses that name. This will allow you to switch XML crates more smoothly if required. //! //! -//! Representation of primitive types in `$value` does not differ from their -//! representation in `$text` field. The difference is how sequences are serialized. -//! `$value` serializes each sequence item as a separate XML element. The name -//! of that element is taken from serialized type, and because only `enum`s provide -//! such name (their variant name), only they should be used for such fields. -//! -//! `$value` fields does not support `struct` types with fields, the serialization -//! of such types would end with an `Err(Unsupported)`. Unit structs and unit -//! type `()` serializing to nothing and can be deserialized from any content. +//! The representation of primitive types in `$value` does not differ from their +//! representation in `$text` fields. The difference is how sequences are serialized +//! and deserialized. `$value` serializes each sequence item as a separate XML element. +//! How the name of the XML element is chosen depends on the field's type. For +//! `enum`s, the variant name is used. For `struct`s, the name of the `struct` +//! is used. +//! +//! During deserialization, if the `$value` field is an enum, then the variant's +//! name is matched against. That's **not** the case with structs, however, since +//! `serde` does not expose type names of nested fields. This does mean that **any** +//! type could be deserialized into a `$value` struct-type field, so long as the +//! struct's fields have compatible types (or are captured as text by `String` +//! or similar-behaving types). This can be handy when using generic types in fields +//! where one knows in advance what to expect. If you do not know what to expect, +//! however, prefer an enum with all possible variants. +//! +//! Unit structs and unit type `()` serialize to nothing and can be deserialized +//! from any content. //! //! Serialization and deserialization of `$value` field performed as usual, except //! that name for an XML element will be given by the serialized type, instead of @@ -1646,6 +1655,38 @@ //! # ); //! ``` //! +//! The next example demonstrates how generic types can be used in conjunction +//! with `$value`-named fields to allow the reuse of wrapping structs. A common +//! example use case for this feature is SOAP messages, which can be commmonly +//! found wrapped around ` ... `. +//! +//! ```rust +//! # use serde::Serialize; +//! # use pretty_assertions::assert_eq; +//! # +//! #[derive(Serialize)] +//! struct Envelope { +//! body: Body, +//! } +//! +//! #[derive(Serialize)] +//! struct Body { +//! #[serde(rename = "$value")] +//! inner: T, +//! } +//! +//! #[derive(Serialize)] +//! struct Example { +//! a: i32, +//! } +//! +//! assert_eq!( +//! quick_xml::se::to_string(&Envelope { body: Body { inner: Example { a: 42 } } }).unwrap(), +//! // Notice how `inner` is not present in the XML +//! "42", +//! ); +//! ``` +//! //! ### Primitives and sequences of primitives //! //! Sequences serialized to a list of elements. Note, that types that does not diff --git a/src/se/content.rs b/src/se/content.rs index 46c6170f..0cc45988 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -179,7 +179,7 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { type SerializeTupleStruct = Seq<'w, 'i, W>; type SerializeTupleVariant = Tuple<'w, 'i, W>; type SerializeMap = Impossible; - type SerializeStruct = Impossible; + type SerializeStruct = Struct<'w, 'i, W>; type SerializeStructVariant = Struct<'w, 'i, W>; write_primitive!(serialize_bool(bool)); @@ -352,11 +352,13 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { fn serialize_struct( self, name: &'static str, - _len: usize, + len: usize, ) -> Result { - Err(SeError::Unsupported( - format!("serialization of struct `{name}` is not supported in `$value` field").into(), - )) + ElementSerializer { + ser: self, + key: XmlName::try_from(name)?, + } + .serialize_struct(name, len) } /// Serializes variant as an element with name `variant`, producing @@ -707,8 +709,13 @@ pub(super) mod tests { // only `enum` can provide err!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: Struct { key: "answer", val: (42, 42) } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\ answer\ @@ -723,13 +730,17 @@ pub(super) mod tests { err!(map: BTreeMap::from([("$text", 2), ("_3", 4)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: + serialize_as!(struct_: Text { before: "answer", content: (42, 42), after: "answer", } - => Unsupported("serialization of struct `Text` is not supported in `$value` field")); + => "\ + answer\ + 42 42\ + answer\ + "); serialize_as!(enum_struct: SpecialEnum::Text { before: "answer", @@ -987,13 +998,21 @@ pub(super) mod tests { after: "answer", } => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: + value!(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")); + => "\ + answer\ + \ + answer\ + 42\ + 42\ + \ + answer\ + "); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\ @@ -1012,12 +1031,12 @@ pub(super) mod tests { err!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: Attributes { key: "answer", val: (42, 42) } - => Unsupported("serialization of struct `Attributes` is not supported in `$value` field")); - err!(struct_before: AttributesBefore { key: "answer", val: 42 } - => Unsupported("serialization of struct `AttributesBefore` is not supported in `$value` field")); - err!(struct_after: AttributesAfter { key: "answer", val: 42 } - => Unsupported("serialization of struct `AttributesAfter` is not supported in `$value` field")); + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } => r#""#); @@ -1157,8 +1176,12 @@ pub(super) mod tests { // only `enum` can provide err!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: Struct { key: "answer", val: (42, 42) } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\n \ answer\n \ @@ -1173,13 +1196,15 @@ pub(super) mod tests { err!(map: BTreeMap::from([("$text", 2), ("_3", 4)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: + serialize_as!(struct_: Text { before: "answer", content: (42, 42), after: "answer", } - => Unsupported("serialization of struct `Text` is not supported in `$value` field")); + => "\n \ + answer42 42answer\n\ + "); serialize_as!(enum_struct: SpecialEnum::Text { before: "answer", @@ -1436,13 +1461,22 @@ pub(super) mod tests { after: "answer", } => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: + value!(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")); + => "\n \ + \n \ + answer\n \ + \n \ + answer\n \ + 42\n \ + 42\n \ + \n \ + answer\n \ + \n "); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\n \ @@ -1462,12 +1496,16 @@ pub(super) mod tests { err!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: Attributes { key: "answer", val: (42, 42) } - => Unsupported("serialization of struct `Attributes` is not supported in `$value` field")); - err!(struct_before: AttributesBefore { key: "answer", val: 42 } - => Unsupported("serialization of struct `AttributesBefore` is not supported in `$value` field")); - err!(struct_after: AttributesAfter { key: "answer", val: 42 } - => Unsupported("serialization of struct `AttributesAfter` is not supported in `$value` field")); + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => "\n \ + 42\n\ + "); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } => r#""#); diff --git a/src/se/element.rs b/src/se/element.rs index 2f6d5f30..f9c43093 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -1107,14 +1107,18 @@ mod tests { => "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 + // We cannot wrap map in any container and should not + // flatten it, so it is impossible to serialize maps err!(map: BTreeMap::from([("$value", BTreeMap::from([("_1", 2), ("_3", 4)]))]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: - BTreeMap::from([("$value", Struct { key: "answer", val: (42, 42) })]) - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(struct_: + Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\ @@ -1234,8 +1238,8 @@ mod tests { => "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 + // We cannot wrap map in any container and should not + // flatten it, so it is impossible to serialize maps err!(map: Value { before: "answer", @@ -1243,13 +1247,13 @@ mod tests { after: "answer", } => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: - Value { - before: "answer", - content: Struct { key: "answer", val: (42, 42) }, - after: "answer", - } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(struct_: + Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\ @@ -1830,14 +1834,19 @@ mod tests { first\n \ 42\n"); - // We cannot wrap map or struct in any container and should not - // flatten it, so it is impossible to serialize maps and structs + // We cannot wrap map in any container and should not + // flatten it, so it is impossible to serialize maps err!(map: BTreeMap::from([("$value", BTreeMap::from([("_1", 2), ("_3", 4)]))]) => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: - BTreeMap::from([("$value", Struct { key: "answer", val: (42, 42) })]) - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(struct_: + Struct { key: "answer", val: (42, 42) } + => "\n \ + \n \ + answer\n \ + 42\n \ + 42\n \ + \n"); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\n \ @@ -1959,8 +1968,8 @@ mod tests { first\n \ 42\n "); - // We cannot wrap map or struct in any container and should not - // flatten it, so it is impossible to serialize maps and structs + // We cannot wrap map in any container and should not + // flatten it, so it is impossible to serialize maps err!(map: Value { before: "answer", @@ -1968,13 +1977,22 @@ mod tests { after: "answer", } => Unsupported("serialization of map types is not supported in `$value` field")); - err!(struct_: + value!(struct_: Value { before: "answer", content: Struct { key: "answer", val: (42, 42) }, after: "answer", } - => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + => "\n \ + \n \ + answer\n \ + \n \ + answer\n \ + 42\n \ + 42\n \ + \n \ + answer\n \ + \n "); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } => "\n \