// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Compound (struct or enum variant) field types use proc_macro2::{Span, TokenStream}; use syn::{spanned::Spanned, *}; use rxml_validation::NcName; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{ AmountConstraint, AttributeKind, Flag, NameRef, NamespaceRef, QNameRef, XmlFieldMeta, }; use crate::scope::{AsItemsScope, FromEventsScope}; mod attribute; mod child; #[cfg(feature = "minidom")] mod element; mod flag; mod text; use self::attribute::{AttributeField, AttributeFieldKind}; use self::child::{ChildField, ExtractDef}; #[cfg(feature = "minidom")] use self::element::ElementField; use self::flag::FlagField; use self::text::TextField; /// Code slices necessary for declaring and initializing a temporary variable /// for parsing purposes. pub(crate) struct FieldTempInit { /// The type of the temporary variable. pub(crate) ty: Type, /// The initializer for the temporary variable. pub(crate) init: TokenStream, } /// Configure how a nested field builder selects child elements. pub(crate) enum NestedMatcher { /// Matches a specific child element fallabily. Selective( /// Expression which evaluates to `Result`, /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`. /// /// If the `name` and `attrs` allow starting to parse the child /// element as a value of this field, `Ok(_)` must be returned. If /// the `name` and `attrs` are those of an element which *could* /// be a value of this field, but they have invalid contents, /// `Err(FromEventsError::Invalid(_))` must be returned. Depending /// on the field kind, it may also be acceptable to return the /// `Invalid` variant if the data is valid, but no further child /// element can be accepted into the value. /// /// Otherwise, the `name` and `attrs` must be returned *unchanged* in /// a `FromEventsError::Mismatch { .. }` variant. In that case, the /// implementation in `Compound` will let the next field attempt to /// parse the child element. /// /// `T` must be the type specified in the /// [`FieldBuilderPart::Nested::builder`] field. TokenStream, ), #[cfg_attr(not(feature = "minidom"), allow(dead_code))] /// Matches any child element not matched by another matcher. /// /// Only a single field may use this variant, otherwise an error is /// raised during execution of the proc macro. Fallback( /// Expression which evaluates to `T` (or `return`s an error), /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`. /// /// Unlike the [`Selective`][`Self::Selective`] variant, this /// expression must always evaluate to an instance of `T`. If that is /// not possible, the expression must diverge, most commonly using /// `return` with a `Err::<_, xso::error::Error>(_)`. /// /// `T` must be the type specified in the /// [`FieldBuilderPart::Nested::builder`] field. TokenStream, ), } /// Describe how a struct or enum variant's member is parsed from XML data. /// /// This struct is returned from [`FieldDef::make_builder_part`] and /// contains code snippets and instructions for /// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`] /// to parse the field's data from XML. pub(crate) enum FieldBuilderPart { /// Parse a field from the item's element's start event. Init { /// Expression and type which extracts the field's data from the /// element's start event. value: FieldTempInit, }, /// Parse a field from text events. Text { /// Expression and type which initializes a buffer to use during /// parsing. value: FieldTempInit, /// Statement which takes text and accumulates it into the temporary /// value declared via `value`. collect: TokenStream, /// Expression which evaluates to the field's type, consuming the /// temporary value. finalize: TokenStream, }, /// Parse a field from child element events. Nested { /// Additional definition items which need to be inserted at module /// level for the rest of the implementation to work. extra_defs: TokenStream, /// Expression and type which initializes a buffer to use during /// parsing. value: FieldTempInit, /// Configure child matching behaviour for this field. See /// [`NestedMatcher`] for options. matcher: NestedMatcher, /// Type implementing `xso::FromEventsBuilder` which parses the child /// element. /// /// This type is returned by the expressions in /// [`matcher`][`Self::Nested::matcher`]. builder: Type, /// Expression which consumes the value stored in the identifier /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`] /// and somehow collects it into the field declared with /// [`value`][`Self::Nested::value`]. collect: TokenStream, /// Expression which consumes the data from the field declared with /// [`value`][`Self::Nested::value`] and converts it into the field's /// type. finalize: TokenStream, }, } /// Describe how a struct or enum variant's member is converted to XML data. /// /// This struct is returned from [`FieldDef::make_iterator_part`] and /// contains code snippets and instructions for /// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`] /// to convert the field's data into XML. pub(crate) enum FieldIteratorPart { /// The field is emitted as part of StartElement. Header { /// An expression which consumes the field's value and returns a /// `Item`. generator: TokenStream, }, /// The field is emitted as text item. Text { /// An expression which consumes the field's value and returns a /// String, which is then emitted as text data. generator: TokenStream, }, /// The field is emitted as series of items which form a child element. Content { /// Additional definition items which need to be inserted at module /// level for the rest of the implementation to work. extra_defs: TokenStream, /// Expression and type which initializes the nested iterator. /// /// Note that this is evaluated at construction time of the iterator. /// Fields of this variant do not get access to their original data, /// unless they carry it in the contents of this `value`. value: FieldTempInit, /// An expression which uses the value (mutably) and evaluates to /// a Result, Error>. Once the state returns None, the /// processing will advance to the next state. generator: TokenStream, }, } trait Field { /// Construct the builder pieces for this field. /// /// `container_name` must be a reference to the compound's type, so that /// it can be used for error messages. /// /// `member` and `ty` refer to the field itself. fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result; /// Construct the iterator pieces for this field. /// /// `bound_name` must be the name to which the field's value is bound in /// the iterator code. /// /// `member` and `ty` refer to the field itself. /// /// `bound_name` is the name under which the field's value is accessible /// in the various parts of the code. fn make_iterator_part( &self, scope: &AsItemsScope, container_name: &ParentRef, bound_name: &Ident, member: &Member, ty: &Type, ) -> Result; /// Return true if and only if this field captures text content. fn captures_text(&self) -> bool { false } /// Return a QNameRef if the field captures an attribute. fn captures_attribute(&self) -> Option { None } } fn default_name(span: Span, name: Option, field_ident: Option<&Ident>) -> Result { match name { Some(v) => Ok(v), None => match field_ident { None => Err(Error::new( span, "name must be explicitly specified with the `name` key on unnamed fields", )), Some(field_ident) => match NcName::try_from(field_ident.to_string()) { Ok(value) => Ok(NameRef::Literal { span: field_ident.span(), value, }), Err(e) => Err(Error::new( field_ident.span(), format!("invalid XML name: {}", e), )), }, }, } } /// Construct a new field implementation from the meta attributes. /// /// `field_ident` is, for some field types, used to infer an XML name if /// it is not specified explicitly. /// /// `field_ty` is needed for type inference on extracted fields. /// /// `container_namespace` is used in some cases to insert a default /// namespace. fn new_field( meta: XmlFieldMeta, field_ident: Option<&Ident>, field_ty: &Type, container_namespace: &NamespaceRef, ) -> Result> { match meta { XmlFieldMeta::Attribute { span, kind: AttributeKind::Generic(QNameRef { name, namespace }), default_, type_, codec, } => { let xml_name = default_name(span, name, field_ident)?; // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(AttributeField { kind: AttributeFieldKind::Generic { xml_name, xml_namespace: namespace, }, default_, codec, })) } XmlFieldMeta::Attribute { span: _, kind: AttributeKind::XmlLang, default_, type_, codec, } => { // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(AttributeField { kind: AttributeFieldKind::XmlLang, default_, codec, })) } XmlFieldMeta::Text { span: _, codec, type_, } => { // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(TextField { codec })) } XmlFieldMeta::Child { span: _, default_, amount, } => { if let Some(AmountConstraint::Any(ref amount_span)) = amount { if let Flag::Present(ref flag_span) = default_ { let mut err = Error::new(*flag_span, "`default` has no meaning for child collections"); err.combine(Error::new( *amount_span, "the field is treated as a collection because of this `n` value", )); return Err(err); } } Ok(Box::new(ChildField { default_, amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())), extract: None, })) } XmlFieldMeta::Extract { span, default_, qname: QNameRef { namespace, name }, amount, fields, on_unknown_attribute, on_unknown_child, } => { let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); let xml_name = default_name(span, name, field_ident)?; let amount = amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())); match amount { AmountConstraint::Any(ref amount) => { if let Flag::Present(default_) = default_ { let mut err = Error::new( default_, "default cannot be set when collecting into a collection", ); err.combine(Error::new( *amount, "`n` was set to a non-1 value here, which enables collection logic", )); return Err(err); } } AmountConstraint::FixedSingle(_) => (), } let mut field_defs = Vec::new(); let allow_inference = matches!(amount, AmountConstraint::FixedSingle(_)) && fields.len() == 1; for (i, mut field) in fields.into_iter().enumerate() { let field_ty = match field.take_type() { Some(v) => v, None => { if allow_inference { field_ty.clone() } else { return Err(Error::new( field.span(), "extracted field must specify a type explicitly when extracting into a collection or when extracting more than one field." )); } } }; field_defs.push(FieldDef::from_extract( field, i as u32, &field_ty, &xml_namespace, )); } let parts = Compound::from_field_defs( field_defs, on_unknown_attribute, on_unknown_child, vec![], )?; Ok(Box::new(ChildField { default_, amount, extract: Some(ExtractDef { xml_namespace, xml_name, parts, }), })) } #[cfg(feature = "minidom")] XmlFieldMeta::Element { span, default_, amount, } => Ok(Box::new(ElementField { default_, amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)), })), #[cfg(not(feature = "minidom"))] XmlFieldMeta::Element { span, amount, default_, } => { let _ = amount; let _ = default_; Err(Error::new( span, "#[xml(element)] requires xso to be built with the \"minidom\" feature.", )) } XmlFieldMeta::Flag { span, qname: QNameRef { namespace, name }, } => { let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); let xml_name = default_name(span, name, field_ident)?; Ok(Box::new(FlagField { xml_namespace, xml_name, })) } } } /// Definition of a single field in a compound. /// /// See [`Compound`][`crate::compound::Compound`] for more information on /// compounds in general. pub(crate) struct FieldDef { /// A span which refers to the field's definition. span: Span, /// The member identifying the field. member: Member, /// The type of the field. ty: Type, /// The way the field is mapped to XML. inner: Box, } impl FieldDef { /// Create a new field definition from its declaration. /// /// The `index` must be the zero-based index of the field even for named /// fields. pub(crate) fn from_field( field: &syn::Field, index: u32, container_namespace: &NamespaceRef, ) -> Result { let (member, ident) = match field.ident.as_ref() { Some(v) => (Member::Named(v.clone()), Some(v)), None => ( Member::Unnamed(Index { index, // We use the type's span here, because `field.span()` // will visually point at the `#[xml(..)]` meta, which is // not helpful when glancing at error messages referring // to the field itself. span: field.ty.span(), }), None, ), }; // This will either be the field's identifier's span (for named // fields) or the field's type (for unnamed fields), which should give // the user a good visual feedback about which field an error message // is. let field_span = member.span(); let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?; let ty = field.ty.clone(); Ok(Self { span: field_span, inner: new_field(meta, ident, &ty, container_namespace)?, member, ty, }) } /// Create a new field definition from its declaration. /// /// The `index` must be the zero-based index of the field even for named /// fields. pub(crate) fn from_extract( meta: XmlFieldMeta, index: u32, ty: &Type, container_namespace: &NamespaceRef, ) -> Result { let span = meta.span(); Ok(Self { span, member: Member::Unnamed(Index { index, span }), ty: ty.clone(), inner: new_field(meta, None, ty, container_namespace)?, }) } /// Access the [`syn::Member`] identifying this field in the original /// type. pub(crate) fn member(&self) -> &Member { &self.member } /// Access the field's type. pub(crate) fn ty(&self) -> &Type { &self.ty } /// Construct the builder pieces for this field. /// /// `container_name` must be a reference to the compound's type, so that /// it can be used for error messages. pub(crate) fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, ) -> Result { self.inner .make_builder_part(scope, container_name, &self.member, &self.ty) } /// Construct the iterator pieces for this field. /// /// `bound_name` must be the name to which the field's value is bound in /// the iterator code. pub(crate) fn make_iterator_part( &self, scope: &AsItemsScope, container_name: &ParentRef, bound_name: &Ident, ) -> Result { self.inner .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty) } /// Return true if this field's parsing consumes text data. pub(crate) fn is_text_field(&self) -> bool { self.inner.captures_text() } /// Return a QNameRef if the field captures an attribute. pub(crate) fn captures_attribute(&self) -> Option { self.inner.captures_attribute() } /// Return a span which points at the field's definition. pub(crate) fn span(&self) -> Span { self.span } }