1
0
mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git synced 2024-06-09 17:54:03 +02:00

parsers-core, parsers-macros: Rename traits

The new names hopefully improve the clarity. The "Attribute" terminology
was replaced by "Optional", because that's what it's really about: The
value is *optional* because it may not be present on the protocol level
(attributes, or extracted child texts).

Likewise, "Text" was replaced by "XmlText", in anticipation of migrating
to CData at some point later and to distinguish it from arbitrary text.
This commit is contained in:
Jonas Schäfer 2024-03-23 18:17:44 +01:00
parent f1dc7594e0
commit 610477242d
5 changed files with 115 additions and 94 deletions

View File

@ -167,8 +167,8 @@ The following field kinds are available:
- `attribute`, `attribute = name`, `attribute(..)`:
Extract a string from an XML attribute. The field type must
implement `FromAttribute` (for [`FromXml`]) or
`IntoAttributeValue` (for [`IntoXml`]).
implement `FromOptionalXmlText` (for [`FromXml`]) or
`IntoOptionalXmlText` (for [`IntoXml`]).
- `name = ..` (default): The XML name of the attribute. If this is not
set, the field's identifier is used.
@ -176,11 +176,11 @@ The following field kinds are available:
and if absent, only unnamespaced attributes are considered.
- `default`: If set, the attribute's value will be defaulted using
[`Default::default`] if the attribute is absent and none could be
generated through [`FromAttribute::from_attribute`].
generated through [`FromOptionalXmlText`].
- `child(..)`: Extract one a text data from a child element. The field type
must implement `FromAttribute` (for [`FromXml`]) or
`IntoAttributeValue` (for [`IntoXml`]).
must implement `FromOptionalXmlText` (for [`FromXml`]) or
`IntoOptionalXmlText` (for [`IntoXml`]).
- `name = ..` (required): The XML name of the child to match.
- `namespace = ..` (required): The XML namespace of the child to match.
@ -230,7 +230,7 @@ The following field kinds are available:
is not allowed to specify them here.
- `text`: Extract the element's text contents. The field type must
implement `FromText` (for [`FromXml`]) or `IntoText`
implement `FromXmlText` (for [`FromXml`]) or `IntoXmlText`
(for [`IntoXml`]).
- `element(..)`: Collect a single element as [`minidom::Element`] instead of
@ -273,21 +273,19 @@ pub mod exports {
pub use minidom;
}
pub use self::exports::minidom::IntoAttributeValue;
use jid::{BareJid, FullJid, Jid};
macro_rules! from_text_via_parse {
($cons:path, $($t:ty,)+) => {
$(
impl FromText for $t {
fn from_text(s: &str) -> Result<Self, error::Error> {
impl FromXmlText for $t {
fn from_xml_text(s: &str) -> Result<Self, error::Error> {
s.parse().map_err($cons)
}
}
impl IntoText for $t {
fn into_text(self) -> String {
impl IntoXmlText for $t {
fn into_xml_text(self) -> String {
self.to_string()
}
}
@ -325,40 +323,96 @@ from_text_via_parse! {
BareJid,
}
/// Provide construction of a value from an XML attribute.
/// Convert XML text to a value.
pub trait FromXmlText: Sized {
/// Construct a value from XML text.
///
/// This is similar to [`std::str::FromStr`], but the error type is fixed.
fn from_xml_text(s: &str) -> Result<Self, error::Error>;
}
impl FromXmlText for String {
/// Copy the string from the source into the result.
fn from_xml_text(s: &str) -> Result<Self, error::Error> {
Ok(s.to_string())
}
}
/// Convert a value into XML text.
pub trait IntoXmlText {
/// Consume the value and return it as XML string.
fn into_xml_text(self) -> String;
}
impl IntoXmlText for String {
fn into_xml_text(self) -> String {
self
}
}
/// Provide construction of a value from optional XML text data.
///
/// Most likely, you don't need to implement this; implement [`FromText`]
/// instead (which automatically provides a [`FromAttribute`] implementation).
/// Most likely, you don't need to implement this; implement [`FromXmlText`]
/// instead (which automatically provides a [`FromOptionalXmlText`]
/// implementation).
///
/// This trait has to exist to handle the special-ness of `Option<_>` when it
/// comes to values which don't always exist.'
pub trait FromAttribute: Sized {
/// comes to values which don't always exist.
pub trait FromOptionalXmlText: Sized {
/// Convert the XML string to a value, maybe.
///
/// This should return `None` if and only if `s` is `None`, but in some
/// cases it makes sense to return `Some(..)` even for an input of
/// `None`, e.g. when a specific default is desired.
fn from_attribute(s: Option<&str>) -> Result<Option<Self>, error::Error>;
fn from_optional_xml_text(s: Option<&str>) -> Result<Option<Self>, error::Error>;
}
impl<T: FromAttribute> FromAttribute for Option<T> {
impl<T: FromXmlText> FromOptionalXmlText for T {
fn from_optional_xml_text(s: Option<&str>) -> Result<Option<Self>, error::Error> {
s.map(T::from_xml_text).transpose()
}
}
impl<T: FromOptionalXmlText> FromOptionalXmlText for Option<T> {
/// This implementation returns `Some(None)` for an input of `None`.
///
/// That way, absent attributes with a `Option<T>` field type effectively
/// default to `None`.
fn from_attribute(s: Option<&str>) -> Result<Option<Self>, error::Error> {
fn from_optional_xml_text(s: Option<&str>) -> Result<Option<Self>, error::Error> {
match s {
None => Ok(Some(None)),
Some(v) => Ok(Some(T::from_attribute(Some(v))?)),
Some(v) => Ok(Some(T::from_optional_xml_text(Some(v))?)),
}
}
}
impl<T: FromText> FromAttribute for T {
fn from_attribute(s: Option<&str>) -> Result<Option<Self>, error::Error> {
match s {
None => Ok(None),
Some(v) => Ok(Some(T::from_text(v)?)),
/// Provide destruction of a value into optional XML text data.
///
/// Most likely, you don't need to implement this; implement [`IntoXmlText`]
/// instead (which automatically provides a [`IntoOptionalXmlText`]
/// implementation).
///
/// This trait has to exist to handle the special-ness of `Option<_>` when it
/// comes to values which don't always exist.
pub trait IntoOptionalXmlText {
/// Destruct the value into an optional string.
///
/// Returning `None` causes the resulting XML object (likely an attribute,
/// but maybe also an extracted child) to not appear in the output.
fn into_optional_xml_text(self) -> Option<String>;
}
impl<T: IntoXmlText> IntoOptionalXmlText for T {
fn into_optional_xml_text(self) -> Option<String> {
Some(self.into_xml_text())
}
}
impl<T: IntoOptionalXmlText> IntoOptionalXmlText for Option<T> {
fn into_optional_xml_text(self) -> Option<String> {
match self {
None => None,
Some(v) => v.into_optional_xml_text(),
}
}
}
@ -407,33 +461,6 @@ impl<T: IntoXml> IntoXml for Option<T> {
}
}
/// Convert XML text to a value.
pub trait FromText: Sized {
/// Construct a value from XML text.
///
/// This is similar to [`std::str::FromStr`], but the error type is fixed.
fn from_text(s: &str) -> Result<Self, error::Error>;
}
impl FromText for String {
/// Copy the string from the source into the result.
fn from_text(s: &str) -> Result<Self, error::Error> {
Ok(s.to_string())
}
}
/// Convert a value into XML text.
pub trait IntoText {
/// Consume the value and return it as XML string.
fn into_text(self) -> String;
}
impl IntoText for String {
fn into_text(self) -> String {
self
}
}
/// Specify handling for complex data types mapped to XML.
///
/// Implementing this trait allows a value to be used in collections in the
@ -442,9 +469,9 @@ impl IntoText for String {
/// elements, to feed vectors or maps respectively.
///
/// Indeed, this trait is automatically implemented for types implementing
/// both [`FromText`] and [`IntoText`] with `T = String` and likewise for
/// tuples of two such types with `T = (String, String)`, covering most use
/// cases.
/// both [`FromXmlText`] and [`IntoXmlText`] with `T = String` and likewise
/// for tuples of two such types with `T = (String, String)`, covering most
/// use cases.
///
/// **If you need more implementations on foreign types, do not hesitate to
/// make a PR or file an issue!**
@ -457,22 +484,24 @@ pub trait XmlDataItem<T>: Sized {
fn into_raw(self) -> T;
}
impl<T: FromText + IntoText> XmlDataItem<String> for T {
impl<T: FromXmlText + IntoXmlText> XmlDataItem<String> for T {
fn from_raw(data: String) -> Result<Self, error::Error> {
Self::from_text(&data)
Self::from_xml_text(&data)
}
fn into_raw(self) -> String {
self.into_text()
self.into_xml_text()
}
}
impl<A: FromText + IntoText, B: FromText + IntoText> XmlDataItem<(String, String)> for (A, B) {
impl<A: FromXmlText + IntoXmlText, B: FromXmlText + IntoXmlText> XmlDataItem<(String, String)>
for (A, B)
{
fn from_raw(data: (String, String)) -> Result<Self, error::Error> {
Ok((A::from_text(&data.0)?, B::from_text(&data.1)?))
Ok((A::from_xml_text(&data.0)?, B::from_xml_text(&data.1)?))
}
fn into_raw(self) -> (String, String) {
(self.0.into_text(), self.1.into_text())
(self.0.into_xml_text(), self.1.into_xml_text())
}
}

View File

@ -1,11 +1,9 @@
use std::fmt;
use std::ops::{Deref, DerefMut};
use minidom::IntoAttributeValue;
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
use crate::{error::Error, FromText, IntoText};
use crate::{error::Error, FromXmlText, IntoXmlText};
/// Base64-encoding behavior for `Vec<u8>`.
///
@ -28,14 +26,14 @@ impl DerefMut for Base64 {
}
}
impl FromText for Base64 {
fn from_text(s: &str) -> Result<Self, Error> {
impl FromXmlText for Base64 {
fn from_xml_text(s: &str) -> Result<Self, Error> {
Ok(Self(Base64Engine.decode(s)?))
}
}
impl IntoText for Base64 {
fn into_text(self) -> String {
impl IntoXmlText for Base64 {
fn into_xml_text(self) -> String {
Base64Engine.encode(&self.0)
}
}
@ -61,8 +59,8 @@ impl DerefMut for WhitespaceAwareBase64 {
}
}
impl FromText for WhitespaceAwareBase64 {
fn from_text(s: &str) -> Result<Self, Error> {
impl FromXmlText for WhitespaceAwareBase64 {
fn from_xml_text(s: &str) -> Result<Self, Error> {
let s: String = s
.chars()
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
@ -72,8 +70,8 @@ impl FromText for WhitespaceAwareBase64 {
}
}
impl IntoText for WhitespaceAwareBase64 {
fn into_text(self) -> String {
impl IntoXmlText for WhitespaceAwareBase64 {
fn into_xml_text(self) -> String {
Base64Engine.encode(&self.0)
}
}
@ -107,8 +105,8 @@ impl fmt::Display for NonEmptyString {
}
}
impl FromText for NonEmptyString {
fn from_text(s: &str) -> Result<Self, Error> {
impl FromXmlText for NonEmptyString {
fn from_xml_text(s: &str) -> Result<Self, Error> {
if s.len() == 0 {
return Err(Error::ParseError("string must not be empty"));
}
@ -116,14 +114,8 @@ impl FromText for NonEmptyString {
}
}
impl IntoText for NonEmptyString {
fn into_text(self) -> String {
impl IntoXmlText for NonEmptyString {
fn into_xml_text(self) -> String {
self.0
}
}
impl IntoAttributeValue for NonEmptyString {
fn into_attribute_value(self) -> Option<String> {
Some(self.0)
}
}

View File

@ -93,7 +93,7 @@ impl AttributeField {
}
},
value: quote! {
match <#ty as ::xmpp_parsers_core::FromAttribute>::from_attribute(
match <#ty as ::xmpp_parsers_core::FromOptionalXmlText>::from_optional_xml_text(
residual.attr(#name)
)? {
Some(v) => v,
@ -114,7 +114,7 @@ impl AttributeField {
pub(super) fn build_into_element(self, ident: Expr, ty: Type) -> Result<TokenStream> {
let name = self.name;
Ok(quote! {
match <#ty as ::xmpp_parsers_core::exports::minidom::IntoAttributeValue>::into_attribute_value(#ident) {
match <#ty as ::xmpp_parsers_core::IntoOptionalXmlText>::into_optional_xml_text(#ident) {
Some(v) => builder.attr(#name, v),
None => builder,
}

View File

@ -76,7 +76,7 @@ impl ExtractDef {
/// given:
/// - If the extracted part is text-like (an attribute or the text
/// contents of the child), the part is converted to
/// `Option<#target_ty>` via `FromAttribute`.
/// `Option<#target_ty>` via `FromOptionalXmlText`.
/// - Otherwise, the part is returned as `Some(..)`.
/// - Otherwise, if this extract only contains a single part and
/// `target_ty` is **not** given, the value is returned as directly.
@ -85,13 +85,13 @@ impl ExtractDef {
///
/// This complexity is currently necessary in order to handle the
/// different cases of extraction; generally, the user of the derive
/// macros wants to be able to use the standard `FromAttribute`
/// macros wants to be able to use the standard `FromOptionalXmlText`
/// conversion for extracted values.
///
/// However, some values are not text-based, e.g. when `extract(elements)`
/// is used or, indeed, multiple parts are being extracted. In that case,
/// `FromAttribute` could not be used and attempting to use it would lead
/// to type errors.
/// `FromOptionalXmlText` could not be used and attempting to use it would
/// lead to type errors.
///
/// Because we cannot switch based on the traits implemented by the target
/// type, we have to use these somewhat complex heuristics (and document
@ -111,7 +111,7 @@ impl ExtractDef {
Compound::Struct { ref fields, .. } => {
if fields[0].kind.is_text_like() {
quote! {
<#target_ty as ::xmpp_parsers_core::FromAttribute>::from_attribute(Some(data.0.as_str()))?
<#target_ty as ::xmpp_parsers_core::FromOptionalXmlText>::from_optional_xml_text(Some(data.0.as_str()))?
}
} else {
quote! { Some(data.0) }
@ -148,7 +148,7 @@ impl ExtractDef {
///
/// If `target_ty` is given, this extract contains exactly one part and
/// that part is text-like, the value of `field` will be converted into
/// a string using the `IntoAttributeValue` trait of `target_ty`.
/// a string using the `IntoOptionalXmlText` trait of `target_ty`.
///
/// Otherwise, the value is used nearly as-is and forwarded to the
/// implementation returned by [`Compound::build_into_element`].
@ -172,7 +172,7 @@ impl ExtractDef {
Compound::Struct { ref fields, .. } => {
if fields[0].kind.is_text_like() {
quote! {
<#target_ty as ::xmpp_parsers_core::exports::minidom::IntoAttributeValue>::into_attribute_value(#field)
<#target_ty as ::xmpp_parsers_core::IntoOptionalXmlText>::into_optional_xml_text(#field)
}
} else {
quote! { Some(#field) }

View File

@ -308,7 +308,7 @@ impl FieldDef {
FieldKind::Child(field) => field.build_try_from_element(name, tempname, ident, ty),
FieldKind::Text => Ok(FieldParsePart {
tempinit: quote! {
let #tempname: #ty = <#ty as ::xmpp_parsers_core::FromText>::from_text(&residual.text())?;
let #tempname: #ty = <#ty as ::xmpp_parsers_core::FromXmlText>::from_xml_text(&residual.text())?;
},
value: quote! { #tempname },
..FieldParsePart::default()
@ -348,7 +348,7 @@ impl FieldDef {
FieldKind::Attribute(field) => field.build_into_element(ident, ty),
FieldKind::Text => Ok(quote! {
{
let __text = <#ty as ::xmpp_parsers_core::IntoText>::into_text(#ident);
let __text = <#ty as ::xmpp_parsers_core::IntoXmlText>::into_xml_text(#ident);
if __text.len() > 0 {
builder.append(::xmpp_parsers_core::exports::minidom::Node::Text(__text))
} else {