mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-18 05:35:57 +02:00
decbeb351f
This simplifies the code of FieldDef and makes it more extensible for future ideas.
181 lines
5.7 KiB
Rust
181 lines
5.7 KiB
Rust
//! Infrastructure for parsing fields from attributes.
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::{quote, quote_spanned};
|
|
use syn::{spanned::Spanned, *};
|
|
|
|
use crate::error_message::{self, ParentRef};
|
|
use crate::meta::{FlagOr, Name, NameRef};
|
|
|
|
use super::{Field, FieldParsePart};
|
|
|
|
/// A field parsed from an XML attribute.
|
|
///
|
|
/// Maps to `#[xml(attribute)]`.
|
|
#[derive(Debug)]
|
|
pub(crate) struct AttributeField {
|
|
/// The XML name of the attribute.
|
|
///
|
|
/// *Note:* Namespaced attributes are currently not supported.
|
|
pub(super) name: Name,
|
|
|
|
/// Whether [`Default`] or a given callable should be used to obtain a
|
|
/// value if the attribute is missing.
|
|
///
|
|
/// If the flag is *not* set, an error is returned when parsing an element
|
|
/// without the attribute.
|
|
pub(super) default_: FlagOr<Path>,
|
|
|
|
/// The codec implementation to use.
|
|
///
|
|
/// If set, parsing does not use the `FromXmlText` / `IntoXmlText` traits
|
|
/// but instead uses the `TextCodec` trait on the given type.
|
|
pub(super) codec: Option<Type>,
|
|
}
|
|
|
|
impl AttributeField {
|
|
/// Construct a new `#[xml(attribute)]` field.
|
|
///
|
|
/// The `field_ident` must be the field's identifier (if in a named
|
|
/// compound).
|
|
///
|
|
/// `name` must be the XML name assigned to the attribtue, if any, as
|
|
/// parsed from the `#[xml(..)]` meta on the field.
|
|
///
|
|
/// `default_on_missing` must be the `default` flag as parsed from the
|
|
/// `#[xml(..)]` meta on the field.
|
|
///
|
|
/// `codec` must be the value of the `codec = ..` option, which, if given
|
|
/// overrides how the attribute's text is converted to a rust value.
|
|
///
|
|
/// `attr_span` is used for emitting error messages when no better span
|
|
/// can be constructed. This should point at the `#[xml(..)]` meta of the
|
|
/// field or another closely-related object.
|
|
pub(super) fn new(
|
|
attr_span: &Span,
|
|
field_ident: Option<&Ident>,
|
|
name: Option<NameRef>,
|
|
default_: FlagOr<Path>,
|
|
codec: Option<Type>,
|
|
) -> Result<Self> {
|
|
let name = name
|
|
.map(Name::Lit)
|
|
.or_else(|| field_ident.map(|ident| Name::Ident(ident.clone())));
|
|
let Some(name) = name else {
|
|
return Err(Error::new(attr_span.clone(), "missing attribute name on unnamed field. specify using #[xml(attribute = \"foo\")]"));
|
|
};
|
|
|
|
Ok(Self {
|
|
name,
|
|
default_,
|
|
codec,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Field for AttributeField {
|
|
fn build_try_from_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
_tempname: Ident,
|
|
member: &Member,
|
|
ty: &Type,
|
|
) -> Result<FieldParsePart> {
|
|
let missing_msg = error_message::on_missing_attribute(container_name, &member);
|
|
let name = &self.name;
|
|
let on_missing = match self.default_ {
|
|
FlagOr::Absent => {
|
|
quote! {
|
|
return Err(::xmpp_parsers_core::error::Error::ParseError(
|
|
#missing_msg,
|
|
))
|
|
}
|
|
}
|
|
FlagOr::Present(_) => {
|
|
let ty_default =
|
|
quote_spanned! {ty.span()=> <#ty as ::std::default::Default>::default};
|
|
quote! {
|
|
#ty_default()
|
|
}
|
|
}
|
|
FlagOr::Value { ref value, .. } => {
|
|
quote! {
|
|
#value()
|
|
}
|
|
}
|
|
};
|
|
let decode = match self.codec {
|
|
Some(ref codec_ty) => {
|
|
let codec_ty_decode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xmpp_parsers_core::TextCodec::<#ty>>::decode};
|
|
quote! {
|
|
residual.attr(#name).map(|value| {
|
|
#codec_ty_decode(value)
|
|
}).transpose()?
|
|
}
|
|
}
|
|
None => {
|
|
let ty_from_optional_xml_text = quote_spanned! {ty.span()=> <#ty as ::xmpp_parsers_core::FromOptionalXmlText>::from_optional_xml_text};
|
|
quote! {
|
|
#ty_from_optional_xml_text(residual.attr(#name))?
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(FieldParsePart {
|
|
attrcheck: quote! {
|
|
if key == #name {
|
|
continue;
|
|
}
|
|
},
|
|
value: quote! {
|
|
match #decode {
|
|
Some(v) => v,
|
|
None => #on_missing,
|
|
}
|
|
},
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
_member: &Member,
|
|
ty: &Type,
|
|
access: Expr,
|
|
) -> Result<TokenStream> {
|
|
let encode = match self.codec {
|
|
Some(ref codec_ty) => {
|
|
let codec_ty_encode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xmpp_parsers_core::TextCodec::<#ty>>::encode};
|
|
quote! {
|
|
#codec_ty_encode(#access)
|
|
}
|
|
}
|
|
None => {
|
|
let ty_into_optional_xml_text = quote_spanned! {ty.span()=> <#ty as ::xmpp_parsers_core::IntoOptionalXmlText>::into_optional_xml_text};
|
|
quote! {
|
|
#ty_into_optional_xml_text(#access)
|
|
}
|
|
}
|
|
};
|
|
let name = &self.name;
|
|
Ok(quote! {
|
|
match #encode {
|
|
Some(v) => builder.attr(#name, v),
|
|
None => builder,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn build_set_namespace(
|
|
&self,
|
|
_input: &Ident,
|
|
_ty: &Type,
|
|
_access: Expr,
|
|
) -> Result<TokenStream> {
|
|
Ok(TokenStream::default())
|
|
}
|
|
}
|