Skip to content

Commit

Permalink
Merge pull request #601 from Mingun/serde-helpers
Browse files Browse the repository at this point in the history
Add `serde_helper` module and document a frequent pattern of enums usage
  • Loading branch information
Mingun authored May 15, 2023
2 parents 9fb797e + cd973f4 commit 84b07b4
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 2 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
74 changes: 73 additions & 1 deletion src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
//! - [Enums and sequences of enums](#enums-and-sequences-of-enums)
//! - [Frequently Used Patterns](#frequently-used-patterns)
//! - [`<element>` lists](#element-lists)
//! - [Enum::Unit Variants As a Text](#enumunit-variants-as-a-text)
//!
//!
//!
Expand Down Expand Up @@ -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.
//!
//! ## `<element>` lists
//! `<element>` lists
//! -----------------
//! Many XML formats wrap lists of elements in the additional container,
//! although this is not required by the XML rules:
//!
Expand Down Expand Up @@ -1672,9 +1674,79 @@
//!
//! Instead of writing such functions manually, you also could try <https://lib.rs/crates/serde-query>.
//!
//! Enum::Unit Variants As a Text
//! -----------------------------
//! 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
//! <some-container>
//! <field>EnumValue</field>
//! </some-container>
//! ```
//! 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
//! <some-container>
//! <EnumValue/>
//! </some-container>
//! ```
//! 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 `<field>` tag:
//! ```xml
//! <some-container>
//! <field some="attribute">EnumValue</field>
//! </some-container>
//! ```
//!
//! 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
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//!
Expand Down Expand Up @@ -62,6 +63,8 @@ pub mod name;
pub mod reader;
#[cfg(feature = "serialize")]
pub mod se;
#[cfg(feature = "serde-types")]
pub mod serde_helpers;
/// Not an official API, public for integration tests
#[doc(hidden)]
pub mod utils;
Expand Down
111 changes: 111 additions & 0 deletions src/serde_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Provides helper functions to glue an XML with a serde content model.

use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// 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 `<Unit/>`. 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 `<field>Unit</field>`. 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 `<EnumValue/>` 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 = "\
/// <some-container>\
/// <field>EnumValue</field>\
/// </some-container>";
///
/// assert_eq!(to_string(&container).unwrap(), xml);
/// assert_eq!(from_str::<SomeContainer>(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 super::*;

/// 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<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
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<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
#[derive(Deserialize)]
struct Field<T> {
#[serde(rename = "$text")]
value: T,
}
Ok(Field::deserialize(deserializer)?.value)
}
}

0 comments on commit 84b07b4

Please sign in to comment.