xmpp-rs/xso-proc/src/meta.rs

1195 lines
38 KiB
Rust

/*!
# Data structures and processing for `#[xml(..)]` attributes
This module is responsible for processing the syntax trees of the `#[xml(..)]`
attributes fields, enums, enum variants, and structs into more friendly
data structures.
However, it is not yet concerned as much with semantic validation: a lot of
invalid combinations can and will be represented with the data structures from
this module. The downstream consumers of these data structures, such as the
structs in the [`crate::field`] module, are responsible for ensuring that the
given combinations make sense and emit compile-time errors if they do not.
*/
use std::fmt;
use std::ops::ControlFlow;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{spanned::Spanned, *};
/// Helper macro to chain multiple [`MetaParse`] structs in a series.
///
/// This attempts to parse one `option` after the other, each of which must
/// be an expression evaluating to a thing implementing [`MetaParse`]. If any
/// matches, `Ok(())` is returned. If none matches, an error message containing
/// the allowed option names by calling `name()` on the `option` values is
/// returned.
macro_rules! parse_meta {
($meta:ident, $($option:expr,)+) => {
#[allow(unused_assignments)]
{
let meta = $meta;
$(
let meta = match $option.parse_at_meta(meta) {
Ok(ControlFlow::Continue(meta)) => meta,
Ok(ControlFlow::Break(())) => return Ok(()),
Err(e) => return Err(e),
};
)+
let mut error = format!("unsupported option. supported options are: ");
let mut first = true;
$(
error.reserve($option.name().len() + 2);
if !first {
error.push_str(", ");
}
first = false;
error.push_str($option.name());
)+
Err(Error::new_spanned(meta.path, error))
}
}
}
/// Helper trait to parse a value of some kind from a
/// [`syn::meta::ParseNestedMeta`].
///
/// This, combined with the [`parse_meta!`] macro, reduces code duplication
/// in the various parsing functions significantly.
trait MetaParse {
/// The identifier to match against the path of the meta.
fn name(&self) -> &'static str;
/// The actual workhorse: Assuming that the path matches [`Self::name`],
/// parse the data from the meta.
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()>;
/// Test the path against [`Self::name`] and parse the value if the path
/// matches.
///
/// Otherwise, return [`std::ops::ControlFlow::Continue`] with the meta
/// to allow other things to be parsed from it.
fn parse_at_meta<'x>(
&mut self,
meta: meta::ParseNestedMeta<'x>,
) -> Result<ControlFlow<(), meta::ParseNestedMeta<'x>>> {
if !meta.path.is_ident(self.name()) {
return Ok(ControlFlow::Continue(meta));
}
Ok(ControlFlow::Break(self.force_parse_at_meta(meta)?))
}
}
/// Parse a [`Flag`] from a meta.
struct ParseFlag<'a>(&'static str, &'a mut Flag);
impl<'a> MetaParse for ParseFlag<'a> {
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()),
));
}
*self.1 = Flag::Present(meta.path.span());
Ok(())
}
}
/// 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>);
impl<'a, T: parse::Parse> MetaParse for ParseValue<'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_some() {
return Err(Error::new_spanned(
meta.path,
format!("duplicate {} option", self.name()),
));
}
*self.1 = Some(meta.value()?.parse()?);
Ok(())
}
}
/// Parse a `NodeFilterMeta` from a meta.
struct ParseNodeFilter<'a>(&'static str, &'a mut Option<NodeFilterMeta>);
impl<'a> MetaParse for ParseNodeFilter<'a> {
fn name(&self) -> &'static str {
self.0
}
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
if self.1.is_some() {
return Err(Error::new_spanned(
meta.path,
format!("duplicate {} option", self.name()),
));
}
*self.1 = Some(NodeFilterMeta::parse_from_meta(meta)?);
Ok(())
}
}
/// Parse a `Vec<Box<XmlFieldMeta>>` from a meta.
struct ParseExtracts<'a>(&'a mut Vec<Box<XmlFieldMeta>>);
impl<'a> MetaParse for ParseExtracts<'a> {
fn name(&self) -> &'static str {
"extract"
}
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
meta.parse_nested_meta(|meta| {
self.0.push(Box::new(XmlFieldMeta::parse_from_meta(meta)?));
Ok(())
})
}
}
/// 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.
Span,
),
}
impl Flag {
/// Return true if the flag is set, false otherwise.
pub fn is_set(&self) -> bool {
match self {
Self::Absent => false,
Self::Present(_) => true,
}
}
/// Basically [`Option::take`], but for [`Flag`].
pub fn take(&mut self) -> Self {
let mut tmp = Flag::Absent;
std::mem::swap(&mut tmp, self);
tmp
}
}
impl<T: Spanned> From<T> for Flag {
fn from(other: T) -> Flag {
Flag::Present(other.span())
}
}
/// A flag with an optional value.
#[derive(Clone, 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
/// `LitStr`.
pub(crate) type StaticNamespace = Path;
/// Value of a `#[xml(namespace = ..)]` attribute.
///
/// XML namespaces can be configured in different ways, which are
/// distinguished in this enum.
#[derive(Debug)]
pub(crate) enum NamespaceRef {
/// A dynamic namespace, expressed as `#[xml(namespace = dyn)]`.
///
/// See [`crate::structs::StructNamespace::Dyn`] for details.
Dyn(
/// The original `dyn` token for better error messages.
Token![dyn],
),
/// Refer to the parent struct's namespace, expressed as
/// `#[xml(namespace = super)]`.
Super(
/// The original `super` token for better error messages.
Token![super],
),
/// A static namespace identified by a static string, expressed as
/// `#[xml(namespace = crate::ns::FOO)]` or similar.
Static(StaticNamespace),
}
impl parse::Parse for NamespaceRef {
fn parse(input: parse::ParseStream<'_>) -> Result<Self> {
if input.peek(Token![dyn]) {
let ns = input.parse()?;
Ok(Self::Dyn(ns))
} else if input.peek(Token![super]) && !input.peek2(Token![::]) {
let ns = input.parse()?;
Ok(Self::Super(ns))
} else {
let ns = input.parse()?;
Ok(Self::Static(ns))
}
}
}
impl ToTokens for NamespaceRef {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Dyn(ns) => ns.to_tokens(tokens),
Self::Super(ns) => ns.to_tokens(tokens),
Self::Static(ns) => ns.to_tokens(tokens),
}
}
fn into_token_stream(self) -> TokenStream {
match self {
Self::Dyn(ns) => ns.into_token_stream(),
Self::Super(ns) => ns.into_token_stream(),
Self::Static(ns) => ns.into_token_stream(),
}
}
}
/// Type alias for a XML name setting.
///
/// This may in the future be replaced by an enum supporting both `Path` and
/// `LitStr`.
pub(crate) type NameRef = LitStr;
/// An XML name.
///
/// This enum, unlike when passing around the XML name as a string, preserves
/// the original tokens, which is useful for better error messages.
#[derive(Debug)]
pub(crate) enum Name {
/// Represented as a string literal.
Lit(LitStr),
/// Represented as an identifier, e.g. when it is derived from an
/// `#[xml(attribute)]` field's identifier.
Ident(Ident),
}
impl From<LitStr> for Name {
fn from(other: LitStr) -> Self {
Self::Lit(other)
}
}
impl From<Ident> for Name {
fn from(other: Ident) -> Self {
Self::Ident(other)
}
}
impl From<Name> for LitStr {
fn from(other: Name) -> Self {
match other {
Name::Lit(v) => v,
Name::Ident(v) => LitStr::new(&v.to_string(), v.span()),
}
}
}
impl From<&Name> for LitStr {
fn from(other: &Name) -> Self {
match other {
Name::Lit(v) => v.clone(),
Name::Ident(v) => LitStr::new(&v.to_string(), v.span()),
}
}
}
impl fmt::Display for Name {
fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
match self {
Self::Lit(s) => f.write_str(&s.value()),
Self::Ident(s) => s.fmt(f),
}
}
}
impl ToTokens for Name {
fn to_tokens(&self, tokens: &mut TokenStream) {
let s: LitStr = self.into();
s.to_tokens(tokens)
}
fn into_token_stream(self) -> TokenStream {
let s: LitStr = self.into();
s.into_token_stream()
}
}
/// Struct containing namespace and name matchers.
#[derive(Default, Debug)]
pub(crate) struct NodeFilterMeta {
/// The value of the `namespace` option.
pub(crate) namespace: Option<NamespaceRef>,
/// The value of the `name` option.
pub(crate) name: Option<NameRef>,
}
impl NodeFilterMeta {
/// Parse the struct's contents from a potenially nested meta.
///
/// This supports three syntaxes, assuming that `foo` is the meta we're
/// at:
///
/// - `#[xml(.., foo, ..)]` (no value at all): All pieces of the returned
/// struct are `None`.
///
/// - `#[xml(.., foo(..), ..)]`, where `foo` may contain the keys
/// `namespace` and `name`, specifying the values of the struct
/// respectively.
fn parse_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
if meta.input.peek(token::Paren) {
let mut name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
meta.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("name", &mut name),
ParseValue("namespace", &mut namespace),
)
})?;
Ok(Self { namespace, name })
} else {
Ok(Self::default())
}
}
}
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
///
/// This is the counterpart to [`XmlFieldMeta`].
#[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` inside `#[xml(..)]`, if any.
pub(crate) namespace: Option<NamespaceRef>,
/// The value assigned to `name` inside `#[xml(..)]`, if any.
pub(crate) name: Option<NameRef>,
/// The value assigned to `attribute` inside `#[xml(..)]`, if any.
pub(crate) attribute: Option<LitStr>,
/// The value assigned to `value` inside `#[xml(..)]`, if any.
pub(crate) value: Option<LitStr>,
/// Flag indicating the presence of `fallback` inside `#[xml(..)].
pub(crate) fallback: Flag,
/// Flag indicating the presence of `transparent` inside `#[xml(..)].
pub(crate) transparent: Flag,
/// Flag indicating the presence of `exhaustive` inside `#[xml(..)].
pub(crate) exhaustive: Flag,
/// The value assigned to `validate` inside `#[xml(..)]`, if any.
pub(crate) validate: Option<Path>,
/// The value assigned to `prepare` inside `#[xml(..)]`, if any.
pub(crate) prepare: Option<Path>,
/// The value assigned to `normalize_with` inside `#[xml(..)]`, if any.
pub(crate) normalize_with: Option<Path>,
/// Flag indicating the presence of `debug` inside `#[xml(..)]`
pub(crate) debug: Flag,
/// The options set inside `element`, if any.
pub(crate) element: Option<NodeFilterMeta>,
/// The options set inside `wrapped_with(..)`, if any.
pub(crate) wrapped_with: Option<NodeFilterMeta>,
/// Member of the `UnknownChildPolicy` enum to use when handling unknown
/// children.
pub(crate) on_unknown_child: Option<Ident>,
/// Member of the `UnknownAttributePolicy` enum to use when handling
/// unknown attributes.
pub(crate) on_unknown_attribute: Option<Ident>,
}
impl XmlCompoundMeta {
pub(crate) fn empty(span: Span) -> Self {
XmlCompoundMeta {
span,
namespace: None,
name: None,
attribute: None,
value: None,
fallback: Flag::Absent,
transparent: Flag::Absent,
exhaustive: Flag::Absent,
validate: None,
prepare: None,
wrapped_with: None,
normalize_with: None,
debug: Flag::Absent,
element: None,
on_unknown_attribute: None,
on_unknown_child: None,
}
}
/// 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 name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
let mut attribute: Option<LitStr> = None;
let mut value: Option<LitStr> = None;
let mut fallback = Flag::Absent;
let mut transparent = Flag::Absent;
let mut exhaustive = Flag::Absent;
let mut debug = Flag::Absent;
let mut validate: Option<Path> = None;
let mut prepare: Option<Path> = None;
let mut normalize_with: Option<Path> = None;
let mut element: Option<NodeFilterMeta> = None;
let mut wrapped_with: Option<NodeFilterMeta> = None;
let mut on_unknown_attribute: Option<Ident> = None;
let mut on_unknown_child: Option<Ident> = None;
attr.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("name", &mut name),
ParseValue("namespace", &mut namespace),
ParseValue("attribute", &mut attribute),
ParseValue("value", &mut value),
ParseFlag("fallback", &mut fallback),
ParseFlag("transparent", &mut transparent),
ParseFlag("exhaustive", &mut exhaustive),
ParseFlag("debug", &mut debug),
ParseValue("validate", &mut validate),
ParseValue("prepare", &mut prepare),
ParseValue("normalize_with", &mut normalize_with),
ParseValue("normalise_with", &mut normalize_with),
ParseNodeFilter("element", &mut element),
ParseNodeFilter("wrapped_with", &mut wrapped_with),
ParseValue("on_unknown_attribute", &mut on_unknown_attribute),
ParseValue("on_unknown_child", &mut on_unknown_child),
)
})?;
Ok(Self {
span: attr.span(),
namespace,
name,
attribute,
value,
fallback,
transparent,
validate,
exhaustive,
prepare,
wrapped_with,
debug,
normalize_with,
element,
on_unknown_child,
on_unknown_attribute,
})
}
/// 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: Option<Self> = 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",
)),
}
}
}
/// Helper struct to parse the tupleof XML namespace, XML name,
/// `default` flag, `type` and `codec` option.
///
/// These three are used in several places.
#[derive(Debug)]
struct AttributeMeta {
/// The value assigned to `namespace` inside a potentially nested
/// `#[xml(..)]`, if any.
namespace: Option<NamespaceRef>,
/// The value assigned to `name` inside a potentially nested
/// `#[xml(..)]`, if any.
name: Option<NameRef>,
/// The presence of the `default` flag a potentially nested `#[xml(..)]`,
/// if any.
default_: FlagOr<Path>,
/// The value assigned to `type` inside a potentially nested
/// `#[xml(..)]`, if any.
ty: Option<Type>,
/// The value assigned to `codec` inside a potentially nested
/// `#[xml(..)]`, if any.
codec: Option<Type>,
}
impl AttributeMeta {
/// Parse the struct's contents from a potenially nested meta.
///
/// This supports three syntaxes, assuming that `foo` is the meta we're
/// at:
///
/// - `#[xml(.., foo, ..)]` (no value at all): All pieces of the returned
/// struct are `None` / `Flag::Absent`.
///
/// - `#[xml(.., foo = "bar", ..)]` (direct assignment): The `name` is set
/// to `"bar"`, the other pieces are `None` / `Flag::Absent`.
///
/// - `#[xml(.., foo(..), ..)]`, where `foo` may contain the keys
/// `namespace`, `name` and `default`, specifying the values of the
/// struct respectively.
fn parse_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
if meta.input.peek(Token![=]) {
let name: LitStr = meta.value()?.parse()?;
Ok(Self {
name: Some(name),
namespace: None,
default_: FlagOr::Absent,
codec: None,
ty: None,
})
} else if meta.input.peek(token::Paren) {
let mut name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
let mut ty: Option<Type> = None;
let mut default_: FlagOr<Path> = FlagOr::Absent;
let mut codec: Option<Type> = None;
meta.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("name", &mut name),
ParseValue("namespace", &mut namespace),
ParseValue("type", &mut ty),
ParseValue("codec", &mut codec),
ParseFlagOr("default", &mut default_),
)
})?;
Ok(Self {
namespace,
name,
default_,
codec,
ty,
})
} else {
Ok(Self {
namespace: None,
name: None,
default_: FlagOr::Absent,
codec: None,
ty: None,
})
}
}
/// Emit an error with the given message if the type is set
fn reject_type(&self, msg: impl Into<String>) -> Result<()> {
if let Some(ty) = self.ty.as_ref() {
Err(Error::new_spanned(ty, msg.into()))
} else {
Ok(())
}
}
/// Emit an error with the given message if the codec is set
fn reject_codec(&self, msg: impl Into<String>) -> Result<()> {
if let Some(codec) = self.codec.as_ref() {
Err(Error::new_spanned(codec, msg.into()))
} else {
Ok(())
}
}
/// Emit an error with the given message if the codec is set
fn reject_default(&self, msg: impl Into<String>) -> Result<()> {
if let Some(span) = self.default_.span() {
Err(Error::new(span, msg.into()))
} else {
Ok(())
}
}
}
/// Mode for destructured child collection.
#[derive(Debug)]
pub(crate) enum ChildMode {
/// The field represents a single XML child element.
Single,
/// The field represents more than one XML child element.
Collection,
}
/// Contents of an `#[xml(..)]` attribute on a field.
///
/// This is the counterpart to [`XmlCompoundMeta`].
#[derive(Debug)]
pub(crate) enum XmlFieldMeta {
/// Maps the field to an XML attribute.
///
/// Maps to the `#[xml(attribute)]`, `#[xml(attribute = ..)]` and
/// `#[xml(attribute(..))]` Rust syntaxes.
///
/// Gets transformed into [`crate::field::attribute::AttributeField`] in
/// later processing stages.
Attribute {
/// Contents of the `name = ..` option or value assigned to
/// `attribute` in the shorthand syntax.
name: Option<NameRef>,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
/// Contents of the `codec = ..` option.
codec: Option<Type>,
/// Contents of the `type = ..` option.
ty: Option<Type>,
},
/// Maps the field to a destructured XML child element.
///
/// Maps to the `#[xml(child)]`, `#[xml(child(..))]`, `#[xml(children)]`,
/// and `#[xml(children(..)]` Rust syntaxes.
///
/// Gets transformed into [`crate::field::child::ChildField`] in later
/// processing stages.
Child {
/// Distinguishes between `#[xml(child)]` and `#[xml(children)]`
/// variants.
mode: ChildMode,
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
/// Contents of the `name = ..` option.
name: Option<NameRef>,
/// Contents of the `extract(..)` option.
extract: Vec<Box<XmlFieldMeta>>,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
/// Contents of the `skip_if = ..` option.
skip_if: Option<Path>,
/// Contents of the `codec = ..` option.
codec: Option<Path>,
},
/// Maps the field to the compounds' XML element's namespace.
///
/// See also [`crate::structs::StructNamespace::Dyn`].
///
/// Maps to the `#[xml(namespace)]` Rust syntax.
///
/// Gets transformed into [`crate::field::namespace::NamespaceField`] in
/// later processing stages.
Namespace,
/// Maps the field to the text contents of the XML element.
///
/// Maps to the `#[xml(text)]` Rust syntax.
///
/// Gets transformed into [`crate::field::text::TextField`] in later
/// processing stages.
Text {
/// Contents of the `codec = ..` option.
codec: Option<Type>,
/// Contents of the `type = ..` option.
ty: Option<Type>,
},
/// Maps the field to a subset of XML child elements, without
/// destructuring them into Rust structs.
///
/// Maps to the `#[xml(elements)]` and `#[xml(elements(..))]` Rust
/// syntaxes.
///
/// Gets transformed into [`crate::field::element::ElementsField`] in
/// later processing stages.
Elements {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
/// Contents of the `name = ..` option.
name: Option<NameRef>,
},
/// Maps the field to a single of XML child element, without destructuring
/// it into a Rust struct.
///
/// Maps to the `#[xml(element(..))]` Rust syntax.
///
/// Gets transformed into [`crate::field::element::ElementField`] in
/// later processing stages.
Element {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
/// Contents of the `name = ..` option.
name: Option<NameRef>,
/// Contents of the `default` flag.
default_: FlagOr<Path>,
},
/// Maps the field to the presence of an empty XML child element.
///
/// Maps to the `#[xml(flag(..))]` Rust syntax.
///
/// Gets transformed into [`crate::field::flag::FlagField`] in later
/// processing stages.
Flag {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
/// Contents of the `name = ..` option.
name: Option<NameRef>,
},
/// Ignores the field for the purpose of XML processing.
///
/// Maps to the `#[xml(ignore)]` Rust syntax.
///
/// Gets transformed into [`crate::field::Ignore`] in later processing
/// stages.
Ignore,
}
static VALID_FIELD_XML_OPTIONS: &'static str =
"attribute, child, children, namespace, text, elements, element, flag, ignore";
impl XmlFieldMeta {
/// Processes a `#[xml(attribute)]` meta, creating a [`Self::Attribute`]
/// variant.
fn attribute_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let meta = AttributeMeta::parse_from_meta(meta)?;
if let Some(namespace) = meta.namespace {
return Err(Error::new_spanned(
namespace,
"namespaced attributes are not supported yet...",
));
}
Ok(Self::Attribute {
name: meta.name,
default_: meta.default_,
codec: meta.codec,
ty: meta.ty,
})
}
/// Common processing for the `#[xml(child)]` and `#[xml(children)]`
/// metas.
fn child_common_from_meta(
meta: meta::ParseNestedMeta<'_>,
) -> Result<(
Option<NamespaceRef>,
Option<NameRef>,
Vec<Box<XmlFieldMeta>>,
FlagOr<Path>,
Option<Path>,
Option<Path>,
)> {
if meta.input.peek(token::Paren) {
let mut name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
let mut extract: Vec<Box<XmlFieldMeta>> = Vec::new();
let mut skip_if: Option<Path> = None;
let mut default_ = FlagOr::Absent;
let mut codec: Option<Path> = None;
meta.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("name", &mut name),
ParseValue("namespace", &mut namespace),
ParseExtracts(&mut extract),
ParseFlagOr("default", &mut default_),
ParseValue("skip_if", &mut skip_if),
ParseValue("codec", &mut codec),
)
})?;
Ok((namespace, name, extract, default_, skip_if, codec))
} else {
Ok((None, None, Vec::new(), FlagOr::Absent, None, 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_, skip_if, codec) =
Self::child_common_from_meta(meta)?;
Ok(Self::Child {
mode: ChildMode::Single,
namespace,
name,
extract,
default_,
skip_if,
codec,
})
}
/// 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_, skip_if, codec) =
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_: FlagOr::Absent,
skip_if,
codec,
})
}
/// Processes a `#[xml(namespace)]` meta, creating a [`Self::Namespace`]
/// variant.
fn namespace_from_meta(_meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
Ok(Self::Namespace)
}
/// Processes a `#[xml(texd)]` meta, creating a [`Self::Text`]
/// variant.
fn text_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let mut codec: Option<Type> = None;
let mut ty: Option<Type> = None;
if meta.input.peek(token::Paren) {
#[rustfmt::skip] // rustfmt transforms the code so that the attribute inside parse_meta! is on an expression which is not allowed
meta.parse_nested_meta(|meta| {
parse_meta!(
meta,
ParseValue("codec", &mut codec),
ParseValue("type", &mut ty),
)
})?;
}
Ok(Self::Text { codec, ty })
}
/// Processes a `#[xml(element)]` meta, creating a [`Self::Element`]
/// variant.
fn element_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let meta = AttributeMeta::parse_from_meta(meta)?;
meta.reject_codec("codec = .. is not allowed on #[xml(element)]")?;
meta.reject_type("type = .. is not allowed on #[xml(element)]")?;
Ok(Self::Element {
name: meta.name,
namespace: meta.namespace,
default_: meta.default_,
})
}
/// Processes a `#[xml(elements)]` meta, creating a [`Self::Elements`]
/// variant.
fn elements_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
let meta = AttributeMeta::parse_from_meta(meta)?;
meta.reject_codec("codec = .. is not allowed on #[xml(elements)]")?;
meta.reject_type("type = .. is not allowed on #[xml(elements)]")?;
meta.reject_default("default cannot be used on #[xml(elements)] (it is implied, the default is the empty container)")?;
Ok(Self::Elements {
name: meta.name,
namespace: meta.namespace,
})
}
/// Processes a `#[xml(flag)]` meta, creating a [`Self::Flag`] variant.
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 Some(span) = meta.default_.into_span() {
return Err(syn::Error::new(
span,
"default cannot be used on #[xml(flag)] (it is implied, the default false)",
));
}
Ok(Self::Flag {
name: meta.name,
namespace: meta.namespace,
})
}
/// Processes a `#[xml(ignore)]` meta, creating a [`Self::Ignore`]
/// variant.
fn ignore_from_meta(_meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
Ok(Self::Ignore)
}
fn parse_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
if meta.path.is_ident("attribute") {
Self::attribute_from_meta(meta)
} else if meta.path.is_ident("child") {
Self::child_from_meta(meta)
} else if meta.path.is_ident("children") {
Self::children_from_meta(meta)
} else if meta.path.is_ident("namespace") {
Self::namespace_from_meta(meta)
} else if meta.path.is_ident("text") {
Self::text_from_meta(meta)
} else if meta.path.is_ident("elements") {
Self::elements_from_meta(meta)
} else if meta.path.is_ident("element") {
Self::element_from_meta(meta)
} else if meta.path.is_ident("flag") {
Self::flag_from_meta(meta)
} else if meta.path.is_ident("ignore") {
Self::ignore_from_meta(meta)
} else {
Err(Error::new_spanned(
meta.path,
format!(
"unsupported field option. supported options: {}.",
VALID_FIELD_XML_OPTIONS
),
))
}
}
/// Parse an `#[xml(..)]` attribute.
///
/// This switches based on the first identifier within the `#[xml(..)]`
/// attribute and generates a struct variant accordingly.
///
/// Only a single nested attribute is allowed; more than one will be
/// rejected with an appropriate compile-time error.
///
/// If no attribute 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 options are not supported",
));
}
result = Some(Self::parse_from_meta(meta)?);
Ok(())
})?;
if let Some(result) = result {
Ok(result)
} else {
Err(Error::new_spanned(
attr,
format!(
"missing field options. specify at one of {}.",
VALID_FIELD_XML_OPTIONS
),
))
}
}
/// Take the explicit type specification out of this meta and return it,
/// if any.
///
/// This function *does not recurse* into nested `extract(..)` specs.
pub(crate) fn take_type(&mut self) -> Option<Type> {
match self {
XmlFieldMeta::Attribute { ref mut ty, .. } => ty.take(),
XmlFieldMeta::Text { ref mut ty, .. } => ty.take(),
_ => None,
}
}
/// Take the explicit type specification out of this meta and return it,
/// if any.
///
/// This function **does recurse** into nested `extract(..)` specs and
/// returns types for kinds with known type.
pub(crate) fn determine_type(&mut self) -> Option<Type> {
match self {
Self::Attribute { ref mut ty, .. } => ty.take(),
Self::Child {
ref mut extract,
mode: ChildMode::Single,
..
} => {
if extract.len() == 1 {
extract[0].take_type()
} else {
None // cannot be determined from meta alone
}
}
Self::Child {
mode: ChildMode::Collection,
..
} => None, // cannot be determined from meta alone
Self::Namespace => None, // cannot be determined from meta alone
Self::Text { ref mut ty, .. } => ty.take(),
Self::Elements { .. } => Some(
parse_str("::std::vec::Vec<::xso::exports::minidom::Element>")
.expect("cannot construct elements type"),
),
Self::Element { .. } => Some(
parse_str("::xso::exports::minidom::Element")
.expect("cannot construct element type"),
),
Self::Flag { .. } => Some(parse_str("bool").expect("cannot construct element type")),
Self::Ignore => None,
}
}
}