structs.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 structs
  8
  9use proc_macro2::Span;
 10use quote::quote;
 11use syn::*;
 12
 13use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
 14use crate::compound::Compound;
 15use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
 16use crate::types::{ref_ty, ty_from_ident};
 17
 18/// Definition of a struct and how to parse it.
 19pub(crate) struct StructDef {
 20    /// The XML namespace of the element to map the struct to.
 21    namespace: NamespaceRef,
 22
 23    /// The XML name of the element to map the struct to.
 24    name: NameRef,
 25
 26    /// The field(s) of this struct.
 27    inner: Compound,
 28
 29    /// Name of the target type.
 30    target_ty_ident: Ident,
 31
 32    /// Name of the builder type.
 33    builder_ty_ident: Ident,
 34
 35    /// Name of the iterator type.
 36    item_iter_ty_ident: Ident,
 37
 38    /// Flag whether debug mode is enabled.
 39    debug: bool,
 40}
 41
 42impl StructDef {
 43    /// Create a new struct from its name, meta, and fields.
 44    pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
 45        // We destructure here so that we get informed when new fields are
 46        // added and can handle them, either by processing them or raising
 47        // an error if they are present.
 48        let XmlCompoundMeta {
 49            span: meta_span,
 50            qname: QNameRef { namespace, name },
 51            exhaustive,
 52            debug,
 53            builder,
 54            iterator,
 55        } = meta;
 56
 57        reject_key!(exhaustive flag not on "structs" only on "enums");
 58
 59        let Some(namespace) = namespace else {
 60            return Err(Error::new(meta_span, "`namespace` is required on structs"));
 61        };
 62
 63        let Some(name) = name else {
 64            return Err(Error::new(meta_span, "`name` is required on structs"));
 65        };
 66
 67        let builder_ty_ident = match builder {
 68            Some(v) => v,
 69            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
 70        };
 71
 72        let item_iter_ty_ident = match iterator {
 73            Some(v) => v,
 74            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
 75        };
 76
 77        Ok(Self {
 78            inner: Compound::from_fields(fields, &namespace)?,
 79            namespace,
 80            name,
 81            target_ty_ident: ident.clone(),
 82            builder_ty_ident,
 83            item_iter_ty_ident,
 84            debug: debug.is_set(),
 85        })
 86    }
 87}
 88
 89impl ItemDef for StructDef {
 90    fn make_from_events_builder(
 91        &self,
 92        vis: &Visibility,
 93        name_ident: &Ident,
 94        attrs_ident: &Ident,
 95    ) -> Result<FromXmlParts> {
 96        let xml_namespace = &self.namespace;
 97        let xml_name = &self.name;
 98
 99        let target_ty_ident = &self.target_ty_ident;
100        let builder_ty_ident = &self.builder_ty_ident;
101        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
102
103        let defs = self
104            .inner
105            .make_from_events_statemachine(
106                &state_ty_ident,
107                &Path::from(target_ty_ident.clone()).into(),
108                "Struct",
109            )?
110            .with_augmented_init(|init| {
111                quote! {
112                    if name.0 != #xml_namespace || 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            .render(
124                vis,
125                builder_ty_ident,
126                &state_ty_ident,
127                &TypePath {
128                    qself: None,
129                    path: target_ty_ident.clone().into(),
130                }
131                .into(),
132            )?;
133
134        Ok(FromXmlParts {
135            defs,
136            from_events_body: quote! {
137                #builder_ty_ident::new(#name_ident, #attrs_ident)
138            },
139            builder_ty_ident: builder_ty_ident.clone(),
140        })
141    }
142
143    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
144        let xml_namespace = &self.namespace;
145        let xml_name = &self.name;
146
147        let target_ty_ident = &self.target_ty_ident;
148        let item_iter_ty_ident = &self.item_iter_ty_ident;
149        let item_iter_ty_lifetime = Lifetime {
150            apostrophe: Span::call_site(),
151            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
152        };
153        let item_iter_ty = Type::Path(TypePath {
154            qself: None,
155            path: Path {
156                leading_colon: None,
157                segments: [PathSegment {
158                    ident: item_iter_ty_ident.clone(),
159                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
160                        colon2_token: None,
161                        lt_token: token::Lt {
162                            spans: [Span::call_site()],
163                        },
164                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
165                            .into_iter()
166                            .collect(),
167                        gt_token: token::Gt {
168                            spans: [Span::call_site()],
169                        },
170                    }),
171                }]
172                .into_iter()
173                .collect(),
174            },
175        });
176        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
177
178        let defs = self
179            .inner
180            .make_as_item_iter_statemachine(
181                &Path::from(target_ty_ident.clone()).into(),
182                &state_ty_ident,
183                "Struct",
184                &item_iter_ty_lifetime,
185            )?
186            .with_augmented_init(|init| {
187                quote! {
188                    let name = (
189                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
190                        ::std::borrow::Cow::Borrowed(#xml_name),
191                    );
192                    #init
193                }
194            })
195            .compile()
196            .render(
197                vis,
198                &ref_ty(
199                    ty_from_ident(target_ty_ident.clone()).into(),
200                    item_iter_ty_lifetime.clone(),
201                ),
202                &state_ty_ident,
203                &item_iter_ty_lifetime,
204                &item_iter_ty,
205            )?;
206
207        Ok(AsXmlParts {
208            defs,
209            as_xml_iter_body: quote! {
210                #item_iter_ty_ident::new(self)
211            },
212            item_iter_ty,
213            item_iter_ty_lifetime,
214        })
215    }
216
217    fn debug(&self) -> bool {
218        self.debug
219    }
220}