From 4910b01244e8b2dd9c3e28036a040d629a4d8e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Tue, 9 Jul 2024 16:57:45 +0200 Subject: [PATCH] xso: add text conversion traits for AsXml --- xso/src/lib.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ xso/src/text.rs | 24 +++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/xso/src/lib.rs b/xso/src/lib.rs index ca4d2924..6a66d1c3 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -285,6 +285,90 @@ impl IntoOptionalXmlText for Option { } } +/// Trait to convert a value to an XML text string. +/// +/// This trait is implemented for many standard library types implementing +/// [`std::fmt::Display`]. In addition, the following feature flags can enable +/// more implementations: +/// +/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid` +/// - `uuid`: `uuid::Uuid` +/// +/// Because of the unfortunate situation as described in [`FromXmlText`], we +/// are **extremely liberal** with accepting optional dependencies for this +/// purpose. You are very welcome to make merge requests against this crate +/// adding support for parsing third-party crates. +pub trait AsXmlText { + /// Convert the value to an XML string in a context where an absent value + /// cannot be represented. + fn as_xml_text(&self) -> Result, self::error::Error>; + + /// Convert the value to an XML string in a context where an absent value + /// can be represented. + /// + /// The provided implementation will always return the result of + /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing + /// this method, implementors can customize the behaviour for certain + /// values. + fn as_optional_xml_text(&self) -> Result>, self::error::Error> { + Ok(Some(self.as_xml_text()?)) + } +} + +impl AsXmlText for String { + fn as_xml_text(&self) -> Result, self::error::Error> { + Ok(Cow::Borrowed(self.as_str())) + } +} + +impl AsXmlText for &str { + fn as_xml_text(&self) -> Result, self::error::Error> { + Ok(Cow::Borrowed(&**self)) + } +} + +impl AsXmlText for Box { + fn as_xml_text(&self) -> Result, self::error::Error> { + T::as_xml_text(&*self) + } +} + +impl AsXmlText for Cow<'_, B> { + fn as_xml_text(&self) -> Result, self::error::Error> { + B::as_xml_text(self.as_ref()) + } +} + +/// Specialized variant of [`AsXmlText`]. +/// +/// Do **not** implement this unless you cannot implement [`AsXmlText`]: +/// implementing [`AsXmlText`] is more versatile and an +/// [`AsOptionalXmlText`] implementation is automatically provided. +/// +/// If you need to customize the behaviour of the [`AsOptionalXmlText`] +/// blanket implementation, implement a custom +/// [`AsXmlText::as_optional_xml_text`] instead. +pub trait AsOptionalXmlText { + /// Convert the value to an XML string in a context where an absent value + /// can be represented. + fn as_optional_xml_text(&self) -> Result>, self::error::Error>; +} + +impl AsOptionalXmlText for T { + fn as_optional_xml_text(&self) -> Result>, self::error::Error> { + ::as_optional_xml_text(self) + } +} + +impl AsOptionalXmlText for Option { + fn as_optional_xml_text(&self) -> Result>, self::error::Error> { + self.as_ref() + .map(T::as_optional_xml_text) + .transpose() + .map(Option::flatten) + } +} + /// Attempt to transform a type implementing [`IntoXml`] into another /// type which implements [`FromXml`]. pub fn transform(from: F) -> Result { diff --git a/xso/src/text.rs b/xso/src/text.rs index 5ff117c3..40664e2c 100644 --- a/xso/src/text.rs +++ b/xso/src/text.rs @@ -9,7 +9,9 @@ #[cfg(feature = "base64")] use core::marker::PhantomData; -use crate::{error::Error, FromXmlText, IntoXmlText}; +use std::borrow::Cow; + +use crate::{error::Error, AsXmlText, FromXmlText, IntoXmlText}; #[cfg(feature = "base64")] use base64::engine::{general_purpose::STANDARD as StandardBase64Engine, Engine as _}; @@ -40,6 +42,16 @@ macro_rules! convert_via_fromstr_and_display { Ok(self.to_string()) } } + + $( + #[cfg(feature = $feature)] + #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] + )? + impl AsXmlText for $t { + fn as_xml_text(&self) -> Result, Error> { + Ok(Cow::Owned(self.to_string())) + } + } )+ } } @@ -64,6 +76,16 @@ impl IntoXmlText for bool { } } +/// This provides an implementation compliant with xsd::bool. +impl AsXmlText for bool { + fn as_xml_text(&self) -> Result, Error> { + match self { + true => Ok(Cow::Borrowed("true")), + false => Ok(Cow::Borrowed("false")), + } + } +} + convert_via_fromstr_and_display! { u8, u16,