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 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}