mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
1195 lines
38 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|