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//! Compound (struct or enum variant) field types
8
9use proc_macro2::{Span, TokenStream};
10use syn::{spanned::Spanned, *};
11
12use rxml_validation::NcName;
13
14use crate::compound::Compound;
15use crate::error_message::ParentRef;
16use crate::meta::{AmountConstraint, Flag, NameRef, NamespaceRef, QNameRef, XmlFieldMeta};
17use crate::scope::{AsItemsScope, FromEventsScope};
18
19mod attribute;
20mod child;
21#[cfg(feature = "minidom")]
22mod element;
23mod text;
24
25use self::attribute::AttributeField;
26use self::child::{ChildField, ExtractDef};
27#[cfg(feature = "minidom")]
28use self::element::ElementField;
29use self::text::TextField;
30
31/// Code slices necessary for declaring and initializing a temporary variable
32/// for parsing purposes.
33pub(crate) struct FieldTempInit {
34 /// The type of the temporary variable.
35 pub(crate) ty: Type,
36
37 /// The initializer for the temporary variable.
38 pub(crate) init: TokenStream,
39}
40
41/// Configure how a nested field builder selects child elements.
42pub(crate) enum NestedMatcher {
43 /// Matches a specific child element fallabily.
44 Selective(
45 /// Expression which evaluates to `Result<T, FromEventsError>`,
46 /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
47 ///
48 /// `T` must be the type specified in the
49 /// [`FieldBuilderPart::Nested::builder`] field.
50 TokenStream,
51 ),
52
53 #[cfg_attr(not(feature = "minidom"), allow(dead_code))]
54 /// Matches any child element not matched by another matcher.
55 ///
56 /// Only a single field may use this variant, otherwise an error is
57 /// raised during execution of the proc macro.
58 Fallback(
59 /// Expression which evaluates to `T` (or `return`s an error),
60 /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
61 ///
62 /// `T` must be the type specified in the
63 /// [`FieldBuilderPart::Nested::builder`] field.
64 TokenStream,
65 ),
66}
67
68/// Describe how a struct or enum variant's member is parsed from XML data.
69///
70/// This struct is returned from [`FieldDef::make_builder_part`] and
71/// contains code snippets and instructions for
72/// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`]
73/// to parse the field's data from XML.
74pub(crate) enum FieldBuilderPart {
75 /// Parse a field from the item's element's start event.
76 Init {
77 /// Expression and type which extracts the field's data from the
78 /// element's start event.
79 value: FieldTempInit,
80 },
81
82 /// Parse a field from text events.
83 Text {
84 /// Expression and type which initializes a buffer to use during
85 /// parsing.
86 value: FieldTempInit,
87
88 /// Statement which takes text and accumulates it into the temporary
89 /// value declared via `value`.
90 collect: TokenStream,
91
92 /// Expression which evaluates to the field's type, consuming the
93 /// temporary value.
94 finalize: TokenStream,
95 },
96
97 /// Parse a field from child element events.
98 Nested {
99 /// Additional definition items which need to be inserted at module
100 /// level for the rest of the implementation to work.
101 extra_defs: TokenStream,
102
103 /// Expression and type which initializes a buffer to use during
104 /// parsing.
105 value: FieldTempInit,
106
107 /// Configure child matching behaviour for this field. See
108 /// [`NestedMatcher`] for options.
109 matcher: NestedMatcher,
110
111 /// Type implementing `xso::FromEventsBuilder` which parses the child
112 /// element.
113 ///
114 /// This type is returned by the expressions in
115 /// [`matcher`][`Self::Nested::matcher`].
116 builder: Type,
117
118 /// Expression which consumes the value stored in the identifier
119 /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`]
120 /// and somehow collects it into the field declared with
121 /// [`value`][`Self::Nested::value`].
122 collect: TokenStream,
123
124 /// Expression which consumes the data from the field declared with
125 /// [`value`][`Self::Nested::value`] and converts it into the field's
126 /// type.
127 finalize: TokenStream,
128 },
129}
130
131/// Describe how a struct or enum variant's member is converted to XML data.
132///
133/// This struct is returned from [`FieldDef::make_iterator_part`] and
134/// contains code snippets and instructions for
135/// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`]
136/// to convert the field's data into XML.
137pub(crate) enum FieldIteratorPart {
138 /// The field is emitted as part of StartElement.
139 Header {
140 /// An expression which consumes the field's value and returns a
141 /// `Item`.
142 generator: TokenStream,
143 },
144
145 /// The field is emitted as text item.
146 Text {
147 /// An expression which consumes the field's value and returns a
148 /// String, which is then emitted as text data.
149 generator: TokenStream,
150 },
151
152 /// The field is emitted as series of items which form a child element.
153 Content {
154 /// Additional definition items which need to be inserted at module
155 /// level for the rest of the implementation to work.
156 extra_defs: TokenStream,
157
158 /// Expression and type which initializes the nested iterator.
159 ///
160 /// Note that this is evaluated at construction time of the iterator.
161 /// Fields of this variant do not get access to their original data,
162 /// unless they carry it in the contents of this `value`.
163 value: FieldTempInit,
164
165 /// An expression which uses the value (mutably) and evaluates to
166 /// a Result<Option<Item>, Error>. Once the state returns None, the
167 /// processing will advance to the next state.
168 generator: TokenStream,
169 },
170}
171
172trait Field {
173 /// Construct the builder pieces for this field.
174 ///
175 /// `container_name` must be a reference to the compound's type, so that
176 /// it can be used for error messages.
177 ///
178 /// `member` and `ty` refer to the field itself.
179 fn make_builder_part(
180 &self,
181 scope: &FromEventsScope,
182 container_name: &ParentRef,
183 member: &Member,
184 ty: &Type,
185 ) -> Result<FieldBuilderPart>;
186
187 /// Construct the iterator pieces for this field.
188 ///
189 /// `bound_name` must be the name to which the field's value is bound in
190 /// the iterator code.
191 ///
192 /// `member` and `ty` refer to the field itself.
193 ///
194 /// `bound_name` is the name under which the field's value is accessible
195 /// in the various parts of the code.
196 fn make_iterator_part(
197 &self,
198 scope: &AsItemsScope,
199 container_name: &ParentRef,
200 bound_name: &Ident,
201 member: &Member,
202 ty: &Type,
203 ) -> Result<FieldIteratorPart>;
204
205 /// Return true if and only if this field captures text content.
206 fn captures_text(&self) -> bool {
207 false
208 }
209}
210
211fn default_name(span: Span, name: Option<NameRef>, field_ident: Option<&Ident>) -> Result<NameRef> {
212 match name {
213 Some(v) => Ok(v),
214 None => match field_ident {
215 None => Err(Error::new(
216 span,
217 "name must be explicitly specified with the `name` key on unnamed fields",
218 )),
219 Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
220 Ok(value) => Ok(NameRef::Literal {
221 span: field_ident.span(),
222 value,
223 }),
224 Err(e) => Err(Error::new(
225 field_ident.span(),
226 format!("invalid XML name: {}", e),
227 )),
228 },
229 },
230 }
231}
232
233/// Construct a new field implementation from the meta attributes.
234///
235/// `field_ident` is, for some field types, used to infer an XML name if
236/// it is not specified explicitly.
237///
238/// `field_ty` is needed for type inferrence on extracted fields.
239///
240/// `container_namespace` is used in some cases to insert a default
241/// namespace.
242fn new_field(
243 meta: XmlFieldMeta,
244 field_ident: Option<&Ident>,
245 field_ty: &Type,
246 container_namespace: &NamespaceRef,
247) -> Result<Box<dyn Field>> {
248 match meta {
249 XmlFieldMeta::Attribute {
250 span,
251 qname: QNameRef { namespace, name },
252 default_,
253 type_,
254 } => {
255 let xml_name = default_name(span, name, field_ident)?;
256
257 // This would've been taken via `XmlFieldMeta::take_type` if
258 // this field was within an extract where a `type_` is legal
259 // to have.
260 if let Some(type_) = type_ {
261 return Err(Error::new_spanned(
262 type_,
263 "specifying `type_` on fields inside structs and enum variants is redundant and not allowed."
264 ));
265 }
266
267 Ok(Box::new(AttributeField {
268 xml_name,
269 xml_namespace: namespace,
270 default_,
271 }))
272 }
273
274 XmlFieldMeta::Text {
275 span: _,
276 codec,
277 type_,
278 } => {
279 // This would've been taken via `XmlFieldMeta::take_type` if
280 // this field was within an extract where a `type_` is legal
281 // to have.
282 if let Some(type_) = type_ {
283 return Err(Error::new_spanned(
284 type_,
285 "specifying `type_` on fields inside structs and enum variants is redundant and not allowed."
286 ));
287 }
288
289 Ok(Box::new(TextField { codec }))
290 }
291
292 XmlFieldMeta::Child {
293 span: _,
294 default_,
295 amount,
296 } => {
297 if let Some(AmountConstraint::Any(ref amount_span)) = amount {
298 if let Flag::Present(ref flag_span) = default_ {
299 let mut err =
300 Error::new(*flag_span, "`default` has no meaning for child collections");
301 err.combine(Error::new(
302 *amount_span,
303 "the field is treated as a collection because of this `n` value",
304 ));
305 return Err(err);
306 }
307 }
308
309 Ok(Box::new(ChildField {
310 default_,
311 amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())),
312 extract: None,
313 }))
314 }
315
316 XmlFieldMeta::Extract {
317 span,
318 default_,
319 qname: QNameRef { namespace, name },
320 amount,
321 fields,
322 } => {
323 let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
324 let xml_name = default_name(span, name, field_ident)?;
325
326 let amount = amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site()));
327 match amount {
328 AmountConstraint::Any(ref amount) => {
329 if let Flag::Present(default_) = default_ {
330 let mut err = Error::new(
331 default_,
332 "default cannot be set when collecting into a collection",
333 );
334 err.combine(Error::new(
335 *amount,
336 "`n` was set to a non-1 value here, which enables connection logic",
337 ));
338 return Err(err);
339 }
340 }
341 AmountConstraint::FixedSingle(_) => (),
342 }
343
344 let mut field_defs = Vec::new();
345 let allow_inference =
346 matches!(amount, AmountConstraint::FixedSingle(_)) && fields.len() == 1;
347 for (i, mut field) in fields.into_iter().enumerate() {
348 let field_ty = match field.take_type() {
349 Some(v) => v,
350 None => {
351 if allow_inference {
352 field_ty.clone()
353 } else {
354 return Err(Error::new(
355 field.span(),
356 "extracted field must specify a type explicitly when extracting into a collection or when extracting more than one field."
357 ));
358 }
359 }
360 };
361
362 field_defs.push(FieldDef::from_extract(
363 field,
364 i as u32,
365 &field_ty,
366 &xml_namespace,
367 ));
368 }
369 let parts = Compound::from_field_defs(field_defs)?;
370
371 Ok(Box::new(ChildField {
372 default_,
373 amount,
374 extract: Some(ExtractDef {
375 xml_namespace,
376 xml_name,
377 parts,
378 }),
379 }))
380 }
381
382 #[cfg(feature = "minidom")]
383 XmlFieldMeta::Element { span, amount } => {
384 match amount {
385 Some(AmountConstraint::Any(_)) => (),
386 Some(AmountConstraint::FixedSingle(span)) => {
387 return Err(Error::new(
388 span,
389 "only `n = ..` is supported for #[xml(element)]` currently",
390 ))
391 }
392 None => return Err(Error::new(span, "`n` must be set to `..` currently")),
393 }
394
395 Ok(Box::new(ElementField))
396 }
397
398 #[cfg(not(feature = "minidom"))]
399 XmlFieldMeta::Element { span, amount } => {
400 let _ = amount;
401 Err(Error::new(
402 span,
403 "#[xml(element)] requires xso to be built with the \"minidom\" feature.",
404 ))
405 }
406 }
407}
408
409/// Definition of a single field in a compound.
410///
411/// See [`Compound`][`crate::compound::Compound`] for more information on
412/// compounds in general.
413pub(crate) struct FieldDef {
414 /// A span which refers to the field's definition.
415 span: Span,
416
417 /// The member identifying the field.
418 member: Member,
419
420 /// The type of the field.
421 ty: Type,
422
423 /// The way the field is mapped to XML.
424 inner: Box<dyn Field>,
425}
426
427impl FieldDef {
428 /// Create a new field definition from its declaration.
429 ///
430 /// The `index` must be the zero-based index of the field even for named
431 /// fields.
432 pub(crate) fn from_field(
433 field: &syn::Field,
434 index: u32,
435 container_namespace: &NamespaceRef,
436 ) -> Result<Self> {
437 let (member, ident) = match field.ident.as_ref() {
438 Some(v) => (Member::Named(v.clone()), Some(v)),
439 None => (
440 Member::Unnamed(Index {
441 index,
442 // We use the type's span here, because `field.span()`
443 // will visually point at the `#[xml(..)]` meta, which is
444 // not helpful when glancing at error messages referring
445 // to the field itself.
446 span: field.ty.span(),
447 }),
448 None,
449 ),
450 };
451 // This will either be the field's identifier's span (for named
452 // fields) or the field's type (for unnamed fields), which should give
453 // the user a good visual feedback about which field an error message
454 // is.
455 let field_span = member.span();
456 let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
457 let ty = field.ty.clone();
458
459 Ok(Self {
460 span: field_span,
461 inner: new_field(meta, ident, &ty, container_namespace)?,
462 member,
463 ty,
464 })
465 }
466
467 /// Create a new field definition from its declaration.
468 ///
469 /// The `index` must be the zero-based index of the field even for named
470 /// fields.
471 pub(crate) fn from_extract(
472 meta: XmlFieldMeta,
473 index: u32,
474 ty: &Type,
475 container_namespace: &NamespaceRef,
476 ) -> Result<Self> {
477 let span = meta.span();
478 Ok(Self {
479 span,
480 member: Member::Unnamed(Index { index, span }),
481 ty: ty.clone(),
482 inner: new_field(meta, None, ty, container_namespace)?,
483 })
484 }
485
486 /// Access the [`syn::Member`] identifying this field in the original
487 /// type.
488 pub(crate) fn member(&self) -> &Member {
489 &self.member
490 }
491
492 /// Access the field's type.
493 pub(crate) fn ty(&self) -> &Type {
494 &self.ty
495 }
496
497 /// Construct the builder pieces for this field.
498 ///
499 /// `container_name` must be a reference to the compound's type, so that
500 /// it can be used for error messages.
501 pub(crate) fn make_builder_part(
502 &self,
503 scope: &FromEventsScope,
504 container_name: &ParentRef,
505 ) -> Result<FieldBuilderPart> {
506 self.inner
507 .make_builder_part(scope, container_name, &self.member, &self.ty)
508 }
509
510 /// Construct the iterator pieces for this field.
511 ///
512 /// `bound_name` must be the name to which the field's value is bound in
513 /// the iterator code.
514 pub(crate) fn make_iterator_part(
515 &self,
516 scope: &AsItemsScope,
517 container_name: &ParentRef,
518 bound_name: &Ident,
519 ) -> Result<FieldIteratorPart> {
520 self.inner
521 .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty)
522 }
523
524 /// Return true if this field's parsing consumes text data.
525 pub(crate) fn is_text_field(&self) -> bool {
526 self.inner.captures_text()
527 }
528
529 /// Return a span which points at the field's definition.'
530 pub(crate) fn span(&self) -> Span {
531 self.span
532 }
533}