diff --git a/xso-proc/src/compound.rs b/xso-proc/src/compound.rs index dc92900c1e1e81975889979269cbb8e6d65a8d7f..55f41da2336a32d8759354203c3c75467409d1ca 100644 --- a/xso-proc/src/compound.rs +++ b/xso-proc/src/compound.rs @@ -8,7 +8,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::*; +use syn::{spanned::Spanned, *}; use crate::error_message::ParentRef; use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit}; @@ -26,6 +26,7 @@ impl Compound { /// Construct a compound from fields. pub(crate) fn from_fields(compound_fields: &Fields) -> Result { let mut fields = Vec::with_capacity(compound_fields.len()); + let mut text_field = None; for (i, field) in compound_fields.iter().enumerate() { let index = match i.try_into() { Ok(v) => v, @@ -38,7 +39,24 @@ impl Compound { )) } }; - fields.push(FieldDef::from_field(field, index)?); + let field = FieldDef::from_field(field, index)?; + + if field.is_text_field() { + if let Some(other_field) = text_field.as_ref() { + let mut err = Error::new_spanned( + field.member(), + "only one `#[xml(text)]` field allowed per compound", + ); + err.combine(Error::new( + *other_field, + "the other `#[xml(text)]` field is here", + )); + return Err(err); + } + text_field = Some(field.member().span()) + } + + fields.push(field); } Ok(Self { fields }) @@ -104,10 +122,9 @@ impl Compound { finalize, } => { if text_handler.is_some() { - return Err(Error::new_spanned( - field.member(), - "more than one field attempts to collect text data", - )); + // the existence of only one text handler is enforced + // by Compound's constructor(s). + panic!("more than one field attempts to collect text data"); } builder_data_def.extend(quote! { diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index 613a29242b831cad989869493a023f4e49818540..410abf72fb4fa9514398b636dc48bb70a5a8ca95 100644 --- a/xso-proc/src/field.rs +++ b/xso-proc/src/field.rs @@ -329,4 +329,12 @@ impl FieldDef { } } } + + /// Return true if this field's parsing consumes text data. + pub(crate) fn is_text_field(&self) -> bool { + match self.kind { + FieldKind::Text { .. } => true, + _ => false, + } + } } diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index 8362f609c954dff0f847eec1e589a251438dfb61..55e4d119f52e0ef43d71c68d47922072222cc083 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -140,7 +140,9 @@ The `text` meta causes the field to be mapped to the text content of the element. For `FromXml`, the field's type must implement [`FromXmlText`] and for `IntoXml`, the field's type must implement [`IntoXmlText`]. -The `text` meta supports no options or value. +The `text` meta supports no options or value. Only a single field per struct +may be annotated with `#[xml(text)]` at a time, to avoid parsing ambiguities. +This is also true if only `IntoXml` is derived on a field, for consistency. ##### Example