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}