xmpp-rs/xso-proc/src/meta.rs
2024-08-05 15:33:57 +02:00

1046 lines
35 KiB
Rust

// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! # Parse Rust attributes
//!
//! This module is concerned with parsing attributes from the Rust "meta"
//! annotations on structs, enums, enum variants and fields.
use core::hash::{Hash, Hasher};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
use rxml_validation::NcName;
/// XML core namespace URI (for the `xml:` prefix)
pub const XMLNS_XML: &str = "http://www.w3.org/XML/1998/namespace";
/// XML namespace URI (for the `xmlns:` prefix)
pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";
macro_rules! reject_key {
($key:ident not on $not_allowed_on:literal only on $only_allowed_on:literal) => {
if let Some($key) = $key {
return Err(Error::new_spanned(
$key,
concat!(
"`",
stringify!($key),
"` is not allowed on ",
$not_allowed_on,
" (only on ",
$only_allowed_on,
")"
),
));
}
};
($key:ident flag not on $not_allowed_on:literal only on $only_allowed_on:literal) => {
if let Flag::Present($key) = $key {
return Err(Error::new(
$key,
concat!(
"`",
stringify!($key),
"` is not allowed on ",
$not_allowed_on,
" (only on ",
$only_allowed_on,
")"
),
));
}
};
}
pub(crate) use reject_key;
/// Value for the `#[xml(namespace = ..)]` attribute.
#[derive(Debug, Clone)]
pub(crate) enum NamespaceRef {
/// The XML namespace is specified as a string literal.
LitStr(LitStr),
/// The XML namespace is specified as a path.
Path(Path),
}
impl NamespaceRef {
fn fudge(value: &str, span: Span) -> Self {
Self::LitStr(LitStr::new(value, span))
}
}
impl syn::parse::Parse for NamespaceRef {
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
if input.peek(syn::LitStr) {
Ok(Self::LitStr(input.parse()?))
} else {
Ok(Self::Path(input.parse()?))
}
}
}
impl quote::ToTokens for NamespaceRef {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::LitStr(ref lit) => lit.to_tokens(tokens),
Self::Path(ref path) => path.to_tokens(tokens),
}
}
}
/// Value for the `#[xml(name = .. )]` attribute.
#[derive(Debug, Clone)]
pub(crate) enum NameRef {
/// The XML name is specified as a string literal.
Literal {
/// The validated XML name.
value: NcName,
/// The span of the original [`syn::LitStr`].
span: Span,
},
/// The XML name is specified as a path.
Path(Path),
}
impl Hash for NameRef {
fn hash<H: Hasher>(&self, h: &mut H) {
match self {
Self::Literal { ref value, .. } => value.hash(h),
Self::Path(ref path) => path.hash(h),
}
}
}
impl PartialEq for NameRef {
fn eq(&self, other: &NameRef) -> bool {
match self {
Self::Literal {
value: ref my_value,
..
} => match other {
Self::Literal {
value: ref other_value,
..
} => my_value == other_value,
_ => false,
},
Self::Path(ref my_path) => match other {
Self::Path(ref other_path) => my_path == other_path,
_ => false,
},
}
}
}
impl Eq for NameRef {}
impl syn::parse::Parse for NameRef {
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
if input.peek(syn::LitStr) {
let s: LitStr = input.parse()?;
let span = s.span();
match NcName::try_from(s.value()) {
Ok(value) => Ok(Self::Literal { value, span }),
Err(e) => Err(Error::new(span, format!("not a valid XML name: {}", e))),
}
} else {
let p: Path = input.parse()?;
Ok(Self::Path(p))
}
}
}
impl quote::ToTokens for NameRef {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Literal { ref value, span } => {
let span = *span;
let value = value.as_str();
let value = quote_spanned! { span=> #value };
// SAFETY: self.0 is a known-good NcName, so converting it to an
// NcNameStr is known to be safe.
// NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
// block as that would then in fact trip a `#[deny(unsafe_code)]` lint
// at the use site of the macro.
tokens.extend(quote! {
unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
})
}
Self::Path(ref path) => path.to_tokens(tokens),
}
}
}
/// Represents the amount constraint used with child elements.
///
/// Currently, this only supports "one" (literal `1`) or "any amount" (`..`).
/// In the future, we might want to add support for any range pattern for
/// `usize` and any positive integer literal.
#[derive(Debug)]
pub(crate) enum AmountConstraint {
/// Equivalent to `1`
#[allow(dead_code)]
FixedSingle(Span),
/// Equivalent to `..`.
Any(Span),
}
impl syn::parse::Parse for AmountConstraint {
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
if input.peek(LitInt) && !input.peek2(token::DotDot) && !input.peek2(token::DotDotEq) {
let lit: LitInt = input.parse()?;
let value: usize = lit.base10_parse()?;
if value == 1 {
Ok(Self::FixedSingle(lit.span()))
} else {
Err(Error::new(lit.span(), "only `1` and `..` are allowed here"))
}
} else {
let p: PatRange = input.parse()?;
if let Some(attr) = p.attrs.first() {
return Err(Error::new_spanned(attr, "attributes not allowed here"));
}
if let Some(start) = p.start.as_ref() {
return Err(Error::new_spanned(
start,
"only full ranges (`..`) are allowed here",
));
}
if let Some(end) = p.end.as_ref() {
return Err(Error::new_spanned(
end,
"only full ranges (`..`) are allowed here",
));
}
Ok(Self::Any(p.span()))
}
}
}
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
#[derive(Clone, Copy, Debug)]
pub(crate) enum Flag {
/// 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.
#[allow(dead_code)]
Span,
),
}
impl Flag {
/// Return true if the flag is set, false otherwise.
pub(crate) fn is_set(&self) -> bool {
match self {
Self::Absent => false,
Self::Present(_) => true,
}
}
}
impl<T: Spanned> From<T> for Flag {
fn from(other: T) -> Flag {
Flag::Present(other.span())
}
}
/// A pair of `namespace` and `name` keys.
#[derive(Debug, Default)]
pub(crate) struct QNameRef {
/// The XML namespace supplied.
pub(crate) namespace: Option<NamespaceRef>,
/// The XML name supplied.
pub(crate) name: Option<NameRef>,
}
impl QNameRef {
/// Attempt to incrementally parse this QNameRef.
///
/// If `meta` contains either `namespace` or `name` keys, they are
/// processed and either `Ok(None)` or an error is returned.
///
/// If no matching key is found, `Ok(Some(meta))` is returned for further
/// processing.
fn parse_incremental_from_meta<'x>(
&mut self,
meta: ParseNestedMeta<'x>,
) -> Result<Option<ParseNestedMeta<'x>>> {
if meta.path.is_ident("name") {
if self.name.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
}
let value = meta.value()?;
let name_span = value.span();
let (new_namespace, new_name) = parse_prefixed_name(value)?;
if let Some(new_namespace) = new_namespace {
if let Some(namespace) = self.namespace.as_ref() {
let mut error = Error::new(
name_span,
"cannot combine `namespace` key with prefixed `name`",
);
error.combine(Error::new_spanned(namespace, "`namespace` was set here"));
return Err(error);
}
self.namespace = Some(new_namespace);
}
self.name = Some(new_name);
Ok(None)
} else if meta.path.is_ident("namespace") {
if self.namespace.is_some() {
return Err(Error::new_spanned(
meta.path,
"duplicate `namespace` key or `name` key has prefix",
));
}
self.namespace = Some(meta.value()?.parse()?);
Ok(None)
} else {
Ok(Some(meta))
}
}
}
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
#[derive(Debug)]
pub(crate) struct XmlCompoundMeta {
/// The span of the `#[xml(..)]` meta from which this was parsed.
///
/// This is useful for error messages.
pub(crate) span: Span,
/// The value assigned to `namespace` and `name` fields inside
/// `#[xml(..)]`, if any.
pub(crate) qname: QNameRef,
/// The debug flag.
pub(crate) debug: Flag,
/// The value assigned to `builder` inside `#[xml(..)]`, if any.
pub(crate) builder: Option<Ident>,
/// The value assigned to `iterator` inside `#[xml(..)]`, if any.
pub(crate) iterator: Option<Ident>,
/// The exhaustive flag.
pub(crate) exhaustive: Flag,
}
impl XmlCompoundMeta {
/// Parse the meta values from a `#[xml(..)]` attribute.
///
/// Undefined options or options with incompatible values are rejected
/// with an appropriate compile-time error.
fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
let mut qname = QNameRef::default();
let mut builder = None;
let mut iterator = None;
let mut debug = Flag::Absent;
let mut exhaustive = Flag::Absent;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("debug") {
if debug.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `debug` key"));
}
debug = (&meta.path).into();
Ok(())
} else if meta.path.is_ident("builder") {
if builder.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `builder` key"));
}
builder = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("iterator") {
if iterator.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `iterator` key"));
}
iterator = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("exhaustive") {
if exhaustive.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key"));
}
exhaustive = (&meta.path).into();
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
}
}
})?;
Ok(Self {
span: attr.span(),
qname,
debug,
builder,
iterator,
exhaustive,
})
}
/// Search through `attrs` for a single `#[xml(..)]` attribute and parse
/// it.
///
/// Undefined options or options with incompatible values are rejected
/// with an appropriate compile-time error.
///
/// If more than one `#[xml(..)]` attribute is found, an error is
/// emitted.
///
/// If no `#[xml(..)]` attribute is found, `None` is returned.
pub(crate) fn try_parse_from_attributes(attrs: &[Attribute]) -> Result<Option<Self>> {
let mut result = None;
for attr in attrs {
if !attr.path().is_ident("xml") {
continue;
}
if result.is_some() {
return Err(syn::Error::new_spanned(
attr.path(),
"only one #[xml(..)] per struct or enum variant allowed",
));
}
result = Some(Self::parse_from_attribute(attr)?);
}
Ok(result)
}
/// Search through `attrs` for a single `#[xml(..)]` attribute and parse
/// it.
///
/// Undefined options or options with incompatible values are rejected
/// with an appropriate compile-time error.
///
/// If more than one or no `#[xml(..)]` attribute is found, an error is
/// emitted.
pub(crate) fn parse_from_attributes(attrs: &[Attribute]) -> Result<Self> {
match Self::try_parse_from_attributes(attrs)? {
Some(v) => Ok(v),
None => Err(syn::Error::new(
Span::call_site(),
"#[xml(..)] attribute required on struct or enum variant",
)),
}
}
}
/// Return true if the tokens the cursor points at are a valid type path
/// prefix.
///
/// This does not advance the parse stream.
///
/// If the tokens *do* look like a type path, a Span which points at the first
/// `<` encountered is returned. This can be used for a helpful error message
/// in case parsing the type path does then fail.
fn maybe_type_path(p: parse::ParseStream<'_>) -> (bool, Option<Span>) {
// ParseStream cursors do not advance the stream, but they are also rather
// unwieldly to use. Prepare for a lot of `let .. = ..`.
let cursor = if p.peek(token::PathSep) {
// If we have a path separator, we need to skip that initially. We
// do this by skipping two punctuations. We use unwrap() here because
// we already know for sure that we see two punctuation items (because
// of the peek).
p.cursor().punct().unwrap().1.punct().unwrap().1
} else {
// No `::` initially, so we just take what we have.
p.cursor()
};
// Now we loop over `$ident::` segments. If we find anything but a `:`
// after the ident, we exit. Depending on *what* we find, we either exit
// true or false, but see for yourself.
let mut cursor = cursor;
loop {
// Here we look for the identifier, but we do not care for its
// contents.
let Some((_, new_cursor)) = cursor.ident() else {
return (false, None);
};
cursor = new_cursor;
// Now we see what actually follows the ident (it must be punctuation
// for it to be a type path...)
let Some((punct, new_cursor)) = cursor.punct() else {
return (false, None);
};
cursor = new_cursor;
match punct.as_char() {
// Looks like a `foo<..`, we treat that as a type path for the
// reasons stated in [`parse_codec_expr`]'s doc.
'<' => return (true, Some(punct.span())),
// Continue looking ahead: looks like a path separator.
':' => (),
// Anything else (such as `,` (separating another argument most
// likely), or `.` (a method call?)) we treat as "not a type
// path".
_ => return (false, None),
}
// If we are here, we saw a `:`. Look for the second one.
let Some((punct, new_cursor)) = cursor.punct() else {
return (false, None);
};
cursor = new_cursor;
if punct.as_char() != ':' {
// If it is not another `:`, it cannot be a type path.
return (false, None);
}
// And round and round and round it goes.
// We will terminate eventually because the cursor will return None
// on any of the lookups because parse streams are (hopefully!)
// finite. Most likely, we'll however encounter a `<` or other non-`:`
// punctuation first.
}
}
/// Parse expressions passed to `codec`.
///
/// Those will generally be paths to unit type constructors (such as `Foo`)
/// or references to static values or chains of function calls.
///
/// In the case of unit type constructors for generic types, users may type
/// for example `FixedHex<20>`, thinking they are writing a type path. However,
/// while `FixedHex<20>` is indeed a valid type path, it is not a valid
/// expression for a unit type constructor. Instead it is parsed as
/// `FixedHex < 20` and then a syntax error.
///
/// We however know that `Foo < Bar` is never a valid expression for a type.
/// Thus, we can be smart about this and inject the `::` at the right place
/// automatically.
fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<(Expr, Option<Error>)> {
let (maybe_type_path, punct_span) = maybe_type_path(p);
if maybe_type_path {
let helpful_error =
punct_span.map(|span| Error::new(span, "help: try inserting a `::` before this `<`"));
let mut type_path: TypePath = match p.parse() {
Ok(v) => v,
Err(mut e) => match helpful_error {
Some(help) => {
e.combine(help);
return Err(e);
}
None => return Err(e),
},
};
// We got a type path -- so we now inject the `::` before any `<` as
// needed.
for segment in type_path.path.segments.iter_mut() {
match segment.arguments {
PathArguments::AngleBracketed(ref mut arguments) => {
let span = arguments.span();
arguments
.colon2_token
.get_or_insert_with(|| token::PathSep {
spans: [span, span],
});
}
_ => (),
}
}
Ok((
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: type_path.qself,
path: type_path.path,
}),
helpful_error,
))
} else {
p.parse().map(|x| (x, None))
}
}
/// Parse an XML name while resolving built-in namespace prefixes.
fn parse_prefixed_name(
value: syn::parse::ParseStream<'_>,
) -> Result<(Option<NamespaceRef>, NameRef)> {
if !value.peek(LitStr) {
// if we don't have a string literal next, we delegate to the default
// `NameRef` parser.
return Ok((None, value.parse()?));
}
let name: LitStr = value.parse()?;
let name_span = name.span();
let (prefix, name) = match name
.value()
.try_into()
.and_then(|name: rxml_validation::Name| name.split_name())
{
Ok(v) => v,
Err(e) => {
return Err(Error::new(
name_span,
format!("not a valid XML name: {}", e),
))
}
};
let name = NameRef::Literal {
value: name,
span: name_span,
};
if let Some(prefix) = prefix {
let namespace_uri = match prefix.as_str() {
"xml" => XMLNS_XML,
"xmlns" => XMLNS_XMLNS,
other => return Err(Error::new(
name_span,
format!("prefix `{}` is not a built-in prefix and cannot be used. specify the desired namespace using the `namespace` key instead.", other)
)),
};
Ok((Some(NamespaceRef::fudge(namespace_uri, name_span)), name))
} else {
Ok((None, name))
}
}
/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
#[derive(Debug)]
pub(crate) enum XmlFieldMeta {
/// `#[xml(attribute)]`, `#[xml(attribute = ..)]` or `#[xml(attribute(..))]`
Attribute {
/// The span of the `#[xml(attribute)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The namespace/name keys.
qname: QNameRef,
/// The `default` flag.
default_: Flag,
/// An explicit type override, only usable within extracts.
type_: Option<Type>,
},
/// `#[xml(text)]`
Text {
/// The span of the `#[xml(text)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The path to the optional codec type.
codec: Option<Expr>,
/// An explicit type override, only usable within extracts.
type_: Option<Type>,
},
/// `#[xml(child)`
Child {
/// The span of the `#[xml(child)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The `default` flag.
default_: Flag,
/// The `n` flag.
amount: Option<AmountConstraint>,
},
/// `#[xml(extract)]
Extract {
/// The span of the `#[xml(extract)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The namespace/name keys.
qname: QNameRef,
/// The `n` flag.
amount: Option<AmountConstraint>,
/// The `default` flag.
default_: Flag,
/// The `fields` nested meta.
fields: Vec<XmlFieldMeta>,
},
/// `#[xml(element)]`
Element {
/// The span of the `#[xml(element)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The `n` flag.
amount: Option<AmountConstraint>,
},
}
impl XmlFieldMeta {
/// Parse a `#[xml(attribute(..))]` meta.
///
/// That meta can have three distinct syntax styles:
/// - argument-less: `#[xml(attribute)]`
/// - shorthand: `#[xml(attribute = ..)]`
/// - full: `#[xml(attribute(..))]`
fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
if meta.input.peek(Token![=]) {
// shorthand syntax
let (namespace, name) = parse_prefixed_name(meta.value()?)?;
Ok(Self::Attribute {
span: meta.path.span(),
qname: QNameRef {
name: Some(name),
namespace,
},
default_: Flag::Absent,
type_: None,
})
} else if meta.input.peek(syn::token::Paren) {
// full syntax
let mut qname = QNameRef::default();
let mut default_ = Flag::Absent;
let mut type_ = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
if default_.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
}
default_ = (&meta.path).into();
Ok(())
} else if meta.path.is_ident("type_") {
if type_.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
}
type_ = Some(meta.value()?.parse()?);
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
}
}
})?;
Ok(Self::Attribute {
span: meta.path.span(),
qname,
default_,
type_,
})
} else {
// argument-less syntax
Ok(Self::Attribute {
span: meta.path.span(),
qname: QNameRef::default(),
default_: Flag::Absent,
type_: None,
})
}
}
/// Parse a `#[xml(text)]` meta.
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
if meta.input.peek(Token![=]) {
let (codec, helpful_error) = parse_codec_expr(meta.value()?)?;
// A meta value can only be followed by either a `,`, or the end
// of the parse stream (because of the delimited group ending).
// Hence we check we are there. And if we are *not* there, we emit
// an error straight away, with the helpful addition from the
// `parse_codec_expr` if we have it.
//
// If we do not do this, the user gets a rather confusing
// "expected `,`" message if the `maybe_type_path` guess was
// wrong.
let lookahead = meta.input.lookahead1();
if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
if let Some(helpful_error) = helpful_error {
let mut e = lookahead.error();
e.combine(helpful_error);
return Err(e);
}
}
Ok(Self::Text {
span: meta.path.span(),
type_: None,
codec: Some(codec),
})
} else if meta.input.peek(syn::token::Paren) {
let mut codec: Option<Expr> = None;
let mut type_: Option<Type> = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("codec") {
if codec.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
}
let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
// See above (at the top-ish of this function) for why we
// do this.
let lookahead = meta.input.lookahead1();
if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
if let Some(helpful_error) = helpful_error {
let mut e = lookahead.error();
e.combine(helpful_error);
return Err(e);
}
}
codec = Some(new_codec);
Ok(())
} else if meta.path.is_ident("type_") {
if type_.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
}
type_ = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Text {
span: meta.path.span(),
type_,
codec,
})
} else {
Ok(Self::Text {
span: meta.path.span(),
type_: None,
codec: None,
})
}
}
/// Parse a `#[xml(child)]` meta.
fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
if meta.input.peek(syn::token::Paren) {
let mut default_ = Flag::Absent;
let mut amount = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
if default_.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
}
default_ = (&meta.path).into();
Ok(())
} else if meta.path.is_ident("n") {
if amount.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
}
amount = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Child {
span: meta.path.span(),
default_,
amount,
})
} else {
Ok(Self::Child {
span: meta.path.span(),
default_: Flag::Absent,
amount: None,
})
}
}
/// Parse a `#[xml(extract)]` meta.
fn extract_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
let mut qname = QNameRef::default();
let mut fields = None;
let mut amount = None;
let mut default_ = Flag::Absent;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
if default_.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
}
default_ = (&meta.path).into();
Ok(())
} else if meta.path.is_ident("fields") {
if let Some((fields_span, _)) = fields.as_ref() {
let mut error = Error::new_spanned(meta.path, "duplicate `fields` meta");
error.combine(Error::new(*fields_span, "previous `fields` meta was here"));
return Err(error);
}
let mut new_fields = Vec::new();
meta.parse_nested_meta(|meta| {
new_fields.push(XmlFieldMeta::parse_from_meta(meta)?);
Ok(())
})?;
fields = Some((meta.path.span(), new_fields));
Ok(())
} else if meta.path.is_ident("n") {
if amount.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
}
amount = Some(meta.value()?.parse()?);
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
}
}
})?;
let fields = fields.map(|(_, x)| x).unwrap_or_else(Vec::new);
Ok(Self::Extract {
span: meta.path.span(),
default_,
qname,
fields,
amount,
})
}
/// Parse a `#[xml(element)]` meta.
fn element_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
let mut amount = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("n") {
if amount.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
}
amount = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Element {
span: meta.path.span(),
amount,
})
}
/// Parse [`Self`] from a nestd meta, switching on the identifier
/// of that nested meta.
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
if meta.path.is_ident("attribute") {
Self::attribute_from_meta(meta)
} else if meta.path.is_ident("text") {
Self::text_from_meta(meta)
} else if meta.path.is_ident("child") {
Self::child_from_meta(meta)
} else if meta.path.is_ident("extract") {
Self::extract_from_meta(meta)
} else if meta.path.is_ident("element") {
Self::element_from_meta(meta)
} else {
Err(Error::new_spanned(meta.path, "unsupported field meta"))
}
}
/// Parse an `#[xml(..)]` meta on a field.
///
/// This switches based on the first identifier within the `#[xml(..)]`
/// meta and generates an enum variant accordingly.
///
/// Only a single nested meta is allowed; more than one will be
/// rejected with an appropriate compile-time error.
///
/// If no meta is contained at all, a compile-time error is generated.
///
/// Undefined options or options with incompatible values are rejected
/// with an appropriate compile-time error.
pub(crate) fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
let mut result: Option<Self> = None;
attr.parse_nested_meta(|meta| {
if result.is_some() {
return Err(Error::new_spanned(
meta.path,
"multiple field type specifiers are not supported",
));
}
result = Some(Self::parse_from_meta(meta)?);
Ok(())
})?;
if let Some(result) = result {
Ok(result)
} else {
Err(Error::new_spanned(
attr,
"missing field type specifier within `#[xml(..)]`",
))
}
}
/// Find and parse a `#[xml(..)]` meta on a field.
///
/// This invokes [`Self::parse_from_attribute`] internally on the first
/// encountered `#[xml(..)]` meta.
///
/// If not exactly one `#[xml(..)]` meta is encountered, an error is
/// returned. The error is spanned to `err_span`.
pub(crate) fn parse_from_attributes(attrs: &[Attribute], err_span: &Span) -> Result<Self> {
let mut result: Option<Self> = None;
for attr in attrs {
if !attr.path().is_ident("xml") {
continue;
}
if result.is_some() {
return Err(Error::new_spanned(
attr,
"only one #[xml(..)] attribute per field allowed.",
));
}
result = Some(Self::parse_from_attribute(attr)?);
}
if let Some(result) = result {
Ok(result)
} else {
Err(Error::new(*err_span, "missing #[xml(..)] meta on field"))
}
}
/// Return a span which points at the meta which constructed this
/// XmlFieldMeta.
pub(crate) fn span(&self) -> Span {
match self {
Self::Attribute { ref span, .. } => *span,
Self::Child { ref span, .. } => *span,
Self::Text { ref span, .. } => *span,
Self::Extract { ref span, .. } => *span,
Self::Element { ref span, .. } => *span,
}
}
/// Extract an explicit type specification if it exists.
pub(crate) fn take_type(&mut self) -> Option<Type> {
match self {
Self::Attribute { ref mut type_, .. } => type_.take(),
Self::Text { ref mut type_, .. } => type_.take(),
_ => None,
}
}
}