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}