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 =
395                Compound::from_field_defs(field_defs, on_unknown_attribute, on_unknown_child)?;
396
397            Ok(Box::new(ChildField {
398                default_,
399                amount,
400                extract: Some(ExtractDef {
401                    xml_namespace,
402                    xml_name,
403                    parts,
404                }),
405            }))
406        }
407
408        #[cfg(feature = "minidom")]
409        XmlFieldMeta::Element {
410            span,
411            default_,
412            amount,
413        } => Ok(Box::new(ElementField {
414            default_,
415            amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)),
416        })),
417
418        #[cfg(not(feature = "minidom"))]
419        XmlFieldMeta::Element { span, amount } => {
420            let _ = amount;
421            Err(Error::new(
422                span,
423                "#[xml(element)] requires xso to be built with the \"minidom\" feature.",
424            ))
425        }
426
427        XmlFieldMeta::Flag {
428            span,
429            qname: QNameRef { namespace, name },
430        } => {
431            let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
432            let xml_name = default_name(span, name, field_ident)?;
433            Ok(Box::new(FlagField {
434                xml_namespace,
435                xml_name,
436            }))
437        }
438    }
439}
440
441/// Definition of a single field in a compound.
442///
443/// See [`Compound`][`crate::compound::Compound`] for more information on
444/// compounds in general.
445pub(crate) struct FieldDef {
446    /// A span which refers to the field's definition.
447    span: Span,
448
449    /// The member identifying the field.
450    member: Member,
451
452    /// The type of the field.
453    ty: Type,
454
455    /// The way the field is mapped to XML.
456    inner: Box<dyn Field>,
457}
458
459impl FieldDef {
460    /// Create a new field definition from its declaration.
461    ///
462    /// The `index` must be the zero-based index of the field even for named
463    /// fields.
464    pub(crate) fn from_field(
465        field: &syn::Field,
466        index: u32,
467        container_namespace: &NamespaceRef,
468    ) -> Result<Self> {
469        let (member, ident) = match field.ident.as_ref() {
470            Some(v) => (Member::Named(v.clone()), Some(v)),
471            None => (
472                Member::Unnamed(Index {
473                    index,
474                    // We use the type's span here, because `field.span()`
475                    // will visually point at the `#[xml(..)]` meta, which is
476                    // not helpful when glancing at error messages referring
477                    // to the field itself.
478                    span: field.ty.span(),
479                }),
480                None,
481            ),
482        };
483        // This will either be the field's identifier's span (for named
484        // fields) or the field's type (for unnamed fields), which should give
485        // the user a good visual feedback about which field an error message
486        // is.
487        let field_span = member.span();
488        let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
489        let ty = field.ty.clone();
490
491        Ok(Self {
492            span: field_span,
493            inner: new_field(meta, ident, &ty, container_namespace)?,
494            member,
495            ty,
496        })
497    }
498
499    /// Create a new field definition from its declaration.
500    ///
501    /// The `index` must be the zero-based index of the field even for named
502    /// fields.
503    pub(crate) fn from_extract(
504        meta: XmlFieldMeta,
505        index: u32,
506        ty: &Type,
507        container_namespace: &NamespaceRef,
508    ) -> Result<Self> {
509        let span = meta.span();
510        Ok(Self {
511            span,
512            member: Member::Unnamed(Index { index, span }),
513            ty: ty.clone(),
514            inner: new_field(meta, None, ty, container_namespace)?,
515        })
516    }
517
518    /// Access the [`syn::Member`] identifying this field in the original
519    /// type.
520    pub(crate) fn member(&self) -> &Member {
521        &self.member
522    }
523
524    /// Access the field's type.
525    pub(crate) fn ty(&self) -> &Type {
526        &self.ty
527    }
528
529    /// Construct the builder pieces for this field.
530    ///
531    /// `container_name` must be a reference to the compound's type, so that
532    /// it can be used for error messages.
533    pub(crate) fn make_builder_part(
534        &self,
535        scope: &FromEventsScope,
536        container_name: &ParentRef,
537    ) -> Result<FieldBuilderPart> {
538        self.inner
539            .make_builder_part(scope, container_name, &self.member, &self.ty)
540    }
541
542    /// Construct the iterator pieces for this field.
543    ///
544    /// `bound_name` must be the name to which the field's value is bound in
545    /// the iterator code.
546    pub(crate) fn make_iterator_part(
547        &self,
548        scope: &AsItemsScope,
549        container_name: &ParentRef,
550        bound_name: &Ident,
551    ) -> Result<FieldIteratorPart> {
552        self.inner
553            .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty)
554    }
555
556    /// Return true if this field's parsing consumes text data.
557    pub(crate) fn is_text_field(&self) -> bool {
558        self.inner.captures_text()
559    }
560
561    /// Return a span which points at the field's definition.'
562    pub(crate) fn span(&self) -> Span {
563        self.span
564    }
565}