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