Skip to content

feat: support serializing structs into $value-named fields #878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1577,15 +1577,24 @@
//! uses that name. This will allow you to switch XML crates more smoothly if required.
//! </div>
//!
//! 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
Expand Down Expand Up @@ -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 `<soapenv:Envelope> ... </soapenv:Envelope>`.
//!
//! ```rust
//! # use serde::Serialize;
//! # use pretty_assertions::assert_eq;
//! #
//! #[derive(Serialize)]
//! struct Envelope<T> {
//! body: Body<T>,
//! }
//!
//! #[derive(Serialize)]
//! struct Body<T> {
//! #[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
//! "<Envelope><body><Example><a>42</a></Example></body></Envelope>",
//! );
//! ```
//!
//! ### Primitives and sequences of primitives
//!
//! Sequences serialized to a list of elements. Note, that types that does not
Expand Down
96 changes: 67 additions & 29 deletions src/se/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self::Ok, Self::Error>;
type SerializeStruct = Impossible<Self::Ok, Self::Error>;
type SerializeStruct = Struct<'w, 'i, W>;
type SerializeStructVariant = Struct<'w, 'i, W>;

write_primitive!(serialize_bool(bool));
Expand Down Expand Up @@ -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<Self::SerializeStruct, Self::Error> {
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
Expand Down Expand Up @@ -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) }
=> "<Struct>\
<key>answer</key>\
<val>42</val>\
<val>42</val>\
</Struct>");

serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) }
=> "<Struct>\
<key>answer</key>\
Expand All @@ -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"));
=> "<Text>\
<before>answer</before>\
42 42\
<after>answer</after>\
</Text>");
serialize_as!(enum_struct:
SpecialEnum::Text {
before: "answer",
Expand Down Expand Up @@ -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"));
=> "<Value>\
<before>answer</before>\
<Struct>\
<key>answer</key>\
<val>42</val>\
<val>42</val>\
</Struct>\
<after>answer</after>\
</Value>");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "<Struct>\
Expand All @@ -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#"<Attributes key="answer" val="42 42"/>"#);
serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 }
=> r#"<AttributesBefore key="answer"><val>42</val></AttributesBefore>"#);
serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 }
=> r#"<AttributesAfter val="42"><key>answer</key></AttributesAfter>"#);

serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) }
=> r#"<Attributes key="answer" val="42 42"/>"#);
Expand Down Expand Up @@ -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) }
=> "<Struct>\n \
<key>answer</key>\n \
<val>42</val>\n \
<val>42</val>\n\
</Struct>");
serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) }
=> "<Struct>\n \
<key>answer</key>\n \
Expand All @@ -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"));
=> "<Text>\n \
<before>answer</before>42 42<after>answer</after>\n\
</Text>");
serialize_as!(enum_struct:
SpecialEnum::Text {
before: "answer",
Expand Down Expand Up @@ -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 \
<Value>\n \
<before>answer</before>\n \
<Struct>\n \
<key>answer</key>\n \
<val>42</val>\n \
<val>42</val>\n \
</Struct>\n \
<after>answer</after>\n \
</Value>\n ");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "\n \
Expand All @@ -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#"<Attributes key="answer" val="42 42"/>"#);
serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 }
=> "<AttributesBefore key=\"answer\">\n \
<val>42</val>\n\
</AttributesBefore>");
serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 }
=> "<AttributesAfter val=\"42\">\n \
<key>answer</key>\n\
</AttributesAfter>");

serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) }
=> r#"<Attributes key="answer" val="42 42"/>"#);
Expand Down
64 changes: 41 additions & 23 deletions src/se/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,14 +1107,18 @@ mod tests {
=> "<Tuple>first</Tuple>\
<Tuple>42</Tuple>");

// 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) }
=> "<Struct>\
<key>answer</key>\
<val>42</val>\
<val>42</val>\
</Struct>");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "<Struct>\
Expand Down Expand Up @@ -1234,22 +1238,22 @@ mod tests {
=> "<Tuple>first</Tuple>\
<Tuple>42</Tuple>");

// 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",
content: BTreeMap::from([("_1", 2), ("_3", 4)]),
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) }
=> "<Struct>\
<key>answer</key>\
<val>42</val>\
<val>42</val>\
</Struct>");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "<Struct>\
Expand Down Expand Up @@ -1830,14 +1834,19 @@ mod tests {
<Tuple>first</Tuple>\n \
<Tuple>42</Tuple>\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 \
<Struct>\n \
<key>answer</key>\n \
<val>42</val>\n \
<val>42</val>\n \
</Struct>\n");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "\n \
Expand Down Expand Up @@ -1959,22 +1968,31 @@ mod tests {
<Tuple>first</Tuple>\n \
<Tuple>42</Tuple>\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",
content: BTreeMap::from([("_1", 2), ("_3", 4)]),
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 \
<Value>\n \
<before>answer</before>\n \
<Struct>\n \
<key>answer</key>\n \
<val>42</val>\n \
<val>42</val>\n \
</Struct>\n \
<after>answer</after>\n \
</Value>\n ");
value!(enum_struct:
Enum::Struct { key: "answer", val: (42, 42) }
=> "\n \
Expand Down