Skip to main content

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}