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}