xso: implement support for enums

Jonas Schรคfer created

Change summary

parsers/src/util/macro_tests.rs | 117 ++++++++++++
xso-proc/src/compound.rs        |   6 
xso-proc/src/enums.rs           | 319 +++++++++++++++++++++++++++++++++++
xso-proc/src/lib.rs             |  10 
xso-proc/src/meta.rs            |  36 +++
xso-proc/src/state.rs           |  57 ++++++
xso-proc/src/structs.rs         |   2 
xso/ChangeLog                   |   1 
xso/src/from_xml_doc.md         |  54 +++++
9 files changed, 594 insertions(+), 8 deletions(-)

Detailed changes

parsers/src/util/macro_tests.rs ๐Ÿ”—

@@ -617,3 +617,120 @@ fn renamed_types_get_renamed() {
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "elem")]
 struct LintTest_;
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1)]
+enum NameSwitchedEnum {
+    #[xml(name = "a")]
+    Variant1 {
+        #[xml(attribute)]
+        foo: String,
+    },
+    #[xml(name = "b")]
+    Variant2 {
+        #[xml(text)]
+        foo: String,
+    },
+}
+
+#[test]
+fn name_switched_enum_positive_variant_1() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    match parse_str::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>") {
+        Ok(NameSwitchedEnum::Variant1 { foo }) => {
+            assert_eq!(foo, "hello");
+        }
+        other => panic!("unexpected result: {:?}", other),
+    }
+}
+
+#[test]
+fn name_switched_enum_positive_variant_2() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>") {
+        Ok(NameSwitchedEnum::Variant2 { foo }) => {
+            assert_eq!(foo, "hello");
+        }
+        other => panic!("unexpected result: {:?}", other),
+    }
+}
+
+#[test]
+fn name_switched_enum_negative_name_mismatch() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    match parse_str::<NameSwitchedEnum>("<x xmlns='urn:example:ns1'>hello</x>") {
+        Err(xso::error::FromElementError::Mismatch { .. }) => (),
+        other => panic!("unexpected result: {:?}", other),
+    }
+}
+
+#[test]
+fn name_switched_enum_negative_namespace_mismatch() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns2'>hello</b>") {
+        Err(xso::error::FromElementError::Mismatch { .. }) => (),
+        other => panic!("unexpected result: {:?}", other),
+    }
+}
+
+#[test]
+fn name_switched_enum_roundtrip_variant_1() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    roundtrip_full::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>")
+}
+
+#[test]
+fn name_switched_enum_roundtrip_variant_2() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    roundtrip_full::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
+}
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, builder = RenamedEnumBuilder, iterator = RenamedEnumIter)]
+enum RenamedEnumTypes {
+    #[xml(name = "elem")]
+    A,
+}
+
+#[test]
+fn renamed_enum_types_roundtrip() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    roundtrip_full::<RenamedEnumTypes>("<elem xmlns='urn:example:ns1'/>")
+}
+
+#[test]
+#[allow(unused_comparisons)]
+fn renamed_enum_types_get_renamed() {
+    // these merely serve as a test that the types are declared with the names
+    // given in the attributes.
+    assert!(std::mem::size_of::<RenamedEnumBuilder>() >= 0);
+    assert!(std::mem::size_of::<RenamedEnumIter>() >= 0);
+}

xso-proc/src/compound.rs ๐Ÿ”—

@@ -297,7 +297,7 @@ impl Compound {
     /// `rxml::QName` is in scope.
     pub(crate) fn make_as_item_iter_statemachine(
         &self,
-        input_name: &Path,
+        input_name: &ParentRef,
         state_prefix: &str,
         lifetime: &Lifetime,
     ) -> Result<AsItemsSubmachine> {
@@ -430,11 +430,13 @@ impl Compound {
             }),
         );
 
+        let ParentRef::Named(input_path) = input_name;
+
         Ok(AsItemsSubmachine {
             defs: TokenStream::default(),
             states,
             destructure: quote! {
-                #input_name { #destructure }
+                #input_path { #destructure }
             },
             init: quote! {
                 Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }

xso-proc/src/enums.rs ๐Ÿ”—

@@ -0,0 +1,319 @@
+// 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/.
+
+//! Handling of enums
+
+use std::collections::HashMap;
+
+use proc_macro2::Span;
+use quote::quote;
+use syn::*;
+
+use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
+use crate::compound::Compound;
+use crate::error_message::ParentRef;
+use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
+use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
+
+/// The definition of an enum variant, switched on the XML element's name.
+struct NameVariant {
+    /// The XML name of the element to map the enum variant to.
+    name: NameRef,
+
+    /// The name of the variant
+    ident: Ident,
+
+    /// The field(s) of this struct.
+    inner: Compound,
+}
+
+impl NameVariant {
+    /// Construct a new name-selected variant from its declaration.
+    fn new(decl: &Variant) -> Result<Self> {
+        let meta = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
+
+        if let Some(namespace) = meta.namespace {
+            return Err(Error::new_spanned(
+                namespace,
+                "`namespace` is not allowed on enum variants (only on enums and structs)",
+            ));
+        }
+
+        let Some(name) = meta.name else {
+            return Err(Error::new(meta.span, "`name` is required on enum variants"));
+        };
+
+        Ok(Self {
+            name,
+            ident: decl.ident.clone(),
+            inner: Compound::from_fields(&decl.fields)?,
+        })
+    }
+
+    fn make_from_events_statemachine(
+        &self,
+        enum_ident: &Ident,
+        state_ty_ident: &Ident,
+    ) -> Result<FromEventsStateMachine> {
+        let xml_name = &self.name;
+
+        Ok(self
+            .inner
+            .make_from_events_statemachine(
+                state_ty_ident,
+                &ParentRef::Named(Path {
+                    leading_colon: None,
+                    segments: [
+                        PathSegment::from(enum_ident.clone()),
+                        self.ident.clone().into(),
+                    ]
+                    .into_iter()
+                    .collect(),
+                }),
+                &self.ident.to_string(),
+            )?
+            .with_augmented_init(|init| {
+                quote! {
+                    if name.1 != #xml_name {
+                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
+                            name,
+                            attrs,
+                        })
+                    } else {
+                        #init
+                    }
+                }
+            })
+            .compile())
+    }
+
+    fn make_as_item_iter_statemachine(
+        &self,
+        xml_namespace: &NamespaceRef,
+        enum_ident: &Ident,
+        item_iter_ty_lifetime: &Lifetime,
+    ) -> Result<AsItemsStateMachine> {
+        let xml_name = &self.name;
+
+        Ok(self
+            .inner
+            .make_as_item_iter_statemachine(
+                &ParentRef::Named(Path {
+                    leading_colon: None,
+                    segments: [
+                        PathSegment::from(enum_ident.clone()),
+                        self.ident.clone().into(),
+                    ]
+                    .into_iter()
+                    .collect(),
+                }),
+                &self.ident.to_string(),
+                &item_iter_ty_lifetime,
+            )?
+            .with_augmented_init(|init| {
+                quote! {
+                    let name = (
+                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
+                        ::std::borrow::Cow::Borrowed(#xml_name),
+                    );
+                    #init
+                }
+            })
+            .compile())
+    }
+}
+
+/// Definition of an enum and how to parse it.
+pub(crate) struct EnumDef {
+    /// The XML namespace of the element to map the enum to.
+    namespace: NamespaceRef,
+
+    /// The variants of the enum.
+    variants: Vec<NameVariant>,
+
+    /// Name of the target type.
+    target_ty_ident: Ident,
+
+    /// Name of the builder type.
+    builder_ty_ident: Ident,
+
+    /// Name of the iterator type.
+    item_iter_ty_ident: Ident,
+
+    /// Flag whether debug mode is enabled.
+    debug: bool,
+}
+
+impl EnumDef {
+    /// Create a new enum from its name, meta, and variants.
+    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
+        ident: &Ident,
+        meta: XmlCompoundMeta,
+        variant_iter: I,
+    ) -> Result<Self> {
+        if let Some(name) = meta.name {
+            return Err(Error::new_spanned(
+                name,
+                "`name` is not allowed on enums (only on their variants)",
+            ));
+        }
+
+        let Some(namespace) = meta.namespace else {
+            return Err(Error::new(meta.span, "`namespace` is required on enums"));
+        };
+
+        let mut variants = Vec::new();
+        let mut seen_names = HashMap::new();
+        for variant in variant_iter {
+            let variant = NameVariant::new(variant)?;
+            if let Some(other) = seen_names.get(&variant.name) {
+                return Err(Error::new_spanned(
+                    variant.name,
+                    format!(
+                        "duplicate `name` in enum: variants {} and {} have the same XML name",
+                        other, variant.ident
+                    ),
+                ));
+            }
+            seen_names.insert(variant.name.clone(), variant.ident.clone());
+            variants.push(variant);
+        }
+
+        let builder_ty_ident = match meta.builder {
+            Some(v) => v,
+            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
+        };
+
+        let item_iter_ty_ident = match meta.iterator {
+            Some(v) => v,
+            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
+        };
+
+        Ok(Self {
+            namespace,
+            variants,
+            target_ty_ident: ident.clone(),
+            builder_ty_ident,
+            item_iter_ty_ident,
+            debug: meta.debug.is_set(),
+        })
+    }
+}
+
+impl ItemDef for EnumDef {
+    fn make_from_events_builder(
+        &self,
+        vis: &Visibility,
+        name_ident: &Ident,
+        attrs_ident: &Ident,
+    ) -> Result<FromXmlParts> {
+        let xml_namespace = &self.namespace;
+        let target_ty_ident = &self.target_ty_ident;
+        let builder_ty_ident = &self.builder_ty_ident;
+        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
+
+        let mut statemachine = FromEventsStateMachine::new();
+        for variant in self.variants.iter() {
+            statemachine
+                .merge(variant.make_from_events_statemachine(target_ty_ident, &state_ty_ident)?);
+        }
+
+        statemachine.set_pre_init(quote! {
+            if name.0 != #xml_namespace {
+                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
+                    name,
+                    attrs,
+                })
+            }
+        });
+
+        let defs = statemachine.render(
+            vis,
+            builder_ty_ident,
+            &state_ty_ident,
+            &TypePath {
+                qself: None,
+                path: target_ty_ident.clone().into(),
+            }
+            .into(),
+        )?;
+
+        Ok(FromXmlParts {
+            defs,
+            from_events_body: quote! {
+                #builder_ty_ident::new(#name_ident, #attrs_ident)
+            },
+            builder_ty_ident: builder_ty_ident.clone(),
+        })
+    }
+
+    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
+        let target_ty_ident = &self.target_ty_ident;
+        let item_iter_ty_ident = &self.item_iter_ty_ident;
+        let item_iter_ty_lifetime = Lifetime {
+            apostrophe: Span::call_site(),
+            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
+        };
+        let item_iter_ty = Type::Path(TypePath {
+            qself: None,
+            path: Path {
+                leading_colon: None,
+                segments: [PathSegment {
+                    ident: item_iter_ty_ident.clone(),
+                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                        colon2_token: None,
+                        lt_token: token::Lt {
+                            spans: [Span::call_site()],
+                        },
+                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
+                            .into_iter()
+                            .collect(),
+                        gt_token: token::Gt {
+                            spans: [Span::call_site()],
+                        },
+                    }),
+                }]
+                .into_iter()
+                .collect(),
+            },
+        });
+        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
+
+        let mut statemachine = AsItemsStateMachine::new();
+        for variant in self.variants.iter() {
+            statemachine.merge(variant.make_as_item_iter_statemachine(
+                &self.namespace,
+                target_ty_ident,
+                &item_iter_ty_lifetime,
+            )?);
+        }
+
+        let defs = statemachine.render(
+            vis,
+            &TypePath {
+                qself: None,
+                path: target_ty_ident.clone().into(),
+            }
+            .into(),
+            &state_ty_ident,
+            &item_iter_ty_lifetime,
+            &item_iter_ty,
+        )?;
+
+        Ok(AsXmlParts {
+            defs,
+            as_xml_iter_body: quote! {
+                #item_iter_ty_ident::new(self)
+            },
+            item_iter_ty,
+            item_iter_ty_lifetime,
+        })
+    }
+
+    fn debug(&self) -> bool {
+        self.debug
+    }
+}

xso-proc/src/lib.rs ๐Ÿ”—

@@ -27,6 +27,7 @@ use syn::*;
 
 mod common;
 mod compound;
+mod enums;
 mod error_message;
 mod field;
 mod meta;
@@ -41,12 +42,17 @@ use common::{AsXmlParts, FromXmlParts, ItemDef};
 ///
 /// If the item is of an unsupported variant, an appropriate error is
 /// returned.
-fn parse_struct(item: Item) -> Result<(Visibility, Ident, structs::StructDef)> {
+fn parse_struct(item: Item) -> Result<(Visibility, Ident, Box<dyn ItemDef>)> {
     match item {
         Item::Struct(item) => {
             let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
             let def = structs::StructDef::new(&item.ident, meta, &item.fields)?;
-            Ok((item.vis, item.ident, def))
+            Ok((item.vis, item.ident, Box::new(def)))
+        }
+        Item::Enum(item) => {
+            let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
+            let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?;
+            Ok((item.vis, item.ident, Box::new(def)))
         }
         other => Err(Error::new_spanned(other, "cannot derive on this item")),
     }

xso-proc/src/meta.rs ๐Ÿ”—

@@ -9,6 +9,8 @@
 //! This module is concerned with parsing attributes from the Rust "meta"
 //! annotations on structs, enums, enum variants and fields.
 
+use std::hash::{Hash, Hasher};
+
 use proc_macro2::{Span, TokenStream};
 use quote::{quote, quote_spanned};
 use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
@@ -56,7 +58,7 @@ impl quote::ToTokens for NamespaceRef {
 }
 
 /// Value for the `#[xml(name = .. )]` attribute.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub(crate) enum NameRef {
     /// The XML name is specified as a string literal.
     Literal {
@@ -71,6 +73,38 @@ pub(crate) enum NameRef {
     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) {

xso-proc/src/state.rs ๐Ÿ”—

@@ -176,6 +176,7 @@ impl FromEventsSubmachine {
             state_defs,
             advance_match_arms,
             variants: vec![FromEventsEntryPoint { init: self.init }],
+            pre_init: TokenStream::default(),
         }
     }
 
@@ -369,6 +370,9 @@ pub(crate) struct FromEventsStateMachine {
     /// Extra items which are needed for the state machine implementation.
     defs: TokenStream,
 
+    /// Extra code run during pre-init phase.
+    pre_init: TokenStream,
+
     /// A sequence of enum variant declarations, separated and terminated by
     /// commas.
     state_defs: TokenStream,
@@ -390,6 +394,36 @@ pub(crate) struct FromEventsStateMachine {
 }
 
 impl FromEventsStateMachine {
+    /// Create a new, empty state machine.
+    pub(crate) fn new() -> Self {
+        Self {
+            defs: TokenStream::default(),
+            state_defs: TokenStream::default(),
+            advance_match_arms: TokenStream::default(),
+            pre_init: TokenStream::default(),
+            variants: Vec::new(),
+        }
+    }
+
+    /// Merge another state machine into this state machine.
+    ///
+    /// This *discards* the other state machine's pre-init code.
+    pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
+        self.defs.extend(other.defs);
+        self.state_defs.extend(other.state_defs);
+        self.advance_match_arms.extend(other.advance_match_arms);
+        self.variants.extend(other.variants);
+    }
+
+    /// Set additional code to inject at the head of the `new` method for the
+    /// builder.
+    ///
+    /// This can be used to do preliminary checks and is commonly used with
+    /// specifically-formed init codes on the variants.
+    pub(crate) fn set_pre_init(&mut self, code: TokenStream) {
+        self.pre_init = code;
+    }
+
     /// Render the state machine as a token stream.
     ///
     /// The token stream contains the following pieces:
@@ -411,9 +445,10 @@ impl FromEventsStateMachine {
             state_defs,
             advance_match_arms,
             variants,
+            pre_init,
         } = self;
 
-        let mut init_body = TokenStream::default();
+        let mut init_body = pre_init;
         for variant in variants {
             let FromEventsEntryPoint { init } = variant;
             init_body.extend(quote! {
@@ -550,6 +585,24 @@ pub(crate) struct AsItemsStateMachine {
 }
 
 impl AsItemsStateMachine {
+    /// Create a new, empty state machine.
+    pub(crate) fn new() -> Self {
+        Self {
+            defs: TokenStream::default(),
+            state_defs: TokenStream::default(),
+            advance_match_arms: TokenStream::default(),
+            variants: Vec::new(),
+        }
+    }
+
+    /// Merge another state machine into this state machine.
+    pub(crate) fn merge(&mut self, other: AsItemsStateMachine) {
+        self.defs.extend(other.defs);
+        self.state_defs.extend(other.state_defs);
+        self.advance_match_arms.extend(other.advance_match_arms);
+        self.variants.extend(other.variants);
+    }
+
     /// Render the state machine as a token stream.
     ///
     /// The token stream contains the following pieces:
@@ -588,7 +641,7 @@ impl AsItemsStateMachine {
             let mut match_arms = TokenStream::default();
             for AsItemsEntryPoint { destructure, init } in variants {
                 match_arms.extend(quote! {
-                    #destructure => #init,
+                    #destructure => { #init }
                 });
             }
 

xso-proc/src/structs.rs ๐Ÿ”—

@@ -163,7 +163,7 @@ impl ItemDef for StructDef {
         let defs = self
             .inner
             .make_as_item_iter_statemachine(
-                &target_ty_ident.clone().into(),
+                &Path::from(target_ty_ident.clone()).into(),
                 "Struct",
                 &item_iter_ty_lifetime,
             )?

xso/ChangeLog ๐Ÿ”—

@@ -5,6 +5,7 @@ Version NEXT:
         be wrapped in Option or Box.
       - Support for overriding the names of the types generated by the derive
         macros.
+      - Support for deriving FromXml and AsXml on enums.
 
 Version 0.1.2:
 2024-07-26 Jonas Schรคfer <jonas@zombofant.net>

xso/src/from_xml_doc.md ๐Ÿ”—

@@ -71,6 +71,60 @@ By default, the builder type uses the type's name suffixed with
 `FromXmlBuilder` and the iterator type uses the type's name suffixed with
 `AsXmlIterator`.
 
+### Enum meta
+
+The following keys are defined on enums:
+
+| Key | Value type | Description |
+| --- | --- | --- |
+| `namespace` | *string literal* or *path* | The XML element namespace to match for this enum. If it is a *path*, it must point at a `&'static str`. |
+| `builder` | optional *ident* | The name to use for the generated builder type. |
+| `iterator` | optional *ident* | The name to use for the generated iterator type. |
+
+All variants of an enum live within the same namespace and are distinguished
+exclusively by their XML name within that namespace. The contents of the XML
+element (including attributes) is not inspected before selecting the variant
+when parsing XML.
+
+For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
+documentation above.
+
+#### Enum variant meta
+
+| Key | Value type | Description |
+| --- | --- | --- |
+| `name` | *string literal* or *path* | The XML element name to match for this variant. If it is a *path*, it must point at a `&'static NcNameStr`. |
+
+Note that the `name` value must be a valid XML element name, without colons.
+The namespace prefix, if any, is assigned automatically at serialisation time
+and cannot be overridden.
+
+#### Example
+
+```rust
+# use xso::FromXml;
+#[derive(FromXml, Debug, PartialEq)]
+#[xml(namespace = "urn:example")]
+enum Foo {
+    #[xml(name = "a")]
+    Variant1 {
+        #[xml(attribute)]
+        foo: String,
+    },
+    #[xml(name = "b")]
+    Variant2 {
+        #[xml(attribute)]
+        bar: String,
+    },
+}
+
+let foo: Foo = xso::from_bytes(b"<a xmlns='urn:example' foo='hello'/>").unwrap();
+assert_eq!(foo, Foo::Variant1 { foo: "hello".to_string() });
+
+let foo: Foo = xso::from_bytes(b"<b xmlns='urn:example' bar='hello'/>").unwrap();
+assert_eq!(foo, Foo::Variant2 { bar: "hello".to_string() });
+```
+
 ### Field meta
 
 For fields, the *meta* consists of a nested meta inside the `#[xml(..)]` meta,