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