xso: make convert_via_fromstr_and_display available

Useful for dependent crates which want an easy way to provide the
AsXmlText / FromXmlText traits.
This commit is contained in:
Jonas Schäfer 2025-05-02 16:54:42 +02:00
commit 25d89e02ea
3 changed files with 56 additions and 9 deletions

View file

@ -50,6 +50,9 @@ Version NEXT:
- Support for a post-deserialization callback function call. (!553)
- Support for correctly inheriting `xml:lang` values throughout the
XML document.
- `xso::convert_via_fromstr_and_display`, a declarative macro to provide
`AsXmlText` and `FromXmlText` implementations based on the standard
library's `Display` and `FromStr` traits.
* Changes
- Generated AsXml iterator and FromXml builder types are now
doc(hidden), to not clutter hand-written documentation with auto

View file

@ -39,10 +39,10 @@ mod rxml_util;
pub mod text;
#[doc(hidden)]
#[cfg(feature = "macros")]
pub mod exports {
#[cfg(feature = "minidom")]
#[cfg(all(feature = "minidom", feature = "macros"))]
pub use minidom;
#[cfg(feature = "macros")]
pub use rxml;
// These re-exports are necessary to support both std and no_std in code
@ -65,12 +65,14 @@ pub mod exports {
///
/// This is re-exported for use by macros in cases where we cannot rely on
/// people not having done `type bool = str` or some similar shenanigans.
#[cfg(feature = "macros")]
pub type CoreBool = bool;
/// The built-in `u8` type.
///
/// This is re-exported for use by macros in cases where we cannot rely on
/// people not having done `type u8 = str` or some similar shenanigans.
#[cfg(feature = "macros")]
pub type CoreU8 = u8;
/// Compile-time comparison of two strings.
@ -78,6 +80,7 @@ pub mod exports {
/// Used by macro-generated code.
///
/// This is necessary because `<str as PartialEq>::eq` is not `const`.
#[cfg(feature = "macros")]
pub const fn const_str_eq(a: &'static str, b: &'static str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();

View file

@ -97,26 +97,67 @@ use crate::{error::Error, AsXmlText, FromXmlText};
#[cfg(feature = "base64")]
use base64::engine::general_purpose::STANDARD as StandardBase64Engine;
/// # Generate `AsXmlText` and `FromXmlText` implementations
///
/// This macro generates an `AsXmlText` implementation which uses
/// [`Display`][`core::fmt::Display`] and an `FromXmlText` which uses
/// [`FromStr`][`core::str::FromStr`] for the types it is called on.
///
/// ## Syntax
///
/// The macro accepts a comma-separated list of types. Optionally, each type
/// can be preceded by a `#[cfg(..)]` attribute to make the implementations
/// conditional on a feature.
///
/// ## Example
///
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::convert_via_fromstr_and_display;
/// # use core::fmt::{self, Display};
/// # use core::str::FromStr;
/// struct Foo;
///
/// impl FromStr for Foo {
/// # type Err = core::convert::Infallible;
/// #
/// # fn from_str(s: &str) -> Result<Self, Self::Err> { todo!() }
/// /* ... */
/// }
///
/// impl Display for Foo {
/// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { todo!() }
/// /* ... */
/// }
///
/// convert_via_fromstr_and_display!(
/// Foo,
/// );
/// ```
#[macro_export]
macro_rules! convert_via_fromstr_and_display {
($($(#[cfg $cfg:tt])?$t:ty,)+) => {
($($(#[cfg $cfg:tt])?$t:ty),+ $(,)?) => {
$(
$(
#[cfg $cfg]
)?
impl FromXmlText for $t {
impl $crate::FromXmlText for $t {
#[doc = concat!("Parse [`", stringify!($t), "`] from XML text via [`FromStr`][`core::str::FromStr`].")]
fn from_xml_text(s: String) -> Result<Self, Error> {
s.parse().map_err(Error::text_parse_error)
fn from_xml_text(s: String) -> Result<Self, $crate::error::Error> {
s.parse().map_err($crate::error::Error::text_parse_error)
}
}
$(
#[cfg $cfg]
)?
impl AsXmlText for $t {
impl $crate::AsXmlText for $t {
#[doc = concat!("Convert [`", stringify!($t), "`] to XML text via [`Display`][`core::fmt::Display`].\n\nThis implementation never fails.")]
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
Ok(Cow::Owned(self.to_string()))
fn as_xml_text(&self) -> Result<$crate::exports::alloc::borrow::Cow<'_, str>, $crate::error::Error> {
Ok($crate::exports::alloc::borrow::Cow::Owned(self.to_string()))
}
}
)+