xso-proc: ensure that all meta keys are handled

Jonas Schรคfer created

See inline comments for the rationale.

Change summary

xso-proc/src/enums.rs   | 61 ++++++++++++++++++++++++++----------------
xso-proc/src/meta.rs    | 38 ++++++++++++++++++++++++++
xso-proc/src/structs.rs | 26 +++++++++++++----
3 files changed, 95 insertions(+), 30 deletions(-)

Detailed changes

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

@@ -15,7 +15,7 @@ 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::meta::{reject_key, Flag, NameRef, NamespaceRef, XmlCompoundMeta};
 use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
 
 /// The definition of an enum variant, switched on the XML element's name.
@@ -33,17 +33,25 @@ struct NameVariant {
 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"));
+        // We destructure here so that we get informed when new fields are
+        // added and can handle them, either by processing them or raising
+        // an error if they are present.
+        let XmlCompoundMeta {
+            span: meta_span,
+            namespace,
+            name,
+            debug,
+            builder,
+            iterator,
+        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
+
+        reject_key!(debug flag not on "enum variants" only on "enums and structs");
+        reject_key!(namespace not on "enum variants" only on "enums and structs");
+        reject_key!(builder not on "enum variants" only on "enums and structs");
+        reject_key!(iterator not on "enum variants" only on "enums and structs");
+
+        let Some(name) = name else {
+            return Err(Error::new(meta_span, "`name` is required on enum variants"));
         };
 
         Ok(Self {
@@ -154,15 +162,22 @@ impl EnumDef {
         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)",
-            ));
-        }
+        // We destructure here so that we get informed when new fields are
+        // added and can handle them, either by processing them or raising
+        // an error if they are present.
+        let XmlCompoundMeta {
+            span: meta_span,
+            namespace,
+            name,
+            debug,
+            builder,
+            iterator,
+        } = meta;
+
+        reject_key!(name not on "enums" only on "their variants");
 
-        let Some(namespace) = meta.namespace else {
-            return Err(Error::new(meta.span, "`namespace` is required on enums"));
+        let Some(namespace) = namespace else {
+            return Err(Error::new(meta_span, "`namespace` is required on enums"));
         };
 
         let mut variants = Vec::new();
@@ -182,12 +197,12 @@ impl EnumDef {
             variants.push(variant);
         }
 
-        let builder_ty_ident = match meta.builder {
+        let builder_ty_ident = match builder {
             Some(v) => v,
             None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
         };
 
-        let item_iter_ty_ident = match meta.iterator {
+        let item_iter_ty_ident = match iterator {
             Some(v) => v,
             None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
         };
@@ -198,7 +213,7 @@ impl EnumDef {
             target_ty_ident: ident.clone(),
             builder_ty_ident,
             item_iter_ty_ident,
-            debug: meta.debug.is_set(),
+            debug: debug.is_set(),
         })
     }
 }

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

@@ -22,6 +22,44 @@ 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)]
 pub(crate) enum NamespaceRef {

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

@@ -41,20 +41,32 @@ pub(crate) struct StructDef {
 impl StructDef {
     /// Create a new struct from its name, meta, and fields.
     pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
-        let Some(namespace) = meta.namespace else {
-            return Err(Error::new(meta.span, "`namespace` is required on structs"));
+        // We destructure here so that we get informed when new fields are
+        // added and can handle them, either by processing them or raising
+        // an error if they are present.
+        let XmlCompoundMeta {
+            span: meta_span,
+            namespace,
+            name,
+            debug,
+            builder,
+            iterator,
+        } = meta;
+
+        let Some(namespace) = namespace else {
+            return Err(Error::new(meta_span, "`namespace` is required on structs"));
         };
 
-        let Some(name) = meta.name else {
-            return Err(Error::new(meta.span, "`name` is required on structs"));
+        let Some(name) = name else {
+            return Err(Error::new(meta_span, "`name` is required on structs"));
         };
 
-        let builder_ty_ident = match meta.builder {
+        let builder_ty_ident = match builder {
             Some(v) => v,
             None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
         };
 
-        let item_iter_ty_ident = match meta.iterator {
+        let item_iter_ty_ident = match iterator {
             Some(v) => v,
             None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
         };
@@ -66,7 +78,7 @@ impl StructDef {
             target_ty_ident: ident.clone(),
             builder_ty_ident,
             item_iter_ty_ident,
-            debug: meta.debug.is_set(),
+            debug: debug.is_set(),
         })
     }
 }