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::TokenStream;
 10use quote::quote;
 11use syn::*;
 12
 13use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
 14
 15/// Parts necessary to construct a `::xso::FromXml` implementation.
 16pub(crate) struct FromXmlParts {
 17    /// Additional items necessary for the implementation.
 18    pub(crate) defs: TokenStream,
 19
 20    /// The body of the `::xso::FromXml::from_xml` function.
 21    pub(crate) from_events_body: TokenStream,
 22
 23    /// The name of the type which is the `::xso::FromXml::Builder`.
 24    pub(crate) builder_ty_ident: Ident,
 25}
 26
 27/// Parts necessary to construct a `::xso::IntoXml` implementation.
 28pub(crate) struct IntoXmlParts {
 29    /// Additional items necessary for the implementation.
 30    pub(crate) defs: TokenStream,
 31
 32    /// The body of the `::xso::IntoXml::into_event_iter` function.
 33    pub(crate) into_event_iter_body: TokenStream,
 34
 35    /// The name of the type which is the `::xso::IntoXml::EventIter`.
 36    pub(crate) event_iter_ty_ident: Ident,
 37}
 38
 39/// Definition of a struct and how to parse it.
 40pub(crate) struct StructDef {
 41    /// The XML namespace of the element to map the struct to.
 42    namespace: NamespaceRef,
 43
 44    /// The XML name of the element to map the struct to.
 45    name: NameRef,
 46
 47    /// Name of the target type.
 48    target_ty_ident: Ident,
 49
 50    /// Name of the builder type.
 51    builder_ty_ident: Ident,
 52
 53    /// Name of the iterator type.
 54    event_iter_ty_ident: Ident,
 55}
 56
 57impl StructDef {
 58    /// Create a new struct from its name, meta, and fields.
 59    pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
 60        let Some(namespace) = meta.namespace else {
 61            return Err(Error::new(meta.span, "`namespace` is required on structs"));
 62        };
 63
 64        let Some(name) = meta.name else {
 65            return Err(Error::new(meta.span, "`name` is required on structs"));
 66        };
 67
 68        match fields {
 69            Fields::Unit => (),
 70            other => {
 71                return Err(Error::new_spanned(
 72                    other,
 73                    "cannot derive on non-unit struct (yet!)",
 74                ))
 75            }
 76        }
 77
 78        Ok(Self {
 79            namespace,
 80            name,
 81            target_ty_ident: ident.clone(),
 82            builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
 83            event_iter_ty_ident: quote::format_ident!("{}IntoXmlIterator", ident),
 84        })
 85    }
 86
 87    pub(crate) 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_name = quote::format_ident!("{}State", builder_ty_ident);
 99
100        let unknown_attr_err = format!(
101            "Unknown attribute in {} element.",
102            xml_name.repr_to_string()
103        );
104        let unknown_child_err = format!("Unknown child in {} element.", xml_name.repr_to_string());
105
106        let docstr = format!("Build a [`{}`] from XML events", target_ty_ident);
107
108        Ok(FromXmlParts {
109            defs: quote! {
110                enum #state_ty_name {
111                    Default,
112                }
113
114                #[doc = #docstr]
115                #vis struct #builder_ty_ident(::core::option::Option<#state_ty_name>);
116
117                impl ::xso::FromEventsBuilder for #builder_ty_ident {
118                    type Output = #target_ty_ident;
119
120                    fn feed(
121                        &mut self,
122                        ev: ::xso::exports::rxml::Event
123                    ) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
124                        match self.0 {
125                            ::core::option::Option::None => panic!("feed() called after it returned a non-None value"),
126                            ::core::option::Option::Some(#state_ty_name::Default) => match ev {
127                                ::xso::exports::rxml::Event::StartElement(..) => {
128                                    ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
129                                }
130                                ::xso::exports::rxml::Event::EndElement(..) => {
131                                    self.0 = ::core::option::Option::None;
132                                    ::core::result::Result::Ok(::core::option::Option::Some(#target_ty_ident))
133                                }
134                                ::xso::exports::rxml::Event::Text(..) => {
135                                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
136                                }
137                                // we ignore these: a correct parser only generates
138                                // them at document start, and there we want to indeed
139                                // not worry about them being in front of the first
140                                // element.
141                                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::option::Option::None)
142                            }
143                        }
144                    }
145                }
146            },
147            from_events_body: quote! {
148                if #name_ident.0 != #xml_namespace || #name_ident.1 != #xml_name {
149                    return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
150                        name: #name_ident,
151                        attrs: #attrs_ident,
152                    });
153                }
154                if attrs.len() > 0 {
155                    return ::core::result::Result::Err(::xso::error::Error::Other(
156                        #unknown_attr_err,
157                    ).into());
158                }
159                ::core::result::Result::Ok(#builder_ty_ident(::core::option::Option::Some(#state_ty_name::Default)))
160            },
161            builder_ty_ident: builder_ty_ident.clone(),
162        })
163    }
164
165    pub(crate) fn make_into_event_iter(&self, vis: &Visibility) -> Result<IntoXmlParts> {
166        let xml_namespace = &self.namespace;
167        let xml_name = &self.name;
168
169        let target_ty_ident = &self.target_ty_ident;
170        let event_iter_ty_ident = &self.event_iter_ty_ident;
171        let state_ty_name = quote::format_ident!("{}State", event_iter_ty_ident);
172
173        let docstr = format!("Decompose a [`{}`] into XML events", target_ty_ident);
174
175        Ok(IntoXmlParts {
176            defs: quote! {
177                enum #state_ty_name {
178                    Header,
179                    Footer,
180                }
181
182                #[doc = #docstr]
183                #vis struct #event_iter_ty_ident(::core::option::Option<#state_ty_name>);
184
185                impl ::std::iter::Iterator for #event_iter_ty_ident {
186                    type Item = ::core::result::Result<::xso::exports::rxml::Event, ::xso::error::Error>;
187
188                    fn next(&mut self) -> ::core::option::Option<Self::Item> {
189                        match self.0 {
190                            ::core::option::Option::Some(#state_ty_name::Header) => {
191                                self.0 = ::core::option::Option::Some(#state_ty_name::Footer);
192                                ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::StartElement(
193                                    ::xso::exports::rxml::parser::EventMetrics::zero(),
194                                    (
195                                        ::xso::exports::rxml::Namespace::from_str(#xml_namespace),
196                                        #xml_name.to_owned(),
197                                    ),
198                                    ::xso::exports::rxml::AttrMap::new(),
199                                )))
200                            }
201                            ::core::option::Option::Some(#state_ty_name::Footer) => {
202                                self.0 = ::core::option::Option::None;
203                                ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::EndElement(
204                                    ::xso::exports::rxml::parser::EventMetrics::zero(),
205                                )))
206                            }
207                            ::core::option::Option::None => ::core::option::Option::None,
208                        }
209                    }
210                }
211            },
212            into_event_iter_body: quote! {
213                ::core::result::Result::Ok(#event_iter_ty_ident(::core::option::Option::Some(#state_ty_name::Header)))
214            },
215            event_iter_ty_ident: event_iter_ty_ident.clone(),
216        })
217    }
218}