xmpp-rs/xso-proc/src/field/attribute.rs

181 lines
5.6 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(::xso::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 ::xso::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 ::xso::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 ::xso::TextCodec::<#ty>>::encode};
quote! {
#codec_ty_encode(#access)
}
}
None => {
let ty_into_optional_xml_text = quote_spanned! {ty.span()=> <#ty as ::xso::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())
}
}