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}