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 let Some(namespace) = meta.namespace else {
45 return Err(Error::new(meta.span, "`namespace` is required on structs"));
46 };
47
48 let Some(name) = meta.name else {
49 return Err(Error::new(meta.span, "`name` is required on structs"));
50 };
51
52 let builder_ty_ident = match meta.builder {
53 Some(v) => v,
54 None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
55 };
56
57 let item_iter_ty_ident = match meta.iterator {
58 Some(v) => v,
59 None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
60 };
61
62 Ok(Self {
63 namespace,
64 name,
65 inner: Compound::from_fields(fields)?,
66 target_ty_ident: ident.clone(),
67 builder_ty_ident,
68 item_iter_ty_ident,
69 debug: meta.debug.is_set(),
70 })
71 }
72}
73
74impl ItemDef for StructDef {
75 fn make_from_events_builder(
76 &self,
77 vis: &Visibility,
78 name_ident: &Ident,
79 attrs_ident: &Ident,
80 ) -> Result<FromXmlParts> {
81 let xml_namespace = &self.namespace;
82 let xml_name = &self.name;
83
84 let target_ty_ident = &self.target_ty_ident;
85 let builder_ty_ident = &self.builder_ty_ident;
86 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
87
88 let defs = self
89 .inner
90 .make_from_events_statemachine(
91 &state_ty_ident,
92 &Path::from(target_ty_ident.clone()).into(),
93 "Struct",
94 )?
95 .with_augmented_init(|init| {
96 quote! {
97 if name.0 != #xml_namespace || name.1 != #xml_name {
98 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
99 name,
100 attrs,
101 })
102 } else {
103 #init
104 }
105 }
106 })
107 .compile()
108 .render(
109 vis,
110 builder_ty_ident,
111 &state_ty_ident,
112 &TypePath {
113 qself: None,
114 path: target_ty_ident.clone().into(),
115 }
116 .into(),
117 )?;
118
119 Ok(FromXmlParts {
120 defs,
121 from_events_body: quote! {
122 #builder_ty_ident::new(#name_ident, #attrs_ident)
123 },
124 builder_ty_ident: builder_ty_ident.clone(),
125 })
126 }
127
128 fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
129 let xml_namespace = &self.namespace;
130 let xml_name = &self.name;
131
132 let target_ty_ident = &self.target_ty_ident;
133 let item_iter_ty_ident = &self.item_iter_ty_ident;
134 let item_iter_ty_lifetime = Lifetime {
135 apostrophe: Span::call_site(),
136 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
137 };
138 let item_iter_ty = Type::Path(TypePath {
139 qself: None,
140 path: Path {
141 leading_colon: None,
142 segments: [PathSegment {
143 ident: item_iter_ty_ident.clone(),
144 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
145 colon2_token: None,
146 lt_token: token::Lt {
147 spans: [Span::call_site()],
148 },
149 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
150 .into_iter()
151 .collect(),
152 gt_token: token::Gt {
153 spans: [Span::call_site()],
154 },
155 }),
156 }]
157 .into_iter()
158 .collect(),
159 },
160 });
161 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
162
163 let defs = self
164 .inner
165 .make_as_item_iter_statemachine(
166 &target_ty_ident.clone().into(),
167 "Struct",
168 &item_iter_ty_lifetime,
169 )?
170 .with_augmented_init(|init| {
171 quote! {
172 let name = (
173 ::xso::exports::rxml::Namespace::from(#xml_namespace),
174 ::std::borrow::Cow::Borrowed(#xml_name),
175 );
176 #init
177 }
178 })
179 .compile()
180 .render(
181 vis,
182 &TypePath {
183 qself: None,
184 path: target_ty_ident.clone().into(),
185 }
186 .into(),
187 &state_ty_ident,
188 &item_iter_ty_lifetime,
189 &item_iter_ty,
190 )?;
191
192 Ok(AsXmlParts {
193 defs,
194 as_xml_iter_body: quote! {
195 #item_iter_ty_ident::new(self)
196 },
197 item_iter_ty,
198 item_iter_ty_lifetime,
199 })
200 }
201
202 fn debug(&self) -> bool {
203 self.debug
204 }
205}