xso_proc/
enums.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Handling of enums
8
9use std::collections::HashMap;
10
11use proc_macro2::{Span, TokenStream};
12use quote::{quote, ToTokens};
13use syn::*;
14
15use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher};
16use crate::compound::Compound;
17use crate::error_message::ParentRef;
18use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
19use crate::state::{AsItemsStateMachine, FromEventsMatchMode, FromEventsStateMachine};
20use crate::structs::StructInner;
21use crate::types::{ref_ty, ty_from_ident};
22
23/// The definition of an enum variant, switched on the XML element's name,
24/// inside a [`NameSwitchedEnum`].
25struct NameVariant {
26    /// The XML name of the element to map the enum variant to.
27    name: NameRef,
28
29    /// The name of the variant
30    ident: Ident,
31
32    /// The field(s) of this struct.
33    inner: Compound,
34}
35
36impl NameVariant {
37    /// Construct a new name-selected variant from its declaration.
38    fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
39        // We destructure here so that we get informed when new fields are
40        // added and can handle them, either by processing them or raising
41        // an error if they are present.
42        let XmlCompoundMeta {
43            span: meta_span,
44            qname: QNameRef { namespace, name },
45            exhaustive,
46            debug,
47            builder,
48            iterator,
49            on_unknown_attribute,
50            on_unknown_child,
51            transparent,
52            discard,
53            deserialize_callback,
54            attribute,
55            value,
56        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
57
58        reject_key!(debug flag not on "enum variants" only on "enums and structs");
59        reject_key!(exhaustive flag not on "enum variants" only on "enums");
60        reject_key!(namespace not on "enum variants" only on "enums and structs");
61        reject_key!(builder not on "enum variants" only on "enums and structs");
62        reject_key!(iterator not on "enum variants" only on "enums and structs");
63        reject_key!(transparent flag not on "named enum variants" only on "structs");
64        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
65        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
66        reject_key!(value not on "name-switched enum variants" only on "attribute-switched enum variants");
67
68        let Some(name) = name else {
69            return Err(Error::new(
70                meta_span,
71                "`name` is required on name-switched enum variants",
72            ));
73        };
74
75        Ok(Self {
76            name,
77            ident: decl.ident.clone(),
78            inner: Compound::from_fields(
79                &decl.fields,
80                enum_namespace,
81                on_unknown_attribute,
82                on_unknown_child,
83                discard,
84            )?,
85        })
86    }
87
88    fn make_from_events_statemachine(
89        &self,
90        enum_ident: &Ident,
91        state_ty_ident: &Ident,
92    ) -> Result<FromEventsStateMachine> {
93        let xml_name = &self.name;
94
95        Ok(self
96            .inner
97            .make_from_events_statemachine(
98                state_ty_ident,
99                &ParentRef::Named(Path {
100                    leading_colon: None,
101                    segments: [
102                        PathSegment::from(enum_ident.clone()),
103                        self.ident.clone().into(),
104                    ]
105                    .into_iter()
106                    .collect(),
107                }),
108                &self.ident.to_string(),
109            )?
110            .with_augmented_init(|init| {
111                quote! {
112                    if name.1 != #xml_name {
113                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
114                            name,
115                            attrs,
116                        })
117                    } else {
118                        #init
119                    }
120                }
121            })
122            .compile())
123    }
124
125    fn make_as_item_iter_statemachine(
126        &self,
127        xml_namespace: &NamespaceRef,
128        enum_ident: &Ident,
129        state_ty_ident: &Ident,
130        item_iter_ty_lifetime: &Lifetime,
131    ) -> Result<AsItemsStateMachine> {
132        let xml_name = &self.name;
133
134        Ok(self
135            .inner
136            .make_as_item_iter_statemachine(
137                &ParentRef::Named(Path {
138                    leading_colon: None,
139                    segments: [
140                        PathSegment::from(enum_ident.clone()),
141                        self.ident.clone().into(),
142                    ]
143                    .into_iter()
144                    .collect(),
145                }),
146                state_ty_ident,
147                &self.ident.to_string(),
148                item_iter_ty_lifetime,
149            )?
150            .with_augmented_init(|init| {
151                quote! {
152                    let name = (
153                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
154                        ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
155                    );
156                    #init
157                }
158            })
159            .compile())
160    }
161}
162
163/// The definition of a enum which switches based on the XML element name,
164/// with the XML namespace fixed.
165struct NameSwitchedEnum {
166    /// The XML namespace of the element to map the enum to.
167    namespace: NamespaceRef,
168
169    /// The variants of the enum.
170    variants: Vec<NameVariant>,
171
172    /// Flag indicating whether the enum is exhaustive.
173    exhaustive: bool,
174}
175
176impl NameSwitchedEnum {
177    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
178        namespace: NamespaceRef,
179        exhaustive: Flag,
180        variant_iter: I,
181    ) -> Result<Self> {
182        let mut variants = Vec::new();
183        let mut seen_names = HashMap::new();
184        for variant in variant_iter {
185            let variant = NameVariant::new(variant, &namespace)?;
186            if let Some(other) = seen_names.get(&variant.name) {
187                return Err(Error::new_spanned(
188                    variant.name,
189                    format!(
190                        "duplicate `name` in enum: variants {} and {} have the same XML name",
191                        other, variant.ident
192                    ),
193                ));
194            }
195            seen_names.insert(variant.name.clone(), variant.ident.clone());
196            variants.push(variant);
197        }
198
199        Ok(Self {
200            namespace,
201            variants,
202            exhaustive: exhaustive.is_set(),
203        })
204    }
205
206    /// Provide the `XmlNameMatcher` template for this enum.
207    ///
208    /// Name-switched enums always return a matcher in the namespace of the
209    /// elements they match against.
210    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
211        Ok(XmlNameMatcher::InNamespace(
212            self.namespace.to_token_stream(),
213        ))
214    }
215
216    /// Build the deserialisation statemachine for the name-switched enum.
217    fn make_from_events_statemachine(
218        &self,
219        target_ty_ident: &Ident,
220        state_ty_ident: &Ident,
221    ) -> Result<FromEventsStateMachine> {
222        let xml_namespace = &self.namespace;
223
224        let mut statemachine = FromEventsStateMachine::new();
225        for variant in self.variants.iter() {
226            statemachine
227                .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?);
228        }
229
230        statemachine.set_pre_init(quote! {
231            if name.0 != #xml_namespace {
232                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
233                    name,
234                    attrs,
235                })
236            }
237        });
238
239        if self.exhaustive {
240            let mismatch_err = format!("This is not a {} element.", target_ty_ident);
241            statemachine.set_fallback(quote! {
242                ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
243                    ::xso::error::Error::Other(#mismatch_err),
244                ))
245            })
246        }
247
248        Ok(statemachine)
249    }
250
251    /// Build the serialisation statemachine for the name-switched enum.
252    fn make_as_item_iter_statemachine(
253        &self,
254        target_ty_ident: &Ident,
255        state_ty_ident: &Ident,
256        item_iter_ty_lifetime: &Lifetime,
257    ) -> Result<AsItemsStateMachine> {
258        let mut statemachine = AsItemsStateMachine::new();
259        for variant in self.variants.iter() {
260            statemachine.merge(variant.make_as_item_iter_statemachine(
261                &self.namespace,
262                target_ty_ident,
263                state_ty_ident,
264                item_iter_ty_lifetime,
265            )?);
266        }
267
268        Ok(statemachine)
269    }
270}
271
272/// The definition of an enum variant, switched on the XML element's
273/// attribute's value, inside a [`AttributeSwitchedEnum`].
274struct ValueVariant {
275    /// The verbatim value to match.
276    value: String,
277
278    /// The identifier of the variant
279    ident: Ident,
280
281    /// The field(s) of the variant.
282    inner: Compound,
283}
284
285impl ValueVariant {
286    /// Construct a new value-selected variant from its declaration.
287    fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
288        // We destructure here so that we get informed when new fields are
289        // added and can handle them, either by processing them or raising
290        // an error if they are present.
291        let XmlCompoundMeta {
292            span: meta_span,
293            qname: QNameRef { namespace, name },
294            exhaustive,
295            debug,
296            builder,
297            iterator,
298            on_unknown_attribute,
299            on_unknown_child,
300            transparent,
301            discard,
302            deserialize_callback,
303            attribute,
304            value,
305        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
306
307        reject_key!(debug flag not on "enum variants" only on "enums and structs");
308        reject_key!(exhaustive flag not on "enum variants" only on "enums");
309        reject_key!(namespace not on "enum variants" only on "enums and structs");
310        reject_key!(name not on "attribute-switched enum variants" only on "enums, structs and name-switched enum variants");
311        reject_key!(builder not on "enum variants" only on "enums and structs");
312        reject_key!(iterator not on "enum variants" only on "enums and structs");
313        reject_key!(transparent flag not on "named enum variants" only on "structs");
314        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
315        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
316
317        let Some(value) = value else {
318            return Err(Error::new(
319                meta_span,
320                "`value` is required on attribute-switched enum variants",
321            ));
322        };
323
324        Ok(Self {
325            value: value.value(),
326            ident: decl.ident.clone(),
327            inner: Compound::from_fields(
328                &decl.fields,
329                enum_namespace,
330                on_unknown_attribute,
331                on_unknown_child,
332                discard,
333            )?,
334        })
335    }
336
337    fn make_from_events_statemachine(
338        &self,
339        enum_ident: &Ident,
340        state_ty_ident: &Ident,
341    ) -> Result<FromEventsStateMachine> {
342        let value = &self.value;
343
344        Ok(self
345            .inner
346            .make_from_events_statemachine(
347                state_ty_ident,
348                &ParentRef::Named(Path {
349                    leading_colon: None,
350                    segments: [
351                        PathSegment::from(enum_ident.clone()),
352                        self.ident.clone().into(),
353                    ]
354                    .into_iter()
355                    .collect(),
356                }),
357                &self.ident.to_string(),
358            )?
359            .with_augmented_init(|init| {
360                quote! {
361                    #value => { #init },
362                }
363            })
364            .compile())
365    }
366
367    fn make_as_item_iter_statemachine(
368        &self,
369        elem_namespace: &NamespaceRef,
370        elem_name: &NameRef,
371        attr_namespace: &TokenStream,
372        attr_name: &NameRef,
373        enum_ident: &Ident,
374        state_ty_ident: &Ident,
375        item_iter_ty_lifetime: &Lifetime,
376    ) -> Result<AsItemsStateMachine> {
377        let attr_value = &self.value;
378
379        Ok(self
380            .inner
381            .make_as_item_iter_statemachine(
382                &ParentRef::Named(Path {
383                    leading_colon: None,
384                    segments: [
385                        PathSegment::from(enum_ident.clone()),
386                        self.ident.clone().into(),
387                    ]
388                    .into_iter()
389                    .collect(),
390                }),
391                state_ty_ident,
392                &self.ident.to_string(),
393                item_iter_ty_lifetime,
394            )?
395            .with_augmented_init(|init| {
396                quote! {
397                    let name = (
398                        ::xso::exports::rxml::Namespace::from(#elem_namespace),
399                        ::xso::exports::alloc::borrow::Cow::Borrowed(#elem_name),
400                    );
401                    #init
402                }
403            })
404            .with_extra_header_state(
405                // Note: we convert the identifier to a string here to prevent
406                // its Span from leaking into the new identifier.
407                quote::format_ident!("{}Discriminator", &self.ident.to_string()),
408                quote! {
409                    ::xso::Item::Attribute(
410                        #attr_namespace,
411                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_name),
412                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_value),
413                    )
414                },
415            )
416            .compile())
417    }
418}
419
420/// The definition of an enum where each variant represents a different value
421/// of a fixed attribute.
422struct AttributeSwitchedEnum {
423    /// The XML namespace of the element.
424    elem_namespace: NamespaceRef,
425
426    /// The XML name of the element.
427    elem_name: NameRef,
428
429    /// The XML namespace of the attribute, or None if the attribute isn't
430    /// namespaced.'
431    attr_namespace: Option<NamespaceRef>,
432
433    /// The XML name of the attribute.
434    attr_name: NameRef,
435
436    /// Enum variant definitions
437    variants: Vec<ValueVariant>,
438}
439
440impl AttributeSwitchedEnum {
441    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
442        elem_namespace: NamespaceRef,
443        elem_name: NameRef,
444        attr_namespace: Option<NamespaceRef>,
445        attr_name: NameRef,
446        variant_iter: I,
447    ) -> Result<Self> {
448        let mut variants = Vec::new();
449        let mut seen_values = HashMap::new();
450        for variant in variant_iter {
451            let variant = ValueVariant::new(variant, &elem_namespace)?;
452            if let Some(other) = seen_values.get(&variant.value) {
453                return Err(Error::new_spanned(
454                    variant.value,
455                    format!(
456                        "duplicate `value` in enum: variants {} and {} have the same attribute value",
457                        other, variant.ident
458                    ),
459                ));
460            }
461            seen_values.insert(variant.value.clone(), variant.ident.clone());
462            variants.push(variant);
463        }
464
465        Ok(Self {
466            elem_namespace,
467            elem_name,
468            attr_namespace,
469            attr_name,
470            variants,
471        })
472    }
473
474    /// Provide the `XmlNameMatcher` template for this enum.
475    ///
476    /// Attribute-switched enums always return a matcher specific to the
477    /// element they operate on.
478    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
479        Ok(XmlNameMatcher::Specific(
480            self.elem_namespace.to_token_stream(),
481            self.elem_name.to_token_stream(),
482        ))
483    }
484
485    /// Build the deserialisation statemachine for the attribute-switched enum.
486    fn make_from_events_statemachine(
487        &self,
488        target_ty_ident: &Ident,
489        state_ty_ident: &Ident,
490    ) -> Result<FromEventsStateMachine> {
491        let elem_namespace = &self.elem_namespace;
492        let elem_name = &self.elem_name;
493        let attr_namespace = match self.attr_namespace.as_ref() {
494            Some(v) => v.to_token_stream(),
495            None => quote! {
496                ::xso::exports::rxml::Namespace::none()
497            },
498        };
499        let attr_name = &self.attr_name;
500
501        let mut statemachine = FromEventsStateMachine::new();
502        for variant in self.variants.iter() {
503            statemachine
504                .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?);
505        }
506
507        statemachine.set_pre_init(quote! {
508            let attr = {
509                if name.0 != #elem_namespace || name.1 != #elem_name {
510                    return ::core::result::Result::Err(
511                        ::xso::error::FromEventsError::Mismatch {
512                            name,
513                            attrs,
514                        },
515                    );
516                }
517
518                let ::core::option::Option::Some(attr) = attrs.remove(#attr_namespace, #attr_name) else {
519                    return ::core::result::Result::Err(
520                        ::xso::error::FromEventsError::Invalid(
521                            ::xso::error::Error::Other("Missing discriminator attribute.")
522                        ),
523                    );
524                };
525
526                attr
527            };
528        });
529        statemachine.set_fallback(quote! {
530            ::core::result::Result::Err(
531                ::xso::error::FromEventsError::Invalid(
532                    ::xso::error::Error::Other("Unknown value for discriminator attribute.")
533                ),
534            )
535        });
536        statemachine.set_match_mode(FromEventsMatchMode::Matched {
537            expr: quote! { attr.as_str() },
538        });
539
540        Ok(statemachine)
541    }
542
543    /// Build the serialisation statemachine for the attribute-switched enum.
544    fn make_as_item_iter_statemachine(
545        &self,
546        target_ty_ident: &Ident,
547        state_ty_ident: &Ident,
548        item_iter_ty_lifetime: &Lifetime,
549    ) -> Result<AsItemsStateMachine> {
550        let attr_namespace = match self.attr_namespace.as_ref() {
551            Some(v) => v.to_token_stream(),
552            None => quote! {
553                ::xso::exports::rxml::Namespace::NONE
554            },
555        };
556
557        let mut statemachine = AsItemsStateMachine::new();
558        for variant in self.variants.iter() {
559            statemachine.merge(variant.make_as_item_iter_statemachine(
560                &self.elem_namespace,
561                &self.elem_name,
562                &attr_namespace,
563                &self.attr_name,
564                target_ty_ident,
565                state_ty_ident,
566                item_iter_ty_lifetime,
567            )?);
568        }
569
570        Ok(statemachine)
571    }
572}
573
574/// The definition of an enum variant in a [`DynamicEnum`].
575struct DynamicVariant {
576    /// The identifier of the enum variant.
577    ident: Ident,
578
579    /// The definition of the struct-like which resembles the enum variant.
580    inner: StructInner,
581}
582
583impl DynamicVariant {
584    fn new(variant: &Variant) -> Result<Self> {
585        let ident = variant.ident.clone();
586        let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
587
588        // We destructure here so that we get informed when new fields are
589        // added and can handle them, either by processing them or raising
590        // an error if they are present.
591        let XmlCompoundMeta {
592            span: _,
593            qname: _, // used by StructInner
594            ref exhaustive,
595            ref debug,
596            ref builder,
597            ref iterator,
598            on_unknown_attribute: _, // used by StructInner
599            on_unknown_child: _,     // used by StructInner
600            transparent: _,          // used by StructInner
601            discard: _,              // used by StructInner
602            ref deserialize_callback,
603            ref attribute,
604            ref value,
605        } = meta;
606
607        reject_key!(debug flag not on "enum variants" only on "enums and structs");
608        reject_key!(exhaustive flag not on "enum variants" only on "enums");
609        reject_key!(builder not on "enum variants" only on "enums and structs");
610        reject_key!(iterator not on "enum variants" only on "enums and structs");
611        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
612        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
613        reject_key!(value not on "dynamic enum variants" only on "attribute-switched enum variants");
614
615        let inner = StructInner::new(meta, &variant.fields)?;
616        Ok(Self { ident, inner })
617    }
618}
619
620/// The definition of an enum where each variant is a completely unrelated
621/// possible XML subtree.
622struct DynamicEnum {
623    /// The enum variants.
624    variants: Vec<DynamicVariant>,
625}
626
627impl DynamicEnum {
628    fn new<'x, I: IntoIterator<Item = &'x Variant>>(variant_iter: I) -> Result<Self> {
629        let mut variants = Vec::new();
630        for variant in variant_iter {
631            variants.push(DynamicVariant::new(variant)?);
632        }
633
634        Ok(Self { variants })
635    }
636
637    /// Provide the `XmlNameMatcher` template for this dynamic enum.
638    ///
639    /// Dynamic enums return the superset of the matchers of their variants,
640    /// which in many cases will be XmlNameMatcher::Any.
641    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
642        let mut iter = self.variants.iter();
643        let Some(first) = iter.next() else {
644            // Since the enum has no variants, it cannot match anything. We
645            // use an empty namespace and an empty name, which will never
646            // match.
647            return Ok(XmlNameMatcher::Custom(quote! {
648                ::xso::fromxml::XmlNameMatcher::<'static>::Specific("", "")
649            }));
650        };
651
652        let first_matcher = first.inner.xml_name_matcher()?;
653        let mut composition = quote! { #first_matcher };
654        for variant in iter {
655            let next_matcher = variant.inner.xml_name_matcher()?;
656            composition.extend(quote! { .superset(#next_matcher) });
657        }
658
659        Ok(XmlNameMatcher::Custom(composition))
660    }
661
662    /// Build the deserialisation statemachine for the dynamic enum.
663    fn make_from_events_statemachine(
664        &self,
665        target_ty_ident: &Ident,
666        state_ty_ident: &Ident,
667    ) -> Result<FromEventsStateMachine> {
668        let mut statemachine = FromEventsStateMachine::new();
669        for variant in self.variants.iter() {
670            let submachine = variant.inner.make_from_events_statemachine(
671                state_ty_ident,
672                &ParentRef::Named(Path {
673                    leading_colon: None,
674                    segments: [
675                        PathSegment::from(target_ty_ident.clone()),
676                        variant.ident.clone().into(),
677                    ]
678                    .into_iter()
679                    .collect(),
680                }),
681                &variant.ident.to_string(),
682            )?;
683
684            statemachine.merge(submachine.compile());
685        }
686
687        Ok(statemachine)
688    }
689
690    /// Build the serialisation statemachine for the dynamic enum.
691    fn make_as_item_iter_statemachine(
692        &self,
693        target_ty_ident: &Ident,
694        state_ty_ident: &Ident,
695        item_iter_ty_lifetime: &Lifetime,
696    ) -> Result<AsItemsStateMachine> {
697        let mut statemachine = AsItemsStateMachine::new();
698        for variant in self.variants.iter() {
699            let submachine = variant.inner.make_as_item_iter_statemachine(
700                &ParentRef::Named(Path {
701                    leading_colon: None,
702                    segments: [
703                        PathSegment::from(target_ty_ident.clone()),
704                        variant.ident.clone().into(),
705                    ]
706                    .into_iter()
707                    .collect(),
708                }),
709                state_ty_ident,
710                &variant.ident.to_string(),
711                item_iter_ty_lifetime,
712            )?;
713
714            statemachine.merge(submachine.compile());
715        }
716
717        Ok(statemachine)
718    }
719}
720
721/// The definition of an enum.
722enum EnumInner {
723    /// The enum switches based on the XML name of the element, with the XML
724    /// namespace fixed.
725    NameSwitched(NameSwitchedEnum),
726
727    /// The enum switches based on the value of an attribute of the XML
728    /// element.
729    AttributeSwitched(AttributeSwitchedEnum),
730
731    /// The enum consists of variants with entirely unrelated XML structures.
732    Dynamic(DynamicEnum),
733}
734
735impl EnumInner {
736    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
737        meta: XmlCompoundMeta,
738        variant_iter: I,
739    ) -> Result<Self> {
740        // We destructure here so that we get informed when new fields are
741        // added and can handle them, either by processing them or raising
742        // an error if they are present.
743        let XmlCompoundMeta {
744            span,
745            qname: QNameRef { namespace, name },
746            exhaustive,
747            debug,
748            builder,
749            iterator,
750            on_unknown_attribute,
751            on_unknown_child,
752            transparent,
753            discard,
754            deserialize_callback,
755            attribute,
756            value,
757        } = meta;
758
759        // These must've been cleared by the caller. Because these being set
760        // is a programming error (in xso-proc) and not a usage error, we
761        // assert here instead of using reject_key!.
762        assert!(builder.is_none());
763        assert!(iterator.is_none());
764        assert!(!debug.is_set());
765        assert!(deserialize_callback.is_none());
766
767        reject_key!(transparent flag not on "enums" only on "structs");
768        reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs");
769        reject_key!(on_unknown_child not on "enums" only on "enum variants and structs");
770        reject_key!(discard vec not on "enums" only on "enum variants and structs");
771        reject_key!(value not on "enums" only on "attribute-switched enum variants");
772
773        if let Some(attribute) = attribute {
774            let Some(attr_name) = attribute.qname.name else {
775                return Err(Error::new(
776                    attribute.span,
777                    "missing `name` for `attribute` key",
778                ));
779            };
780
781            let attr_namespace = attribute.qname.namespace;
782
783            let Some(elem_namespace) = namespace else {
784                let mut error =
785                    Error::new(span, "missing `namespace` key on attribute-switched enum");
786                error.combine(Error::new(
787                    attribute.span,
788                    "enum is attribute-switched because of the `attribute` key here",
789                ));
790                return Err(error);
791            };
792
793            let Some(elem_name) = name else {
794                let mut error = Error::new(span, "missing `name` key on attribute-switched enum");
795                error.combine(Error::new(
796                    attribute.span,
797                    "enum is attribute-switched because of the `attribute` key here",
798                ));
799                return Err(error);
800            };
801
802            if !exhaustive.is_set() {
803                return Err(Error::new(
804                    span,
805                    "attribute-switched enums must be marked as `exhaustive`. non-exhaustive attribute-switched enums are not supported."
806                ));
807            }
808
809            Ok(Self::AttributeSwitched(AttributeSwitchedEnum::new(
810                elem_namespace,
811                elem_name,
812                attr_namespace,
813                attr_name,
814                variant_iter,
815            )?))
816        } else if let Some(namespace) = namespace {
817            reject_key!(name not on "name-switched enums" only on "their variants");
818            Ok(Self::NameSwitched(NameSwitchedEnum::new(
819                namespace,
820                exhaustive,
821                variant_iter,
822            )?))
823        } else {
824            reject_key!(name not on "dynamic enums" only on "their variants");
825            reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums");
826            Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?))
827        }
828    }
829
830    /// Provide the `XmlNameMatcher` template for this enum.
831    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
832        match self {
833            Self::NameSwitched(ref inner) => inner.xml_name_matcher(),
834            Self::AttributeSwitched(ref inner) => inner.xml_name_matcher(),
835            Self::Dynamic(ref inner) => inner.xml_name_matcher(),
836        }
837    }
838
839    /// Build the deserialisation statemachine for the enum.
840    fn make_from_events_statemachine(
841        &self,
842        target_ty_ident: &Ident,
843        state_ty_ident: &Ident,
844    ) -> Result<FromEventsStateMachine> {
845        match self {
846            Self::NameSwitched(ref inner) => {
847                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
848            }
849            Self::AttributeSwitched(ref inner) => {
850                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
851            }
852            Self::Dynamic(ref inner) => {
853                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
854            }
855        }
856    }
857
858    /// Build the serialisation statemachine for the enum.
859    fn make_as_item_iter_statemachine(
860        &self,
861        target_ty_ident: &Ident,
862        state_ty_ident: &Ident,
863        item_iter_ty_lifetime: &Lifetime,
864    ) -> Result<AsItemsStateMachine> {
865        match self {
866            Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine(
867                target_ty_ident,
868                state_ty_ident,
869                item_iter_ty_lifetime,
870            ),
871            Self::AttributeSwitched(ref inner) => inner.make_as_item_iter_statemachine(
872                target_ty_ident,
873                state_ty_ident,
874                item_iter_ty_lifetime,
875            ),
876            Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine(
877                target_ty_ident,
878                state_ty_ident,
879                item_iter_ty_lifetime,
880            ),
881        }
882    }
883}
884
885/// Definition of an enum and how to parse it.
886pub(crate) struct EnumDef {
887    /// Implementation of the enum itself
888    inner: EnumInner,
889
890    /// Name of the target type.
891    target_ty_ident: Ident,
892
893    /// Name of the builder type.
894    builder_ty_ident: Ident,
895
896    /// Name of the iterator type.
897    item_iter_ty_ident: Ident,
898
899    /// Flag whether debug mode is enabled.
900    debug: bool,
901
902    /// Optional validator function to call.
903    deserialize_callback: Option<Path>,
904}
905
906impl EnumDef {
907    /// Create a new enum from its name, meta, and variants.
908    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
909        ident: &Ident,
910        mut meta: XmlCompoundMeta,
911        variant_iter: I,
912    ) -> Result<Self> {
913        let builder_ty_ident = match meta.builder.take() {
914            Some(v) => v,
915            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
916        };
917
918        let item_iter_ty_ident = match meta.iterator.take() {
919            Some(v) => v,
920            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
921        };
922
923        let debug = meta.debug.take().is_set();
924        let deserialize_callback = meta.deserialize_callback.take();
925
926        Ok(Self {
927            inner: EnumInner::new(meta, variant_iter)?,
928            target_ty_ident: ident.clone(),
929            builder_ty_ident,
930            item_iter_ty_ident,
931            debug,
932            deserialize_callback,
933        })
934    }
935}
936
937impl ItemDef for EnumDef {
938    fn make_from_events_builder(
939        &self,
940        vis: &Visibility,
941        name_ident: &Ident,
942        attrs_ident: &Ident,
943    ) -> Result<FromXmlParts> {
944        let target_ty_ident = &self.target_ty_ident;
945        let builder_ty_ident = &self.builder_ty_ident;
946        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
947
948        let defs = self
949            .inner
950            .make_from_events_statemachine(target_ty_ident, &state_ty_ident)?
951            .render(
952                vis,
953                builder_ty_ident,
954                &state_ty_ident,
955                &TypePath {
956                    qself: None,
957                    path: target_ty_ident.clone().into(),
958                }
959                .into(),
960                self.deserialize_callback.as_ref(),
961            )?;
962
963        Ok(FromXmlParts {
964            defs,
965            from_events_body: quote! {
966                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
967            },
968            builder_ty_ident: builder_ty_ident.clone(),
969            name_matcher: self.inner.xml_name_matcher()?,
970        })
971    }
972
973    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
974        let target_ty_ident = &self.target_ty_ident;
975        let item_iter_ty_ident = &self.item_iter_ty_ident;
976        let item_iter_ty_lifetime = Lifetime {
977            apostrophe: Span::call_site(),
978            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
979        };
980        let item_iter_ty = Type::Path(TypePath {
981            qself: None,
982            path: Path {
983                leading_colon: None,
984                segments: [PathSegment {
985                    ident: item_iter_ty_ident.clone(),
986                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
987                        colon2_token: None,
988                        lt_token: token::Lt {
989                            spans: [Span::call_site()],
990                        },
991                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
992                            .into_iter()
993                            .collect(),
994                        gt_token: token::Gt {
995                            spans: [Span::call_site()],
996                        },
997                    }),
998                }]
999                .into_iter()
1000                .collect(),
1001            },
1002        });
1003        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
1004
1005        let defs = self
1006            .inner
1007            .make_as_item_iter_statemachine(
1008                target_ty_ident,
1009                &state_ty_ident,
1010                &item_iter_ty_lifetime,
1011            )?
1012            .render(
1013                vis,
1014                &ref_ty(
1015                    ty_from_ident(target_ty_ident.clone()).into(),
1016                    item_iter_ty_lifetime.clone(),
1017                ),
1018                &state_ty_ident,
1019                &item_iter_ty_lifetime,
1020                &item_iter_ty,
1021            )?;
1022
1023        Ok(AsXmlParts {
1024            defs,
1025            as_xml_iter_body: quote! {
1026                #item_iter_ty_ident::new(self)
1027            },
1028            item_iter_ty,
1029            item_iter_ty_lifetime,
1030        })
1031    }
1032
1033    fn debug(&self) -> bool {
1034        self.debug
1035    }
1036}