xsd_parser/pipeline/optimizer/
flatten_unions.rs

1use crate::models::{
2    meta::{MetaTypeVariant, UnionMeta, UnionMetaType},
3    Ident,
4};
5
6use super::{Error, Optimizer};
7
8struct FlattenUnionInfo {
9    count: usize,
10    meta: UnionMeta,
11}
12
13impl Optimizer {
14    /// This will flatten the union identified by `ident` to one single union.
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if the passed `ident` could not be found,
19    /// or is not an union.
20    ///
21    /// # Examples
22    ///
23    /// Consider the following XML schema.
24    /// ```xml
25    #[doc = include_str!("../../../tests/optimizer/union_flatten.xsd")]
26    /// ```
27    ///
28    /// Without this optimization this will result in the following code:
29    /// ```rust
30    #[doc = include_str!("../../../tests/optimizer/expected0/flatten_unions.rs")]
31    /// ```
32    ///
33    /// With this optimization the following code is generated:
34    /// ```rust
35    #[doc = include_str!("../../../tests/optimizer/expected1/flatten_unions.rs")]
36    /// ```
37    pub fn flatten_union(mut self, ident: Ident) -> Result<Self, Error> {
38        tracing::debug!("flatten_union(ident={ident:?})");
39
40        let Some(ty) = self.types.items.get(&ident) else {
41            return Err(Error::UnknownType(ident));
42        };
43
44        let MetaTypeVariant::Union(ui) = &ty.variant else {
45            return Err(Error::ExpectedUnion(ident));
46        };
47
48        let mut info = FlattenUnionInfo {
49            count: 0,
50            meta: UnionMeta::default(),
51        };
52
53        self.flatten_union_impl(&ident, None, &mut info);
54
55        if info.count > 1 {
56            info.meta.base = ui.base.clone();
57
58            let ty = self.types.items.get_mut(&ident).unwrap();
59            ty.variant = MetaTypeVariant::Union(info.meta);
60        }
61
62        Ok(self)
63    }
64
65    /// This will flatten all union types.
66    ///
67    /// For details see [`flatten_union`](Self::flatten_union).
68    pub fn flatten_unions(mut self) -> Self {
69        tracing::debug!("flatten_unions");
70
71        let idents = self
72            .types
73            .items
74            .iter()
75            .filter_map(|(ident, type_)| {
76                if matches!(&type_.variant, MetaTypeVariant::Union(_)) {
77                    Some(ident)
78                } else {
79                    None
80                }
81            })
82            .cloned()
83            .collect::<Vec<_>>();
84
85        for ident in idents {
86            self = self.flatten_union(ident).unwrap();
87        }
88
89        self
90    }
91
92    fn flatten_union_impl(
93        &self,
94        ident: &Ident,
95        display_name: Option<&str>,
96        next: &mut FlattenUnionInfo,
97    ) {
98        let Some(type_) = self.types.items.get(ident) else {
99            return;
100        };
101
102        match &type_.variant {
103            MetaTypeVariant::Union(x) => {
104                next.count += 1;
105                for t in &*x.types {
106                    self.flatten_union_impl(&t.type_, t.display_name.as_deref(), next);
107                }
108            }
109            MetaTypeVariant::Reference(x) if x.is_single() => {
110                self.flatten_union_impl(&x.type_, display_name, next);
111            }
112            _ => {
113                let mut ui = UnionMetaType::new(ident.clone());
114                ui.display_name = display_name.map(ToOwned::to_owned);
115
116                next.meta.types.push(ui);
117            }
118        }
119    }
120}