field.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 quote::{quote, ToTokens};
 11use syn::{spanned::Spanned, *};
 12
 13use rxml_validation::NcName;
 14
 15use crate::error_message::{self, ParentRef};
 16use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
 17use crate::scope::{AsItemsScope, FromEventsScope};
 18use crate::types::{
 19    as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, from_events_fn,
 20    from_xml_builder_ty, from_xml_text_fn, item_iter_ty, option_ty, string_ty,
 21    text_codec_decode_fn, text_codec_encode_fn,
 22};
 23
 24/// Code slices necessary for declaring and initializing a temporary variable
 25/// for parsing purposes.
 26pub(crate) struct FieldTempInit {
 27    /// The type of the temporary variable.
 28    pub(crate) ty: Type,
 29
 30    /// The initializer for the temporary variable.
 31    pub(crate) init: TokenStream,
 32}
 33
 34/// Describe how a struct or enum variant's member is parsed from XML data.
 35///
 36/// This struct is returned from [`FieldDef::make_builder_part`] and
 37/// contains code snippets and instructions for
 38/// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`]
 39/// to parse the field's data from XML.
 40pub(crate) enum FieldBuilderPart {
 41    /// Parse a field from the item's element's start event.
 42    Init {
 43        /// Expression and type which extracts the field's data from the
 44        /// element's start event.
 45        value: FieldTempInit,
 46    },
 47
 48    /// Parse a field from text events.
 49    Text {
 50        /// Expression and type which initializes a buffer to use during
 51        /// parsing.
 52        value: FieldTempInit,
 53
 54        /// Statement which takes text and accumulates it into the temporary
 55        /// value declared via `value`.
 56        collect: TokenStream,
 57
 58        /// Expression which evaluates to the field's type, consuming the
 59        /// temporary value.
 60        finalize: TokenStream,
 61    },
 62
 63    /// Parse a field from child element events.
 64    Nested {
 65        /// Expression and type which initializes a buffer to use during
 66        /// parsing.
 67        value: FieldTempInit,
 68
 69        /// Expression which evaluates to `Result<T, FromEventsError>`,
 70        /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
 71        ///
 72        /// `T` must be the type specified in the
 73        /// [`Self::Nested::builder`]  field.
 74        matcher: TokenStream,
 75
 76        /// Type implementing `xso::FromEventsBuilder` which parses the child
 77        /// element.
 78        ///
 79        /// This type is returned by the expressions in
 80        /// [`matcher`][`Self::Nested::matcher`].
 81        builder: Type,
 82
 83        /// Expression which consumes the value stored in the identifier
 84        /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`]
 85        /// and somehow collects it into the field declared with
 86        /// [`value`][`Self::Nested::value`].
 87        collect: TokenStream,
 88
 89        /// Expression which consumes the data from the field declared with
 90        /// [`value`][`Self::Nested::value`] and converts it into the field's
 91        /// type.
 92        finalize: TokenStream,
 93    },
 94}
 95
 96/// Describe how a struct or enum variant's member is converted to XML data.
 97///
 98/// This struct is returned from [`FieldDef::make_iterator_part`] and
 99/// contains code snippets and instructions for
100/// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`]
101/// to convert the field's data into XML.
102pub(crate) enum FieldIteratorPart {
103    /// The field is emitted as part of StartElement.
104    Header {
105        /// An expression which consumes the field's value and returns a
106        /// `Item`.
107        generator: TokenStream,
108    },
109
110    /// The field is emitted as text item.
111    Text {
112        /// An expression which consumes the field's value and returns a
113        /// String, which is then emitted as text data.
114        generator: TokenStream,
115    },
116
117    /// The field is emitted as series of items which form a child element.
118    Content {
119        /// Expression and type which initializes the nested iterator.
120        ///
121        /// Note that this is evaluated at construction time of the iterator.
122        /// Fields of this variant do not get access to their original data,
123        /// unless they carry it in the contents of this `value`.
124        value: FieldTempInit,
125
126        /// An expression which uses the value (mutably) and evaluates to
127        /// a Result<Option<Item>, Error>. Once the state returns None, the
128        /// processing will advance to the next state.
129        generator: TokenStream,
130    },
131}
132
133/// Specify how the field is mapped to XML.
134enum FieldKind {
135    /// The field maps to an attribute.
136    Attribute {
137        /// The optional XML namespace of the attribute.
138        xml_namespace: Option<NamespaceRef>,
139
140        /// The XML name of the attribute.
141        xml_name: NameRef,
142
143        // Flag indicating whether the value should be defaulted if the
144        // attribute is absent.
145        default_: Flag,
146    },
147
148    /// The field maps to the character data of the element.
149    Text {
150        /// Optional codec to use
151        codec: Option<Type>,
152    },
153
154    /// The field maps to a child
155    Child {
156        // Flag indicating whether the value should be defaulted if the
157        // child is absent.
158        default_: Flag,
159    },
160}
161
162impl FieldKind {
163    /// Construct a new field implementation from the meta attributes.
164    ///
165    /// `field_ident` is, for some field types, used to infer an XML name if
166    /// it is not specified explicitly.
167    fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
168        match meta {
169            XmlFieldMeta::Attribute {
170                span,
171                namespace,
172                name,
173                default_,
174            } => {
175                let xml_name = match name {
176                    Some(v) => v,
177                    None => match field_ident {
178                        None => return Err(Error::new(
179                            span,
180                            "attribute name must be explicitly specified using `#[xml(attribute = ..)] on unnamed fields",
181                        )),
182                        Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
183                            Ok(value) => NameRef::Literal {
184                                span: field_ident.span(),
185                                value,
186                            },
187                            Err(e) => {
188                                return Err(Error::new(
189                                    field_ident.span(),
190                                    format!("invalid XML attribute name: {}", e),
191                                ))
192                            }
193                        },
194                    }
195                };
196
197                Ok(Self::Attribute {
198                    xml_name,
199                    xml_namespace: namespace,
200                    default_,
201                })
202            }
203
204            XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
205
206            XmlFieldMeta::Child { default_ } => Ok(Self::Child { default_ }),
207        }
208    }
209}
210
211/// Definition of a single field in a compound.
212///
213/// See [`Compound`][`crate::compound::Compound`] for more information on
214/// compounds in general.
215pub(crate) struct FieldDef {
216    /// The member identifying the field.
217    member: Member,
218
219    /// The type of the field.
220    ty: Type,
221
222    /// The way the field is mapped to XML.
223    kind: FieldKind,
224}
225
226impl FieldDef {
227    /// Create a new field definition from its declaration.
228    ///
229    /// The `index` must be the zero-based index of the field even for named
230    /// fields.
231    pub(crate) fn from_field(field: &syn::Field, index: u32) -> Result<Self> {
232        let field_span = field.span();
233        let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
234
235        let (member, ident) = match field.ident.as_ref() {
236            Some(v) => (Member::Named(v.clone()), Some(v)),
237            None => (
238                Member::Unnamed(Index {
239                    index,
240                    span: field_span,
241                }),
242                None,
243            ),
244        };
245
246        let ty = field.ty.clone();
247
248        Ok(Self {
249            member,
250            ty,
251            kind: FieldKind::from_meta(meta, ident)?,
252        })
253    }
254
255    /// Access the [`syn::Member`] identifying this field in the original
256    /// type.
257    pub(crate) fn member(&self) -> &Member {
258        &self.member
259    }
260
261    /// Access the field's type.
262    pub(crate) fn ty(&self) -> &Type {
263        &self.ty
264    }
265
266    /// Construct the builder pieces for this field.
267    ///
268    /// `container_name` must be a reference to the compound's type, so that
269    /// it can be used for error messages.
270    pub(crate) fn make_builder_part(
271        &self,
272        scope: &FromEventsScope,
273        container_name: &ParentRef,
274    ) -> Result<FieldBuilderPart> {
275        match self.kind {
276            FieldKind::Attribute {
277                ref xml_name,
278                ref xml_namespace,
279                ref default_,
280            } => {
281                let FromEventsScope { ref attrs, .. } = scope;
282                let ty = self.ty.clone();
283
284                let missing_msg = error_message::on_missing_attribute(container_name, &self.member);
285
286                let xml_namespace = match xml_namespace {
287                    Some(v) => v.to_token_stream(),
288                    None => quote! {
289                        ::xso::exports::rxml::Namespace::none()
290                    },
291                };
292
293                let from_xml_text = from_xml_text_fn(ty.clone());
294
295                let on_absent = match default_ {
296                    Flag::Absent => quote! {
297                        return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
298                    },
299                    Flag::Present(_) => {
300                        let default_ = default_fn(ty.clone());
301                        quote! {
302                            #default_()
303                        }
304                    }
305                };
306
307                Ok(FieldBuilderPart::Init {
308                    value: FieldTempInit {
309                        init: quote! {
310                            match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
311                                ::core::option::Option::Some(v) => v,
312                                ::core::option::Option::None => #on_absent,
313                            }
314                        },
315                        ty: self.ty.clone(),
316                    },
317                })
318            }
319
320            FieldKind::Text { ref codec } => {
321                let FromEventsScope { ref text, .. } = scope;
322                let field_access = scope.access_field(&self.member);
323                let finalize = match codec {
324                    Some(codec_ty) => {
325                        let decode = text_codec_decode_fn(codec_ty.clone(), self.ty.clone());
326                        quote! {
327                            #decode(#field_access)?
328                        }
329                    }
330                    None => {
331                        let from_xml_text = from_xml_text_fn(self.ty.clone());
332                        quote! { #from_xml_text(#field_access)? }
333                    }
334                };
335
336                Ok(FieldBuilderPart::Text {
337                    value: FieldTempInit {
338                        init: quote! { ::std::string::String::new() },
339                        ty: string_ty(Span::call_site()),
340                    },
341                    collect: quote! {
342                        #field_access.push_str(#text.as_str());
343                    },
344                    finalize,
345                })
346            }
347
348            FieldKind::Child { ref default_ } => {
349                let FromEventsScope {
350                    ref substate_result,
351                    ..
352                } = scope;
353                let field_access = scope.access_field(&self.member);
354
355                let missing_msg = error_message::on_missing_child(container_name, &self.member);
356
357                let from_events = from_events_fn(self.ty.clone());
358                let from_xml_builder = from_xml_builder_ty(self.ty.clone());
359
360                let on_absent = match default_ {
361                    Flag::Absent => quote! {
362                        return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
363                    },
364                    Flag::Present(_) => {
365                        let default_ = default_fn(self.ty.clone());
366                        quote! {
367                            #default_()
368                        }
369                    }
370                };
371
372                Ok(FieldBuilderPart::Nested {
373                    value: FieldTempInit {
374                        init: quote! { ::std::option::Option::None },
375                        ty: option_ty(self.ty.clone()),
376                    },
377                    matcher: quote! {
378                        #from_events(name, attrs)
379                    },
380                    builder: from_xml_builder,
381                    collect: quote! {
382                        #field_access = ::std::option::Option::Some(#substate_result);
383                    },
384                    finalize: quote! {
385                        match #field_access {
386                            ::std::option::Option::Some(value) => value,
387                            ::std::option::Option::None => #on_absent,
388                        }
389                    },
390                })
391            }
392        }
393    }
394
395    /// Construct the iterator pieces for this field.
396    ///
397    /// `bound_name` must be the name to which the field's value is bound in
398    /// the iterator code.
399    pub(crate) fn make_iterator_part(
400        &self,
401        scope: &AsItemsScope,
402        bound_name: &Ident,
403    ) -> Result<FieldIteratorPart> {
404        match self.kind {
405            FieldKind::Attribute {
406                ref xml_name,
407                ref xml_namespace,
408                ..
409            } => {
410                let xml_namespace = match xml_namespace {
411                    Some(v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
412                    None => quote! {
413                        ::xso::exports::rxml::Namespace::NONE
414                    },
415                };
416
417                let as_optional_xml_text = as_optional_xml_text_fn(self.ty.clone());
418
419                Ok(FieldIteratorPart::Header {
420                    generator: quote! {
421                        #as_optional_xml_text(#bound_name)?.map(|#bound_name| ::xso::Item::Attribute(
422                            #xml_namespace,
423                            ::std::borrow::Cow::Borrowed(#xml_name),
424                            #bound_name,
425                        ));
426                    },
427                })
428            }
429
430            FieldKind::Text { ref codec } => {
431                let generator = match codec {
432                    Some(codec_ty) => {
433                        let encode = text_codec_encode_fn(codec_ty.clone(), self.ty.clone());
434                        quote! { #encode(#bound_name)? }
435                    }
436                    None => {
437                        let as_xml_text = as_xml_text_fn(self.ty.clone());
438                        quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) }
439                    }
440                };
441
442                Ok(FieldIteratorPart::Text { generator })
443            }
444
445            FieldKind::Child { default_: _ } => {
446                let AsItemsScope { ref lifetime, .. } = scope;
447
448                let as_xml_iter = as_xml_iter_fn(self.ty.clone());
449                let item_iter = item_iter_ty(self.ty.clone(), lifetime.clone());
450
451                Ok(FieldIteratorPart::Content {
452                    value: FieldTempInit {
453                        init: quote! {
454                            #as_xml_iter(#bound_name)?
455                        },
456                        ty: item_iter,
457                    },
458                    generator: quote! {
459                        #bound_name.next().transpose()
460                    },
461                })
462            }
463        }
464    }
465
466    /// Return true if this field's parsing consumes text data.
467    pub(crate) fn is_text_field(&self) -> bool {
468        matches!(self.kind, FieldKind::Text { .. })
469    }
470}