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

parsers-macros: add support for custom defaulting

This adds support for using a custom function to create a default value
during parsing instead of relying on [`std::default::Default`].
This commit is contained in:
Jonas Schäfer 2024-03-31 14:47:11 +02:00
parent 953de151c8
commit eae3efc1de
7 changed files with 222 additions and 98 deletions

View File

@ -250,9 +250,11 @@ The following field kinds are available:
set, the field's identifier is used.
- `namespace = ..`: The XML namespace of the attribute. This is optional,
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 [`FromOptionalXmlText`].
- `default`, `default = ..`: If set, a field value is generated if the
attribute is not present and [`FromOptionalXmlText`] did not create a
value from [`None`], instead of failing to parse. If the optional
argument is present, it must be the path to a callable which returns the
field's type. Otherwise, [`std::default::Default::default`] is used.
- `codec = ..`: Path to a type implementing [`TextCodec`] to use instead
of the [`FromOptionalXmlText`] / [`IntoOptionalXmlText`] implementation
of the field's type.
@ -284,9 +286,13 @@ The following field kinds are available:
completely.
This should often be combined with `default`.
- `default`: If the child is not present, the field value will be created
using [`Default::default`]. For `extract(..)`, this is even required when
using `Option<..>` as a field type.
- `default`, `default = ..`: If set, a field value is generated if the
child is not present instead of failing to parse. If the optional argument
is present, it must be the path to a callable which returns the field's
type. Otherwise, [`std::default::Default::default`] is used.
*Note:* When using `extract(..)`, this is required even when the field's
type is `Option<..>`.
- `child`: Extract an entire child element. The field type must
implement [`FromXml`] (for [`FromXml`]) or `IntoXml` (for
@ -331,9 +337,10 @@ The following field kinds are available:
- `name = ..` : The XML name of the element to match.
- `namespace = ..`: The XML namespace of the element to match.
- `default`: If the element cannot be found, initialize the field using
[`std::default::Default`] instead of emitting an error. The type must
implement `Default` for that (in addition to the other required traits).
- `default`, `default = ..`: If set, a field value is generated if the
child is not present instead of failing to parse. If the optional argument
is present, it must be the path to a callable which returns the field's
type. Otherwise, [`std::default::Default::default`] is used.
If the field converts into `None` when invoking `Into<Option<Element>>`,
the element is omitted from the output altogether.

View File

@ -5,7 +5,7 @@ use quote::quote;
use syn::*;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, Name, NameRef};
use crate::meta::{FlagOr, Name, NameRef};
use super::FieldParsePart;
@ -19,12 +19,12 @@ pub(crate) struct AttributeField {
/// *Note:* Namespaced attributes are currently not supported.
pub(super) name: Name,
/// Whether [`Default`] should be used to obtain a value f the attribute
/// is missing.
/// 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_on_missing: Flag,
pub(super) default_: FlagOr<Path>,
/// The codec implementation to use.
///
@ -55,7 +55,7 @@ impl AttributeField {
attr_span: &Span,
field_ident: Option<&Ident>,
name: Option<NameRef>,
default_on_missing: Flag,
default_: FlagOr<Path>,
codec: Option<Type>,
) -> Result<Self> {
let name = name
@ -67,7 +67,7 @@ impl AttributeField {
Ok(Self {
name,
default_on_missing,
default_,
codec,
})
}
@ -86,15 +86,23 @@ impl AttributeField {
) -> Result<FieldParsePart> {
let missing_msg = error_message::on_missing_attribute(name, &ident);
let name = self.name;
let on_missing = if self.default_on_missing.is_set() {
quote! {
<#ty as ::std::default::Default>::default()
let on_missing = match self.default_ {
FlagOr::Absent => {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(
#missing_msg,
))
}
}
} else {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(
#missing_msg,
))
FlagOr::Present(_) => {
quote! {
<#ty as ::std::default::Default>::default()
}
}
FlagOr::Value { value, .. } => {
quote! {
#value()
}
}
};
let decode = match self.codec {

View File

@ -7,7 +7,7 @@ use syn::*;
use crate::compound::Compound;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
use crate::meta::{FlagOr, NameRef, NamespaceRef, XmlFieldMeta};
use super::{ChildMode, FieldDef, FieldNamespace, FieldParsePart};
@ -236,9 +236,9 @@ pub(crate) struct ChildField {
extract: Option<ExtractDef>,
/// If set, the field's value will be generated using
/// [`std::default::Default`] if no matching child can be found, instead
/// of aborting parsing with an error.
default_on_missing: Flag,
/// [`std::default::Default`] or the given path if no matching child can
/// be found, instead of aborting parsing with an error.
default_: FlagOr<Path>,
/// If set, it must point to a function. That function will be called with
/// an immutable reference to the field's value and must return a boolean.
@ -259,8 +259,7 @@ impl ChildField {
/// Otherwise, if no extract is intended, `namespace` and `name` must be
/// `None` and `extract` must be empty.
///
/// The `default_on_missing` flag stored, see [`Self::default_on_missing`]
/// for semantics.
/// The `default_` flag stored, see [`Self::default_`] for semantics.
///
/// `field_type` must be the type of the field. It is used to configure
/// the extract correctly, if it is specified and the mode is single.
@ -274,7 +273,7 @@ impl ChildField {
namespace: Option<NamespaceRef>,
name: Option<NameRef>,
extract: Vec<Box<XmlFieldMeta>>,
default_on_missing: Flag,
default_: FlagOr<Path>,
skip_if: Option<Path>,
field_type: &Type,
) -> Result<Self> {
@ -323,7 +322,7 @@ impl ChildField {
single_extract_type,
)?),
skip_if,
default_on_missing,
default_,
})
} else {
if let Some(namespace) = namespace {
@ -341,7 +340,7 @@ impl ChildField {
Ok(Self {
mode,
extract: None,
default_on_missing,
default_,
skip_if,
})
}
@ -379,13 +378,21 @@ impl ChildField {
ChildMode::Single => {
let missingerr = error_message::on_missing_child(name, &ident);
let duperr = error_message::on_duplicate_child(name, &ident);
let on_missing = if self.default_on_missing.is_set() {
quote! {
<#ty as ::std::default::Default>::default()
let on_missing = match self.default_ {
FlagOr::Absent => {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(#missingerr));
}
}
} else {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(#missingerr));
FlagOr::Present(_) => {
quote! {
<#ty as ::std::default::Default>::default()
}
}
FlagOr::Value { value, .. } => {
quote! {
#value()
}
}
};
match self.extract {

View File

@ -6,7 +6,7 @@ use quote::quote;
use syn::*;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, Name, NameRef, NamespaceRef, StaticNamespace};
use crate::meta::{FlagOr, Name, NameRef, NamespaceRef, StaticNamespace};
use super::FieldParsePart;
@ -23,10 +23,10 @@ pub(crate) struct ElementField {
name: Name,
/// If set, the field value will be generated using
/// [`std::default::Default`] if no matching child element is encountered
/// during parsing. If unset, an error is generated instead and parsing of
/// the parent element fails.
default_on_missing: Flag,
/// [`std::default::Default`] or the given callable if no matching child
/// element is encountered during parsing. If unset, an error is generated
/// instead and parsing of the parent element fails.
default_: FlagOr<Path>,
}
impl ElementField {
@ -46,7 +46,7 @@ impl ElementField {
attr_span: &Span,
namespace: Option<NamespaceRef>,
name: Option<NameRef>,
default_on_missing: Flag,
default_: FlagOr<Path>,
) -> Result<Self> {
let namespace = match namespace {
None => {
@ -81,7 +81,7 @@ impl ElementField {
Ok(Self {
namespace,
name: name.into(),
default_on_missing,
default_,
})
}
@ -111,13 +111,21 @@ impl ElementField {
let field_namespace = self.namespace;
let missingerr = error_message::on_missing_child(name, &ident);
let duperr = error_message::on_duplicate_child(name, &ident);
let on_missing = if self.default_on_missing.is_set() {
quote! {
<#ty as ::std::default::Default>::default()
let on_missing = match self.default_ {
FlagOr::Absent => {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(#missingerr));
}
}
} else {
quote! {
return Err(::xmpp_parsers_core::error::Error::ParseError(#missingerr));
FlagOr::Present(_) => {
quote! {
<#ty as ::std::default::Default>::default()
}
}
FlagOr::Value { value, .. } => {
quote! {
#value()
}
}
};
Ok(FieldParsePart {

View File

@ -174,7 +174,7 @@ impl FieldKind {
match meta {
XmlFieldMeta::Attribute {
name,
default_on_missing,
default_,
codec,
ty,
} => {
@ -185,7 +185,7 @@ impl FieldKind {
span,
field_ident,
name,
default_on_missing,
default_,
codec,
)?))
}
@ -194,17 +194,10 @@ impl FieldKind {
namespace,
name,
extract,
default_on_missing,
default_,
skip_if,
} => Ok(FieldKind::Child(ChildField::new(
span,
mode,
namespace,
name,
extract,
default_on_missing,
skip_if,
field_ty,
span, mode, namespace, name, extract, default_, skip_if, field_ty,
)?)),
XmlFieldMeta::Text { codec, ty } => {
if let Some(ty) = ty {
@ -216,12 +209,9 @@ impl FieldKind {
XmlFieldMeta::Element {
namespace,
name,
default_on_missing,
default_,
} => Ok(FieldKind::Element(ElementField::new(
span,
namespace,
name,
default_on_missing,
span, namespace, name, default_,
)?)),
XmlFieldMeta::Elements { namespace, name } => Ok(FieldKind::Elements(
ElementsField::new(span, namespace, name)?,

View File

@ -102,6 +102,33 @@ impl<'a> MetaParse for ParseFlag<'a> {
}
}
/// Parse a [`FlagOr`] from a meta.
struct ParseFlagOr<'a, T>(&'static str, &'a mut FlagOr<T>);
impl<'a, T: parse::Parse> MetaParse for ParseFlagOr<'a, T> {
fn name(&self) -> &'static str {
self.0
}
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
if self.1.is_set() {
return Err(Error::new_spanned(
meta.path,
format!("flag {} is already set", self.name()),
));
}
if meta.input.peek(Token![=]) {
*self.1 = FlagOr::Value {
span: meta.path.span(),
value: meta.value()?.parse()?,
};
} else {
*self.1 = FlagOr::Present(meta.path.span());
}
Ok(())
}
}
/// Parse any parseable value from a meta.
struct ParseValue<'a, T: parse::Parse>(&'static str, &'a mut Option<T>);
@ -198,6 +225,61 @@ impl<T: Spanned> From<T> for Flag {
}
}
/// A flag with an optional value.
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) enum FlagOr<T> {
/// The flag is not set.
Absent,
/// The flag was set.
Present(
/// The span of the syntax element which enabled the flag.
///
/// This is used to generate useful error messages by pointing at the
/// specific place the flag was activated.
Span,
),
/// A value was assigned.
Value {
/// The span of the left hand side of the assignment.
span: Span,
/// The actual value.
value: T,
},
}
impl<T> FlagOr<T> {
/// Return true if the flag is set, false otherwise.
pub fn is_set(&self) -> bool {
match self {
Self::Absent => false,
Self::Present(_) => true,
Self::Value { .. } => true,
}
}
/// Obtain the span of the path setting the flag or value.
pub fn span(&self) -> Option<Span> {
match self {
Self::Absent => None,
Self::Present(ref span) => Some(*span),
Self::Value { ref span, .. } => Some(*span),
}
}
/// Obtain the span of the path setting the flag or value.
pub fn into_span(self) -> Option<Span> {
match self {
Self::Absent => None,
Self::Present(span) => Some(span),
Self::Value { span, .. } => Some(span),
}
}
}
/// Type alias for a XML namespace setting.
///
/// This may in the future be replaced by an enum supporting both `Path` and
@ -547,7 +629,7 @@ struct AttributeMeta {
/// The presence of the `default` flag a potentially nested `#[xml(..)]`,
/// if any.
default_on_missing: Flag,
default_: FlagOr<Path>,
/// The value assigned to `type` inside a potentially nested
/// `#[xml(..)]`, if any.
@ -579,7 +661,7 @@ impl AttributeMeta {
Ok(Self {
name: Some(name),
namespace: None,
default_on_missing: Flag::Absent,
default_: FlagOr::Absent,
codec: None,
ty: None,
})
@ -587,7 +669,7 @@ impl AttributeMeta {
let mut name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
let mut ty: Option<Type> = None;
let mut default_on_missing: Flag = Flag::Absent;
let mut default_: FlagOr<Path> = FlagOr::Absent;
let mut codec: Option<Type> = None;
meta.parse_nested_meta(|meta| {
parse_meta!(
@ -596,13 +678,13 @@ impl AttributeMeta {
ParseValue("namespace", &mut namespace),
ParseValue("type", &mut ty),
ParseValue("codec", &mut codec),
ParseFlag("default", &mut default_on_missing),
ParseFlagOr("default", &mut default_),
)
})?;
Ok(Self {
namespace,
name,
default_on_missing,
default_,
codec,
ty,
})
@ -610,7 +692,7 @@ impl AttributeMeta {
Ok(Self {
namespace: None,
name: None,
default_on_missing: Flag::Absent,
default_: FlagOr::Absent,
codec: None,
ty: None,
})
@ -637,8 +719,8 @@ impl AttributeMeta {
/// Emit an error with the given message if the codec is set
fn reject_default(&self, msg: impl Into<String>) -> Result<()> {
if let Flag::Present(path) = &self.default_on_missing {
Err(Error::new(*path, msg.into()))
if let Some(span) = self.default_.span() {
Err(Error::new(span, msg.into()))
} else {
Ok(())
}
@ -672,8 +754,8 @@ pub(crate) enum XmlFieldMeta {
/// `attribute` in the shorthand syntax.
name: Option<NameRef>,
/// Presence of the `default` flag.
default_on_missing: Flag,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
/// Contents of the `codec = ..` option.
codec: Option<Type>,
@ -703,8 +785,8 @@ pub(crate) enum XmlFieldMeta {
/// Contents of the `extract(..)` option.
extract: Vec<Box<XmlFieldMeta>>,
/// Presence of the `default` flag.
default_on_missing: Flag,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
/// Contents of the `skip_if = ..` option.
skip_if: Option<Path>,
@ -752,8 +834,8 @@ pub(crate) enum XmlFieldMeta {
/// Contents of the `name = ..` option.
name: Option<NameRef>,
/// Presence of the `default` flag.
default_on_missing: Flag,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
},
/// Maps the field to the presence of an empty XML child element.
@ -789,7 +871,7 @@ impl XmlFieldMeta {
}
Ok(Self::Attribute {
name: meta.name,
default_on_missing: meta.default_on_missing,
default_: meta.default_,
codec: meta.codec,
ty: meta.ty,
})
@ -803,7 +885,7 @@ impl XmlFieldMeta {
Option<NamespaceRef>,
Option<NameRef>,
Vec<Box<XmlFieldMeta>>,
Flag,
FlagOr<Path>,
Option<Path>,
)> {
if meta.input.peek(token::Paren) {
@ -811,34 +893,33 @@ impl XmlFieldMeta {
let mut namespace: Option<NamespaceRef> = None;
let mut extract: Vec<Box<XmlFieldMeta>> = Vec::new();
let mut skip_if: Option<Path> = None;
let mut default_on_missing = Flag::Absent;
let mut default_ = FlagOr::Absent;
meta.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("name", &mut name),
ParseValue("namespace", &mut namespace),
ParseExtracts(&mut extract),
ParseFlag("default", &mut default_on_missing),
ParseFlagOr("default", &mut default_),
ParseValue("skip_if", &mut skip_if),
)
})?;
Ok((namespace, name, extract, default_on_missing, skip_if))
Ok((namespace, name, extract, default_, skip_if))
} else {
Ok((None, None, Vec::new(), Flag::Absent, None))
Ok((None, None, Vec::new(), FlagOr::Absent, None))
}
}
/// Processes a `#[xml(child)]` meta, creating a [`Self::Child`]
/// variant with [`ChildMode::Single`].
fn child_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let (namespace, name, extract, default_on_missing, skip_if) =
Self::child_common_from_meta(meta)?;
let (namespace, name, extract, default_, skip_if) = Self::child_common_from_meta(meta)?;
Ok(Self::Child {
mode: ChildMode::Single,
namespace,
name,
extract,
default_on_missing,
default_,
skip_if,
})
}
@ -846,17 +927,16 @@ impl XmlFieldMeta {
/// Processes a `#[xml(children)]` meta, creating a [`Self::Child`]
/// variant with [`ChildMode::Collection`].
fn children_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let (namespace, name, extract, default_on_missing, skip_if) =
Self::child_common_from_meta(meta)?;
if let Flag::Present(default_on_missing) = default_on_missing {
return Err(syn::Error::new(default_on_missing, "default cannot be used on #[xml(children)] (it is implied, the default is the empty container)"));
let (namespace, name, extract, default_, skip_if) = Self::child_common_from_meta(meta)?;
if let Some(default_) = default_.into_span() {
return Err(syn::Error::new(default_, "default cannot be used on #[xml(children)] (it is implied, the default is the empty container)"));
}
Ok(Self::Child {
mode: ChildMode::Collection,
namespace,
name,
extract,
default_on_missing: Flag::Absent,
default_: FlagOr::Absent,
skip_if,
})
}
@ -894,7 +974,7 @@ impl XmlFieldMeta {
Ok(Self::Element {
name: meta.name,
namespace: meta.namespace,
default_on_missing: meta.default_on_missing,
default_: meta.default_,
})
}
@ -915,9 +995,9 @@ impl XmlFieldMeta {
fn flag_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let meta = AttributeMeta::parse_from_meta(meta)?;
meta.reject_type("type = .. is not allowed on #[xml(flag)]")?;
if let Flag::Present(default_on_missing) = meta.default_on_missing {
if let Some(span) = meta.default_.into_span() {
return Err(syn::Error::new(
default_on_missing,
span,
"default cannot be used on #[xml(flag)] (it is implied, the default false)",
));
}

View File

@ -1000,3 +1000,27 @@ fn child_extract_skip_if_roundtrip_present() {
"<skip-if xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo>10</foo></skip-if>",
);
}
fn u8_255() -> u8 {
255
}
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
#[xml(namespace = self::TEST_NS1, name = "default-func")]
pub struct DefaultFunc {
#[xml(attribute(default = u8_255))]
pub attr: u8,
#[xml(child(namespace = self::TEST_NS1, name = "value", extract(text), default = u8_255))]
pub child: u8,
}
#[test]
fn default_func_generates_values() {
let v = crate::util::test::parse_str::<DefaultFunc>(
"<default-func xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
)
.expect("parse");
assert_eq!(v.attr, 255);
assert_eq!(v.child, 255);
}