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