From ca20cde36451701b69b324d385e3938e3a74faf7 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 11 May 2023 23:54:42 +0500 Subject: [PATCH] Add `serde_helper` module and document a frequent pattern of enums usage --- Changelog.md | 5 ++ src/de/mod.rs | 74 ++++++++++++++++++++++++++++- src/lib.rs | 5 +- src/serde_helpers.rs | 109 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/serde_helpers.rs diff --git a/Changelog.md b/Changelog.md index 7cf8d68f..49892f20 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,10 +12,15 @@ ### New Features +- [#601]: Add `serde_helper` module to the crate root with some useful utility + functions and document using of enum's unit variants as a text content of element. + ### Bug Fixes ### Misc Changes +[#601]: https://github.com/tafia/quick-xml/pull/601 + ## 0.28.2 -- 2023-04-12 diff --git a/src/de/mod.rs b/src/de/mod.rs index 9fd0ebf6..177d2444 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -27,6 +27,7 @@ //! - [Enums and sequences of enums](#enums-and-sequences-of-enums) //! - [Frequently Used Patterns](#frequently-used-patterns) //! - [`` lists](#element-lists) +//! - [Enum::Unit Variants As a Text](#enumunit-variants-as-a-text) //! //! //! @@ -1579,7 +1580,8 @@ //! Some XML constructs used so frequent, that it is worth to document the recommended //! way to represent them in the Rust. The sections below describes them. //! -//! ## `` lists +//! `` lists +//! ----------------- //! Many XML formats wrap lists of elements in the additional container, //! although this is not required by the XML rules: //! @@ -1672,9 +1674,79 @@ //! //! Instead of writing such functions manually, you also could try . //! +//! Enum::Unit Variants As a Text +//! ----------------------------- +//! A 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`]. +//! //! [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 // Macros should be defined before the modules that using them // Also, macros should be imported before using them diff --git a/src/lib.rs b/src/lib.rs index 6f3ae7a2..f4f71828 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,8 @@ //! Furthermore, quick-xml also contains optional [Serde] support to directly //! serialize and deserialize from structs, without having to deal with the XML events. //! To get it enable the `serialize` feature. Read more about mapping Rust types -//! to XML in the documentation of [`de`] module. +//! to XML in the documentation of [`de`] module. Also check [`serde_helpers`] +//! module. //! //! # Examples //! @@ -62,6 +63,8 @@ pub mod name; pub mod reader; #[cfg(feature = "serialize")] pub mod se; +#[cfg(feature = "serialize")] +pub mod serde_helpers; /// Not an official API, public for integration tests #[doc(hidden)] pub mod utils; diff --git a/src/serde_helpers.rs b/src/serde_helpers.rs new file mode 100644 index 00000000..2d10ad4d --- /dev/null +++ b/src/serde_helpers.rs @@ -0,0 +1,109 @@ +//! Provides helper functions to glue an XML with a serde content model. + +/// Provides helper functions to serialization and deserialization of types +/// (usually enums) as a text content of an element and intended to use with +/// [`#[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 +/// elements, like ``. At the same time, when enum consist only from unit +/// variants, it is frequently needed 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; +/// 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(with = "quick_xml::serde_helpers::text_content")] +/// field: SomeEnum, +/// } +/// +/// let container = SomeContainer { +/// field: SomeEnum::EnumValue, +/// }; +/// let xml = "\ +/// \ +/// EnumValue\ +/// "; +/// +/// assert_eq!(to_string(&container).unwrap(), xml); +/// assert_eq!(from_str::(xml).unwrap(), container); +/// ``` +/// +/// Using of this module is equivalent to replacing `field`'s type to this: +/// +/// ``` +/// # use serde::{Deserialize, Serialize}; +/// # 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, +/// } +/// ``` +/// Read about the meaning of a special [`$text`] field. +/// +/// [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 +/// [`$text`]: ../../de/index.html#text +pub mod text_content { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Serializes `value` as an XSD [simple type]. Intended to use with + /// `#[serde(serialize_with = "...")]`. See example at [`text_content`] + /// module level. + /// + /// [simple type]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: Serialize, + { + #[derive(Serialize)] + struct Field<'a, T> { + #[serde(rename = "$text")] + value: &'a T, + } + Field { value }.serialize(serializer) + } + + /// Deserializes XSD's [simple type]. Intended to use with + /// `#[serde(deserialize_with = "...")]`. See example at [`text_content`] + /// module level. + /// + /// [simple type]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + #[derive(Deserialize)] + struct Field { + #[serde(rename = "$text")] + value: T, + } + Ok(Field::deserialize(deserializer)?.value) + } +}