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};
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    /// Build the deserialisation statemachine for the name-switched enum.
207    fn make_from_events_statemachine(
208        &self,
209        target_ty_ident: &Ident,
210        state_ty_ident: &Ident,
211    ) -> Result<FromEventsStateMachine> {
212        let xml_namespace = &self.namespace;
213
214        let mut statemachine = FromEventsStateMachine::new();
215        for variant in self.variants.iter() {
216            statemachine
217                .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?);
218        }
219
220        statemachine.set_pre_init(quote! {
221            if name.0 != #xml_namespace {
222                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
223                    name,
224                    attrs,
225                })
226            }
227        });
228
229        if self.exhaustive {
230            let mismatch_err = format!("This is not a {} element.", target_ty_ident);
231            statemachine.set_fallback(quote! {
232                ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
233                    ::xso::error::Error::Other(#mismatch_err),
234                ))
235            })
236        }
237
238        Ok(statemachine)
239    }
240
241    /// Build the serialisation statemachine for the name-switched enum.
242    fn make_as_item_iter_statemachine(
243        &self,
244        target_ty_ident: &Ident,
245        state_ty_ident: &Ident,
246        item_iter_ty_lifetime: &Lifetime,
247    ) -> Result<AsItemsStateMachine> {
248        let mut statemachine = AsItemsStateMachine::new();
249        for variant in self.variants.iter() {
250            statemachine.merge(variant.make_as_item_iter_statemachine(
251                &self.namespace,
252                target_ty_ident,
253                state_ty_ident,
254                item_iter_ty_lifetime,
255            )?);
256        }
257
258        Ok(statemachine)
259    }
260}
261
262/// The definition of an enum variant, switched on the XML element's
263/// attribute's value, inside a [`AttributeSwitchedEnum`].
264struct ValueVariant {
265    /// The verbatim value to match.
266    value: String,
267
268    /// The identifier of the variant
269    ident: Ident,
270
271    /// The field(s) of the variant.
272    inner: Compound,
273}
274
275impl ValueVariant {
276    /// Construct a new value-selected variant from its declaration.
277    fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
278        // We destructure here so that we get informed when new fields are
279        // added and can handle them, either by processing them or raising
280        // an error if they are present.
281        let XmlCompoundMeta {
282            span: meta_span,
283            qname: QNameRef { namespace, name },
284            exhaustive,
285            debug,
286            builder,
287            iterator,
288            on_unknown_attribute,
289            on_unknown_child,
290            transparent,
291            discard,
292            deserialize_callback,
293            attribute,
294            value,
295        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
296
297        reject_key!(debug flag not on "enum variants" only on "enums and structs");
298        reject_key!(exhaustive flag not on "enum variants" only on "enums");
299        reject_key!(namespace not on "enum variants" only on "enums and structs");
300        reject_key!(name not on "attribute-switched enum variants" only on "enums, structs and name-switched enum variants");
301        reject_key!(builder not on "enum variants" only on "enums and structs");
302        reject_key!(iterator not on "enum variants" only on "enums and structs");
303        reject_key!(transparent flag not on "named enum variants" only on "structs");
304        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
305        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
306
307        let Some(value) = value else {
308            return Err(Error::new(
309                meta_span,
310                "`value` is required on attribute-switched enum variants",
311            ));
312        };
313
314        Ok(Self {
315            value: value.value(),
316            ident: decl.ident.clone(),
317            inner: Compound::from_fields(
318                &decl.fields,
319                enum_namespace,
320                on_unknown_attribute,
321                on_unknown_child,
322                discard,
323            )?,
324        })
325    }
326
327    fn make_from_events_statemachine(
328        &self,
329        enum_ident: &Ident,
330        state_ty_ident: &Ident,
331    ) -> Result<FromEventsStateMachine> {
332        let value = &self.value;
333
334        Ok(self
335            .inner
336            .make_from_events_statemachine(
337                state_ty_ident,
338                &ParentRef::Named(Path {
339                    leading_colon: None,
340                    segments: [
341                        PathSegment::from(enum_ident.clone()),
342                        self.ident.clone().into(),
343                    ]
344                    .into_iter()
345                    .collect(),
346                }),
347                &self.ident.to_string(),
348            )?
349            .with_augmented_init(|init| {
350                quote! {
351                    #value => { #init },
352                }
353            })
354            .compile())
355    }
356
357    fn make_as_item_iter_statemachine(
358        &self,
359        elem_namespace: &NamespaceRef,
360        elem_name: &NameRef,
361        attr_namespace: &TokenStream,
362        attr_name: &NameRef,
363        enum_ident: &Ident,
364        state_ty_ident: &Ident,
365        item_iter_ty_lifetime: &Lifetime,
366    ) -> Result<AsItemsStateMachine> {
367        let attr_value = &self.value;
368
369        Ok(self
370            .inner
371            .make_as_item_iter_statemachine(
372                &ParentRef::Named(Path {
373                    leading_colon: None,
374                    segments: [
375                        PathSegment::from(enum_ident.clone()),
376                        self.ident.clone().into(),
377                    ]
378                    .into_iter()
379                    .collect(),
380                }),
381                state_ty_ident,
382                &self.ident.to_string(),
383                item_iter_ty_lifetime,
384            )?
385            .with_augmented_init(|init| {
386                quote! {
387                    let name = (
388                        ::xso::exports::rxml::Namespace::from(#elem_namespace),
389                        ::xso::exports::alloc::borrow::Cow::Borrowed(#elem_name),
390                    );
391                    #init
392                }
393            })
394            .with_extra_header_state(
395                // Note: we convert the identifier to a string here to prevent
396                // its Span from leaking into the new identifier.
397                quote::format_ident!("{}Discriminator", &self.ident.to_string()),
398                quote! {
399                    ::xso::Item::Attribute(
400                        #attr_namespace,
401                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_name),
402                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_value),
403                    )
404                },
405            )
406            .compile())
407    }
408}
409
410/// The definition of an enum where each variant represents a different value
411/// of a fixed attribute.
412struct AttributeSwitchedEnum {
413    /// The XML namespace of the element.
414    elem_namespace: NamespaceRef,
415
416    /// The XML name of the element.
417    elem_name: NameRef,
418
419    /// The XML namespace of the attribute, or None if the attribute isn't
420    /// namespaced.'
421    attr_namespace: Option<NamespaceRef>,
422
423    /// The XML name of the attribute.
424    attr_name: NameRef,
425
426    /// Enum variant definitions
427    variants: Vec<ValueVariant>,
428}
429
430impl AttributeSwitchedEnum {
431    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
432        elem_namespace: NamespaceRef,
433        elem_name: NameRef,
434        attr_namespace: Option<NamespaceRef>,
435        attr_name: NameRef,
436        variant_iter: I,
437    ) -> Result<Self> {
438        let mut variants = Vec::new();
439        let mut seen_values = HashMap::new();
440        for variant in variant_iter {
441            let variant = ValueVariant::new(variant, &elem_namespace)?;
442            if let Some(other) = seen_values.get(&variant.value) {
443                return Err(Error::new_spanned(
444                    variant.value,
445                    format!(
446                        "duplicate `value` in enum: variants {} and {} have the same attribute value",
447                        other, variant.ident
448                    ),
449                ));
450            }
451            seen_values.insert(variant.value.clone(), variant.ident.clone());
452            variants.push(variant);
453        }
454
455        Ok(Self {
456            elem_namespace,
457            elem_name,
458            attr_namespace,
459            attr_name,
460            variants,
461        })
462    }
463
464    /// Build the deserialisation statemachine for the attribute-switched enum.
465    fn make_from_events_statemachine(
466        &self,
467        target_ty_ident: &Ident,
468        state_ty_ident: &Ident,
469    ) -> Result<FromEventsStateMachine> {
470        let elem_namespace = &self.elem_namespace;
471        let elem_name = &self.elem_name;
472        let attr_namespace = match self.attr_namespace.as_ref() {
473            Some(v) => v.to_token_stream(),
474            None => quote! {
475                ::xso::exports::rxml::Namespace::none()
476            },
477        };
478        let attr_name = &self.attr_name;
479
480        let mut statemachine = FromEventsStateMachine::new();
481        for variant in self.variants.iter() {
482            statemachine
483                .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?);
484        }
485
486        statemachine.set_pre_init(quote! {
487            let attr = {
488                if name.0 != #elem_namespace || name.1 != #elem_name {
489                    return ::core::result::Result::Err(
490                        ::xso::error::FromEventsError::Mismatch {
491                            name,
492                            attrs,
493                        },
494                    );
495                }
496
497                let ::core::option::Option::Some(attr) = attrs.remove(#attr_namespace, #attr_name) else {
498                    return ::core::result::Result::Err(
499                        ::xso::error::FromEventsError::Invalid(
500                            ::xso::error::Error::Other("Missing discriminator attribute.")
501                        ),
502                    );
503                };
504
505                attr
506            };
507        });
508        statemachine.set_fallback(quote! {
509            ::core::result::Result::Err(
510                ::xso::error::FromEventsError::Invalid(
511                    ::xso::error::Error::Other("Unknown value for discriminator attribute.")
512                ),
513            )
514        });
515        statemachine.set_match_mode(FromEventsMatchMode::Matched {
516            expr: quote! { attr.as_str() },
517        });
518
519        Ok(statemachine)
520    }
521
522    /// Build the serialisation statemachine for the attribute-switched enum.
523    fn make_as_item_iter_statemachine(
524        &self,
525        target_ty_ident: &Ident,
526        state_ty_ident: &Ident,
527        item_iter_ty_lifetime: &Lifetime,
528    ) -> Result<AsItemsStateMachine> {
529        let attr_namespace = match self.attr_namespace.as_ref() {
530            Some(v) => v.to_token_stream(),
531            None => quote! {
532                ::xso::exports::rxml::Namespace::NONE
533            },
534        };
535
536        let mut statemachine = AsItemsStateMachine::new();
537        for variant in self.variants.iter() {
538            statemachine.merge(variant.make_as_item_iter_statemachine(
539                &self.elem_namespace,
540                &self.elem_name,
541                &attr_namespace,
542                &self.attr_name,
543                target_ty_ident,
544                state_ty_ident,
545                item_iter_ty_lifetime,
546            )?);
547        }
548
549        Ok(statemachine)
550    }
551}
552
553/// The definition of an enum variant in a [`DynamicEnum`].
554struct DynamicVariant {
555    /// The identifier of the enum variant.
556    ident: Ident,
557
558    /// The definition of the struct-like which resembles the enum variant.
559    inner: StructInner,
560}
561
562impl DynamicVariant {
563    fn new(variant: &Variant) -> Result<Self> {
564        let ident = variant.ident.clone();
565        let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
566
567        // We destructure here so that we get informed when new fields are
568        // added and can handle them, either by processing them or raising
569        // an error if they are present.
570        let XmlCompoundMeta {
571            span: _,
572            qname: _, // used by StructInner
573            ref exhaustive,
574            ref debug,
575            ref builder,
576            ref iterator,
577            on_unknown_attribute: _, // used by StructInner
578            on_unknown_child: _,     // used by StructInner
579            transparent: _,          // used by StructInner
580            discard: _,              // used by StructInner
581            ref deserialize_callback,
582            ref attribute,
583            ref value,
584        } = meta;
585
586        reject_key!(debug flag not on "enum variants" only on "enums and structs");
587        reject_key!(exhaustive flag not on "enum variants" only on "enums");
588        reject_key!(builder not on "enum variants" only on "enums and structs");
589        reject_key!(iterator not on "enum variants" only on "enums and structs");
590        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
591        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
592        reject_key!(value not on "dynamic enum variants" only on "attribute-switched enum variants");
593
594        let inner = StructInner::new(meta, &variant.fields)?;
595        Ok(Self { ident, inner })
596    }
597}
598
599/// The definition of an enum where each variant is a completely unrelated
600/// possible XML subtree.
601struct DynamicEnum {
602    /// The enum variants.
603    variants: Vec<DynamicVariant>,
604}
605
606impl DynamicEnum {
607    fn new<'x, I: IntoIterator<Item = &'x Variant>>(variant_iter: I) -> Result<Self> {
608        let mut variants = Vec::new();
609        for variant in variant_iter {
610            variants.push(DynamicVariant::new(variant)?);
611        }
612
613        Ok(Self { variants })
614    }
615
616    /// Build the deserialisation statemachine for the dynamic enum.
617    fn make_from_events_statemachine(
618        &self,
619        target_ty_ident: &Ident,
620        state_ty_ident: &Ident,
621    ) -> Result<FromEventsStateMachine> {
622        let mut statemachine = FromEventsStateMachine::new();
623        for variant in self.variants.iter() {
624            let submachine = variant.inner.make_from_events_statemachine(
625                state_ty_ident,
626                &ParentRef::Named(Path {
627                    leading_colon: None,
628                    segments: [
629                        PathSegment::from(target_ty_ident.clone()),
630                        variant.ident.clone().into(),
631                    ]
632                    .into_iter()
633                    .collect(),
634                }),
635                &variant.ident.to_string(),
636            )?;
637
638            statemachine.merge(submachine.compile());
639        }
640
641        Ok(statemachine)
642    }
643
644    /// Build the serialisation statemachine for the dynamic enum.
645    fn make_as_item_iter_statemachine(
646        &self,
647        target_ty_ident: &Ident,
648        state_ty_ident: &Ident,
649        item_iter_ty_lifetime: &Lifetime,
650    ) -> Result<AsItemsStateMachine> {
651        let mut statemachine = AsItemsStateMachine::new();
652        for variant in self.variants.iter() {
653            let submachine = variant.inner.make_as_item_iter_statemachine(
654                &ParentRef::Named(Path {
655                    leading_colon: None,
656                    segments: [
657                        PathSegment::from(target_ty_ident.clone()),
658                        variant.ident.clone().into(),
659                    ]
660                    .into_iter()
661                    .collect(),
662                }),
663                state_ty_ident,
664                &variant.ident.to_string(),
665                item_iter_ty_lifetime,
666            )?;
667
668            statemachine.merge(submachine.compile());
669        }
670
671        Ok(statemachine)
672    }
673}
674
675/// The definition of an enum.
676enum EnumInner {
677    /// The enum switches based on the XML name of the element, with the XML
678    /// namespace fixed.
679    NameSwitched(NameSwitchedEnum),
680
681    /// The enum switches based on the value of an attribute of the XML
682    /// element.
683    AttributeSwitched(AttributeSwitchedEnum),
684
685    /// The enum consists of variants with entirely unrelated XML structures.
686    Dynamic(DynamicEnum),
687}
688
689impl EnumInner {
690    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
691        meta: XmlCompoundMeta,
692        variant_iter: I,
693    ) -> Result<Self> {
694        // We destructure here so that we get informed when new fields are
695        // added and can handle them, either by processing them or raising
696        // an error if they are present.
697        let XmlCompoundMeta {
698            span,
699            qname: QNameRef { namespace, name },
700            exhaustive,
701            debug,
702            builder,
703            iterator,
704            on_unknown_attribute,
705            on_unknown_child,
706            transparent,
707            discard,
708            deserialize_callback,
709            attribute,
710            value,
711        } = meta;
712
713        // These must've been cleared by the caller. Because these being set
714        // is a programming error (in xso-proc) and not a usage error, we
715        // assert here instead of using reject_key!.
716        assert!(builder.is_none());
717        assert!(iterator.is_none());
718        assert!(!debug.is_set());
719        assert!(deserialize_callback.is_none());
720
721        reject_key!(transparent flag not on "enums" only on "structs");
722        reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs");
723        reject_key!(on_unknown_child not on "enums" only on "enum variants and structs");
724        reject_key!(discard vec not on "enums" only on "enum variants and structs");
725        reject_key!(value not on "enums" only on "attribute-switched enum variants");
726
727        if let Some(attribute) = attribute {
728            let Some(attr_name) = attribute.qname.name else {
729                return Err(Error::new(
730                    attribute.span,
731                    "missing `name` for `attribute` key",
732                ));
733            };
734
735            let attr_namespace = attribute.qname.namespace;
736
737            let Some(elem_namespace) = namespace else {
738                let mut error =
739                    Error::new(span, "missing `namespace` key on attribute-switched enum");
740                error.combine(Error::new(
741                    attribute.span,
742                    "enum is attribute-switched because of the `attribute` key here",
743                ));
744                return Err(error);
745            };
746
747            let Some(elem_name) = name else {
748                let mut error = Error::new(span, "missing `name` key on attribute-switched enum");
749                error.combine(Error::new(
750                    attribute.span,
751                    "enum is attribute-switched because of the `attribute` key here",
752                ));
753                return Err(error);
754            };
755
756            if !exhaustive.is_set() {
757                return Err(Error::new(
758                    span,
759                    "attribute-switched enums must be marked as `exhaustive`. non-exhaustive attribute-switched enums are not supported."
760                ));
761            }
762
763            Ok(Self::AttributeSwitched(AttributeSwitchedEnum::new(
764                elem_namespace,
765                elem_name,
766                attr_namespace,
767                attr_name,
768                variant_iter,
769            )?))
770        } else if let Some(namespace) = namespace {
771            reject_key!(name not on "name-switched enums" only on "their variants");
772            Ok(Self::NameSwitched(NameSwitchedEnum::new(
773                namespace,
774                exhaustive,
775                variant_iter,
776            )?))
777        } else {
778            reject_key!(name not on "dynamic enums" only on "their variants");
779            reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums");
780            Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?))
781        }
782    }
783
784    /// Build the deserialisation statemachine for the enum.
785    fn make_from_events_statemachine(
786        &self,
787        target_ty_ident: &Ident,
788        state_ty_ident: &Ident,
789    ) -> Result<FromEventsStateMachine> {
790        match self {
791            Self::NameSwitched(ref inner) => {
792                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
793            }
794            Self::AttributeSwitched(ref inner) => {
795                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
796            }
797            Self::Dynamic(ref inner) => {
798                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
799            }
800        }
801    }
802
803    /// Build the serialisation statemachine for the enum.
804    fn make_as_item_iter_statemachine(
805        &self,
806        target_ty_ident: &Ident,
807        state_ty_ident: &Ident,
808        item_iter_ty_lifetime: &Lifetime,
809    ) -> Result<AsItemsStateMachine> {
810        match self {
811            Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine(
812                target_ty_ident,
813                state_ty_ident,
814                item_iter_ty_lifetime,
815            ),
816            Self::AttributeSwitched(ref inner) => inner.make_as_item_iter_statemachine(
817                target_ty_ident,
818                state_ty_ident,
819                item_iter_ty_lifetime,
820            ),
821            Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine(
822                target_ty_ident,
823                state_ty_ident,
824                item_iter_ty_lifetime,
825            ),
826        }
827    }
828}
829
830/// Definition of an enum and how to parse it.
831pub(crate) struct EnumDef {
832    /// Implementation of the enum itself
833    inner: EnumInner,
834
835    /// Name of the target type.
836    target_ty_ident: Ident,
837
838    /// Name of the builder type.
839    builder_ty_ident: Ident,
840
841    /// Name of the iterator type.
842    item_iter_ty_ident: Ident,
843
844    /// Flag whether debug mode is enabled.
845    debug: bool,
846
847    /// Optional validator function to call.
848    deserialize_callback: Option<Path>,
849}
850
851impl EnumDef {
852    /// Create a new enum from its name, meta, and variants.
853    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
854        ident: &Ident,
855        mut meta: XmlCompoundMeta,
856        variant_iter: I,
857    ) -> Result<Self> {
858        let builder_ty_ident = match meta.builder.take() {
859            Some(v) => v,
860            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
861        };
862
863        let item_iter_ty_ident = match meta.iterator.take() {
864            Some(v) => v,
865            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
866        };
867
868        let debug = meta.debug.take().is_set();
869        let deserialize_callback = meta.deserialize_callback.take();
870
871        Ok(Self {
872            inner: EnumInner::new(meta, variant_iter)?,
873            target_ty_ident: ident.clone(),
874            builder_ty_ident,
875            item_iter_ty_ident,
876            debug,
877            deserialize_callback,
878        })
879    }
880}
881
882impl ItemDef for EnumDef {
883    fn make_from_events_builder(
884        &self,
885        vis: &Visibility,
886        name_ident: &Ident,
887        attrs_ident: &Ident,
888    ) -> Result<FromXmlParts> {
889        let target_ty_ident = &self.target_ty_ident;
890        let builder_ty_ident = &self.builder_ty_ident;
891        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
892
893        let defs = self
894            .inner
895            .make_from_events_statemachine(target_ty_ident, &state_ty_ident)?
896            .render(
897                vis,
898                builder_ty_ident,
899                &state_ty_ident,
900                &TypePath {
901                    qself: None,
902                    path: target_ty_ident.clone().into(),
903                }
904                .into(),
905                self.deserialize_callback.as_ref(),
906            )?;
907
908        Ok(FromXmlParts {
909            defs,
910            from_events_body: quote! {
911                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
912            },
913            builder_ty_ident: builder_ty_ident.clone(),
914        })
915    }
916
917    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
918        let target_ty_ident = &self.target_ty_ident;
919        let item_iter_ty_ident = &self.item_iter_ty_ident;
920        let item_iter_ty_lifetime = Lifetime {
921            apostrophe: Span::call_site(),
922            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
923        };
924        let item_iter_ty = Type::Path(TypePath {
925            qself: None,
926            path: Path {
927                leading_colon: None,
928                segments: [PathSegment {
929                    ident: item_iter_ty_ident.clone(),
930                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
931                        colon2_token: None,
932                        lt_token: token::Lt {
933                            spans: [Span::call_site()],
934                        },
935                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
936                            .into_iter()
937                            .collect(),
938                        gt_token: token::Gt {
939                            spans: [Span::call_site()],
940                        },
941                    }),
942                }]
943                .into_iter()
944                .collect(),
945            },
946        });
947        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
948
949        let defs = self
950            .inner
951            .make_as_item_iter_statemachine(
952                target_ty_ident,
953                &state_ty_ident,
954                &item_iter_ty_lifetime,
955            )?
956            .render(
957                vis,
958                &ref_ty(
959                    ty_from_ident(target_ty_ident.clone()).into(),
960                    item_iter_ty_lifetime.clone(),
961                ),
962                &state_ty_ident,
963                &item_iter_ty_lifetime,
964                &item_iter_ty,
965            )?;
966
967        Ok(AsXmlParts {
968            defs,
969            as_xml_iter_body: quote! {
970                #item_iter_ty_ident::new(self)
971            },
972            item_iter_ty,
973            item_iter_ty_lifetime,
974        })
975    }
976
977    fn debug(&self) -> bool {
978        self.debug
979    }
980}