Skip to content
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

Add serde_helper module and document a frequent pattern of enums usage #601

Merged
merged 1 commit into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}
}