Skip to main content

xsd_parser/pipeline/optimizer/
merge_enum_unions.rs

1use crate::models::{
2    meta::{EnumerationMeta, EnumerationMetaVariant, MetaTypeVariant, UnionMeta, UnionMetaType},
3    TypeIdent,
4};
5use crate::traits::VecHelper;
6
7use super::{Error, Optimizer};
8
9impl Optimizer {
10    /// This will flatten the union identified by `ident` to one single union.
11    ///
12    /// This will merge the nested union and enum types of the union identified
13    /// by `ident` to one single enum type.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error if the passed `ident` could not be found,
18    /// or is not an union.
19    ///
20    /// # Examples
21    ///
22    /// Consider the following XML schema.
23    /// ```xml
24    #[doc = include_str!("../../../tests/optimizer/union_flatten.xsd")]
25    /// ```
26    ///
27    /// Without this optimization this will result in the following code:
28    /// ```rust
29    #[doc = include_str!("../../../tests/optimizer/expected0/merge_enum_unions.rs")]
30    /// ```
31    ///
32    /// With this optimization the following code is generated:
33    /// ```rust
34    #[doc = include_str!("../../../tests/optimizer/expected1/merge_enum_unions.rs")]
35    /// ```
36    pub fn merge_enum_union(mut self, ident: TypeIdent) -> Result<Self, Error> {
37        tracing::debug!("merge_enum_union(ident={ident:?})");
38
39        let Some(variant) = self.types.get_variant(&ident) else {
40            return Err(Error::UnknownType(ident));
41        };
42
43        let MetaTypeVariant::Union(_) = variant else {
44            return Err(Error::ExpectedUnion(ident));
45        };
46
47        let mut next = None;
48
49        self.merge_enum_union_impl(&ident, None, &mut next);
50
51        if let Some(next) = next {
52            let ty = self.types.items.get_mut(&ident).unwrap();
53            ty.variant = next;
54        }
55
56        Ok(self)
57    }
58
59    /// This will flatten all enumeration and union types.
60    ///
61    /// For details see [`merge_enum_union`](Self::merge_enum_union).
62    pub fn merge_enum_unions(mut self) -> Self {
63        tracing::debug!("merge_enum_unions");
64
65        let idents = self
66            .types
67            .items
68            .iter()
69            .filter_map(|(ident, type_)| {
70                if matches!(&type_.variant, MetaTypeVariant::Union(_)) {
71                    Some(ident)
72                } else {
73                    None
74                }
75            })
76            .cloned()
77            .collect::<Vec<_>>();
78
79        for ident in idents {
80            self = self.merge_enum_union(ident).unwrap();
81        }
82
83        self
84    }
85
86    fn merge_enum_union_impl(
87        &self,
88        ident: &TypeIdent,
89        display_name: Option<&str>,
90        next: &mut Option<MetaTypeVariant>,
91    ) {
92        let Some(type_) = self.types.get_variant(ident) else {
93            return;
94        };
95
96        match type_ {
97            MetaTypeVariant::Union(x) => {
98                for t in &*x.types {
99                    self.merge_enum_union_impl(&t.type_, t.display_name.as_deref(), next);
100                }
101            }
102            MetaTypeVariant::Enumeration(x) => {
103                *next = match next.take() {
104                    None => Some(MetaTypeVariant::Enumeration(EnumerationMeta::default())),
105                    Some(MetaTypeVariant::Enumeration(ei)) => {
106                        Some(MetaTypeVariant::Enumeration(ei))
107                    }
108                    Some(MetaTypeVariant::Union(ui)) => {
109                        let mut ei = EnumerationMeta::default();
110
111                        for t in ui.types.0 {
112                            let var =
113                                ei.variants
114                                    .find_or_insert(t.type_.to_property_ident(), |ident| {
115                                        EnumerationMetaVariant::new(ident)
116                                            .with_type(Some(t.type_.clone()))
117                                    });
118                            var.display_name = t.display_name;
119                        }
120
121                        Some(MetaTypeVariant::Enumeration(ei))
122                    }
123                    _ => crate::unreachable!(),
124                };
125
126                let Some(MetaTypeVariant::Enumeration(ei)) = next else {
127                    crate::unreachable!();
128                };
129
130                for var in &*x.variants {
131                    let new_var = ei.variants.find_or_insert(var.ident.clone(), |ident| {
132                        EnumerationMetaVariant::new(ident).with_type(var.type_.clone())
133                    });
134                    new_var.display_name.clone_from(&var.display_name);
135                }
136            }
137            MetaTypeVariant::Reference(x) if x.is_simple() => {
138                self.merge_enum_union_impl(&x.type_, display_name, next);
139            }
140            _ => {
141                if next.is_none() {
142                    *next = Some(MetaTypeVariant::Union(UnionMeta::default()));
143                }
144
145                match next {
146                    Some(MetaTypeVariant::Union(ui)) => {
147                        let mut ti = UnionMetaType::new(ident.clone());
148                        ti.display_name = display_name.map(ToOwned::to_owned);
149
150                        ui.types.push(ti);
151                    }
152                    Some(MetaTypeVariant::Enumeration(ei)) => {
153                        let var = ei.variants.find_or_insert(ident.to_property_ident(), |x| {
154                            EnumerationMetaVariant::new(x).with_type(Some(ident.clone()))
155                        });
156                        var.display_name = display_name.map(ToOwned::to_owned);
157                    }
158                    _ => crate::unreachable!(),
159                }
160            }
161        }
162    }
163}