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}