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::{NameRef, NamespaceRef, 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            namespace,
 50            name,
 51            debug,
 52            builder,
 53            iterator,
 54        } = meta;
 55
 56        let Some(namespace) = namespace else {
 57            return Err(Error::new(meta_span, "`namespace` is required on structs"));
 58        };
 59
 60        let Some(name) = name else {
 61            return Err(Error::new(meta_span, "`name` is required on structs"));
 62        };
 63
 64        let builder_ty_ident = match builder {
 65            Some(v) => v,
 66            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
 67        };
 68
 69        let item_iter_ty_ident = match iterator {
 70            Some(v) => v,
 71            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
 72        };
 73
 74        Ok(Self {
 75            namespace,
 76            name,
 77            inner: Compound::from_fields(fields)?,
 78            target_ty_ident: ident.clone(),
 79            builder_ty_ident,
 80            item_iter_ty_ident,
 81            debug: debug.is_set(),
 82        })
 83    }
 84}
 85
 86impl ItemDef for StructDef {
 87    fn make_from_events_builder(
 88        &self,
 89        vis: &Visibility,
 90        name_ident: &Ident,
 91        attrs_ident: &Ident,
 92    ) -> Result<FromXmlParts> {
 93        let xml_namespace = &self.namespace;
 94        let xml_name = &self.name;
 95
 96        let target_ty_ident = &self.target_ty_ident;
 97        let builder_ty_ident = &self.builder_ty_ident;
 98        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
 99
100        let defs = self
101            .inner
102            .make_from_events_statemachine(
103                &state_ty_ident,
104                &Path::from(target_ty_ident.clone()).into(),
105                "Struct",
106            )?
107            .with_augmented_init(|init| {
108                quote! {
109                    if name.0 != #xml_namespace || name.1 != #xml_name {
110                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
111                            name,
112                            attrs,
113                        })
114                    } else {
115                        #init
116                    }
117                }
118            })
119            .compile()
120            .render(
121                vis,
122                builder_ty_ident,
123                &state_ty_ident,
124                &TypePath {
125                    qself: None,
126                    path: target_ty_ident.clone().into(),
127                }
128                .into(),
129            )?;
130
131        Ok(FromXmlParts {
132            defs,
133            from_events_body: quote! {
134                #builder_ty_ident::new(#name_ident, #attrs_ident)
135            },
136            builder_ty_ident: builder_ty_ident.clone(),
137        })
138    }
139
140    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
141        let xml_namespace = &self.namespace;
142        let xml_name = &self.name;
143
144        let target_ty_ident = &self.target_ty_ident;
145        let item_iter_ty_ident = &self.item_iter_ty_ident;
146        let item_iter_ty_lifetime = Lifetime {
147            apostrophe: Span::call_site(),
148            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
149        };
150        let item_iter_ty = Type::Path(TypePath {
151            qself: None,
152            path: Path {
153                leading_colon: None,
154                segments: [PathSegment {
155                    ident: item_iter_ty_ident.clone(),
156                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
157                        colon2_token: None,
158                        lt_token: token::Lt {
159                            spans: [Span::call_site()],
160                        },
161                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
162                            .into_iter()
163                            .collect(),
164                        gt_token: token::Gt {
165                            spans: [Span::call_site()],
166                        },
167                    }),
168                }]
169                .into_iter()
170                .collect(),
171            },
172        });
173        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
174
175        let defs = self
176            .inner
177            .make_as_item_iter_statemachine(
178                &Path::from(target_ty_ident.clone()).into(),
179                "Struct",
180                &item_iter_ty_lifetime,
181            )?
182            .with_augmented_init(|init| {
183                quote! {
184                    let name = (
185                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
186                        ::std::borrow::Cow::Borrowed(#xml_name),
187                    );
188                    #init
189                }
190            })
191            .compile()
192            .render(
193                vis,
194                &TypePath {
195                    qself: None,
196                    path: target_ty_ident.clone().into(),
197                }
198                .into(),
199                &state_ty_ident,
200                &item_iter_ty_lifetime,
201                &item_iter_ty,
202            )?;
203
204        Ok(AsXmlParts {
205            defs,
206            as_xml_iter_body: quote! {
207                #item_iter_ty_ident::new(self)
208            },
209            item_iter_ty,
210            item_iter_ty_lifetime,
211        })
212    }
213
214    fn debug(&self) -> bool {
215        self.debug
216    }
217}