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, TokenStream};
10use quote::quote;
11use syn::*;
12
13use crate::compound::Compound;
14use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
15
16/// Parts necessary to construct a `::xso::FromXml` implementation.
17pub(crate) struct FromXmlParts {
18 /// Additional items necessary for the implementation.
19 pub(crate) defs: TokenStream,
20
21 /// The body of the `::xso::FromXml::from_xml` function.
22 pub(crate) from_events_body: TokenStream,
23
24 /// The name of the type which is the `::xso::FromXml::Builder`.
25 pub(crate) builder_ty_ident: Ident,
26}
27
28/// Parts necessary to construct a `::xso::AsXml` implementation.
29pub(crate) struct AsXmlParts {
30 /// Additional items necessary for the implementation.
31 pub(crate) defs: TokenStream,
32
33 /// The body of the `::xso::AsXml::as_xml_iter` function.
34 pub(crate) as_xml_iter_body: TokenStream,
35
36 /// The type which is the `::xso::AsXml::ItemIter`.
37 pub(crate) item_iter_ty: Type,
38
39 /// The lifetime name used in `item_iter_ty`.
40 pub(crate) item_iter_ty_lifetime: Lifetime,
41}
42
43/// Definition of a struct and how to parse it.
44pub(crate) struct StructDef {
45 /// The XML namespace of the element to map the struct to.
46 namespace: NamespaceRef,
47
48 /// The XML name of the element to map the struct to.
49 name: NameRef,
50
51 /// The field(s) of this struct.
52 inner: Compound,
53
54 /// Name of the target type.
55 target_ty_ident: Ident,
56
57 /// Name of the builder type.
58 builder_ty_ident: Ident,
59
60 /// Name of the iterator type.
61 item_iter_ty_ident: Ident,
62
63 /// Flag whether debug mode is enabled.
64 debug: bool,
65}
66
67impl StructDef {
68 /// Create a new struct from its name, meta, and fields.
69 pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
70 let Some(namespace) = meta.namespace else {
71 return Err(Error::new(meta.span, "`namespace` is required on structs"));
72 };
73
74 let Some(name) = meta.name else {
75 return Err(Error::new(meta.span, "`name` is required on structs"));
76 };
77
78 let builder_ty_ident = match meta.builder {
79 Some(v) => v,
80 None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
81 };
82
83 let item_iter_ty_ident = match meta.iterator {
84 Some(v) => v,
85 None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
86 };
87
88 Ok(Self {
89 namespace,
90 name,
91 inner: Compound::from_fields(fields)?,
92 target_ty_ident: ident.clone(),
93 builder_ty_ident,
94 item_iter_ty_ident,
95 debug: meta.debug.is_set(),
96 })
97 }
98
99 pub(crate) fn make_from_events_builder(
100 &self,
101 vis: &Visibility,
102 name_ident: &Ident,
103 attrs_ident: &Ident,
104 ) -> Result<FromXmlParts> {
105 let xml_namespace = &self.namespace;
106 let xml_name = &self.name;
107
108 let target_ty_ident = &self.target_ty_ident;
109 let builder_ty_ident = &self.builder_ty_ident;
110 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
111
112 let defs = self
113 .inner
114 .make_from_events_statemachine(
115 &state_ty_ident,
116 &Path::from(target_ty_ident.clone()).into(),
117 "Struct",
118 )?
119 .with_augmented_init(|init| {
120 quote! {
121 if name.0 != #xml_namespace || name.1 != #xml_name {
122 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
123 name,
124 attrs,
125 })
126 } else {
127 #init
128 }
129 }
130 })
131 .compile()
132 .render(
133 vis,
134 builder_ty_ident,
135 &state_ty_ident,
136 &TypePath {
137 qself: None,
138 path: target_ty_ident.clone().into(),
139 }
140 .into(),
141 )?;
142
143 Ok(FromXmlParts {
144 defs,
145 from_events_body: quote! {
146 #builder_ty_ident::new(#name_ident, #attrs_ident)
147 },
148 builder_ty_ident: builder_ty_ident.clone(),
149 })
150 }
151
152 pub(crate) fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
153 let xml_namespace = &self.namespace;
154 let xml_name = &self.name;
155
156 let target_ty_ident = &self.target_ty_ident;
157 let item_iter_ty_ident = &self.item_iter_ty_ident;
158 let item_iter_ty_lifetime = Lifetime {
159 apostrophe: Span::call_site(),
160 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
161 };
162 let item_iter_ty = Type::Path(TypePath {
163 qself: None,
164 path: Path {
165 leading_colon: None,
166 segments: [PathSegment {
167 ident: item_iter_ty_ident.clone(),
168 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
169 colon2_token: None,
170 lt_token: token::Lt {
171 spans: [Span::call_site()],
172 },
173 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
174 .into_iter()
175 .collect(),
176 gt_token: token::Gt {
177 spans: [Span::call_site()],
178 },
179 }),
180 }]
181 .into_iter()
182 .collect(),
183 },
184 });
185 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
186
187 let defs = self
188 .inner
189 .make_as_item_iter_statemachine(
190 &target_ty_ident.clone().into(),
191 "Struct",
192 &item_iter_ty_lifetime,
193 )?
194 .with_augmented_init(|init| {
195 quote! {
196 let name = (
197 ::xso::exports::rxml::Namespace::from(#xml_namespace),
198 ::std::borrow::Cow::Borrowed(#xml_name),
199 );
200 #init
201 }
202 })
203 .compile()
204 .render(
205 vis,
206 &TypePath {
207 qself: None,
208 path: target_ty_ident.clone().into(),
209 }
210 .into(),
211 &state_ty_ident,
212 &item_iter_ty_lifetime,
213 &item_iter_ty,
214 )?;
215
216 Ok(AsXmlParts {
217 defs,
218 as_xml_iter_body: quote! {
219 #item_iter_ty_ident::new(self)
220 },
221 item_iter_ty,
222 item_iter_ty_lifetime,
223 })
224 }
225
226 pub(crate) fn debug(&self) -> bool {
227 self.debug
228 }
229}