diff --git a/parsers/src/data_forms.rs b/parsers/src/data_forms.rs index b1add0db0e2088e6734b170a62817a970b79a786..d2b0a6a1097b2614e4ac86777240af6b08fae129 100644 --- a/parsers/src/data_forms.rs +++ b/parsers/src/data_forms.rs @@ -24,7 +24,7 @@ pub struct Option_ { pub label: Option, /// The value returned to the server when selecting this option. - #[xml(extract(namespace = ns::DATA_FORMS, name = "value", fields(text)))] + #[xml(extract(fields(text)))] pub value: String, } diff --git a/parsers/src/mix.rs b/parsers/src/mix.rs index ac2ef6b2ea701a1187320c8637dfbd10c0211205..7f40f9fbd82e9f0a44598cf551e5a3c4d2e627c9 100644 --- a/parsers/src/mix.rs +++ b/parsers/src/mix.rs @@ -38,11 +38,11 @@ generate_id!( #[xml(namespace = ns::MIX_CORE, name = "participant")] pub struct Participant { /// The nick of this participant. - #[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))] + #[xml(extract(fields(text)))] pub nick: String, /// The bare JID of this participant. - #[xml(extract(namespace = ns::MIX_CORE, name = "jid", fields(text)))] + #[xml(extract(fields(text)))] pub jid: BareJid, } @@ -85,7 +85,7 @@ pub struct Join { pub id: Option, /// The nick requested by the user or set by the service. - #[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))] + #[xml(extract(fields(text)))] pub nick: String, /// Which MIX nodes to subscribe to. @@ -164,7 +164,7 @@ impl IqResultPayload for Leave {} #[xml(namespace = ns::MIX_CORE, name = "setnick")] pub struct SetNick { /// The new requested nick. - #[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))] + #[xml(extract(fields(text)))] pub nick: String, } @@ -184,11 +184,11 @@ impl SetNick { #[xml(namespace = ns::MIX_CORE, name = "mix")] pub struct Mix { /// The nick of the user who said something. - #[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))] + #[xml(extract(fields(text)))] pub nick: String, /// The JID of the user who said something. - #[xml(extract(namespace = ns::MIX_CORE, name = "jid", fields(text)))] + #[xml(extract(fields(text)))] pub jid: BareJid, } diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 1d1c4f15265db5038e21b30516bdf2873f4d2e45..fd9c312bc4de9b5c9cce5bc0bab36e11a7096664 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -1124,3 +1124,60 @@ fn nested_extract_roundtrip() { }; roundtrip_full::("hello world") } + +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "parent")] +struct ExtractOmitNamespace { + #[xml(extract(name = "child", fields(text)))] + contents: String, +} + +#[test] +fn extract_omit_namespace_roundtrip() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::( + "hello world!", + ) +} + +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "parent")] +struct ExtractOmitName { + #[xml(extract(namespace = NS1, fields(text)))] + contents: String, +} + +#[test] +fn extract_omit_name_roundtrip() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::( + "hello world!", + ) +} + +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "parent")] +struct ExtractOmitNameAndNamespace { + #[xml(extract(fields(text)))] + contents: String, +} + +#[test] +fn extract_omit_name_and_namespace_roundtrip() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::( + "hello world!", + ) +} diff --git a/xso-proc/src/compound.rs b/xso-proc/src/compound.rs index bff02c48ca8d89236474b42bf7f6733dcfdf62da..afe8369cb4fc5c04a69481bee6cac16ae24dbf44 100644 --- a/xso-proc/src/compound.rs +++ b/xso-proc/src/compound.rs @@ -12,6 +12,7 @@ use syn::{spanned::Spanned, *}; use crate::error_message::ParentRef; use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit}; +use crate::meta::NamespaceRef; use crate::scope::{mangle_member, AsItemsScope, FromEventsScope}; use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State}; use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty}; @@ -55,7 +56,10 @@ impl Compound { } /// Construct a compound from fields. - pub(crate) fn from_fields(compound_fields: &Fields) -> Result { + pub(crate) fn from_fields( + compound_fields: &Fields, + container_namespace: &NamespaceRef, + ) -> Result { Self::from_field_defs(compound_fields.iter().enumerate().map(|(i, field)| { let index = match i.try_into() { Ok(v) => v, @@ -68,7 +72,7 @@ impl Compound { )) } }; - FieldDef::from_field(field, index) + FieldDef::from_field(field, index, container_namespace) })) } diff --git a/xso-proc/src/enums.rs b/xso-proc/src/enums.rs index 92ada0696b7ecc94503808cfcb626e7c70314608..404eb0ee5b067aa360d5e42fa97ad400378d1661 100644 --- a/xso-proc/src/enums.rs +++ b/xso-proc/src/enums.rs @@ -33,7 +33,7 @@ struct NameVariant { impl NameVariant { /// Construct a new name-selected variant from its declaration. - fn new(decl: &Variant) -> Result { + fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result { // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. @@ -59,7 +59,7 @@ impl NameVariant { Ok(Self { name, ident: decl.ident.clone(), - inner: Compound::from_fields(&decl.fields)?, + inner: Compound::from_fields(&decl.fields, enum_namespace)?, }) } @@ -190,7 +190,7 @@ impl EnumDef { let mut variants = Vec::new(); let mut seen_names = HashMap::new(); for variant in variant_iter { - let variant = NameVariant::new(variant)?; + let variant = NameVariant::new(variant, &namespace)?; if let Some(other) = seen_names.get(&variant.name) { return Err(Error::new_spanned( variant.name, diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index 26940f9e5bd61360978f317f8d590f9403043512..a1779fd21a43c810db58e60e383fc1aeec3a1230 100644 --- a/xso-proc/src/field.rs +++ b/xso-proc/src/field.rs @@ -192,6 +192,28 @@ enum FieldKind { }, } +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), + )), + }, + }, + } +} + impl FieldKind { /// Construct a new field implementation from the meta attributes. /// @@ -199,34 +221,22 @@ impl FieldKind { /// it is not specified explicitly. /// /// `field_ty` is needed for type inferrence on extracted fields. - fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>, field_ty: &Type) -> Result { + /// + /// `container_namespace` is used in some cases to insert a default + /// namespace. + fn from_meta( + meta: XmlFieldMeta, + field_ident: Option<&Ident>, + field_ty: &Type, + container_namespace: &NamespaceRef, + ) -> Result { match meta { XmlFieldMeta::Attribute { span, qname: QNameRef { namespace, name }, default_, } => { - let xml_name = match name { - Some(v) => v, - None => match field_ident { - None => return Err(Error::new( - span, - "attribute name must be explicitly specified using `#[xml(attribute = ..)] on unnamed fields", - )), - Some(field_ident) => match NcName::try_from(field_ident.to_string()) { - Ok(value) => NameRef::Literal { - span: field_ident.span(), - value, - }, - Err(e) => { - return Err(Error::new( - field_ident.span(), - format!("invalid XML attribute name: {}", e), - )) - } - }, - } - }; + let xml_name = default_name(span, name, field_ident)?; Ok(Self::Attribute { xml_name, @@ -267,19 +277,8 @@ impl FieldKind { qname: QNameRef { namespace, name }, fields, } => { - let Some(xml_namespace) = namespace else { - return Err(Error::new( - span, - "`#[xml(extract(..))]` must contain a `namespace` key.", - )); - }; - - let Some(xml_name) = name else { - return Err(Error::new( - span, - "`#[xml(extract(..))]` must contain a `name` key.", - )); - }; + let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); + let xml_name = default_name(span, name, field_ident)?; let field = { let mut fields = fields.into_iter(); @@ -301,7 +300,7 @@ impl FieldKind { }; let parts = Compound::from_field_defs( - [FieldDef::from_extract(field, 0, field_ty)].into_iter(), + [FieldDef::from_extract(field, 0, field_ty, &xml_namespace)].into_iter(), )?; Ok(Self::Extract { @@ -334,7 +333,11 @@ impl FieldDef { /// /// 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) -> Result { + pub(crate) fn from_field( + field: &syn::Field, + index: u32, + container_namespace: &NamespaceRef, + ) -> Result { let field_span = field.span(); let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?; @@ -352,7 +355,7 @@ impl FieldDef { let ty = field.ty.clone(); Ok(Self { - kind: FieldKind::from_meta(meta, ident, &ty)?, + kind: FieldKind::from_meta(meta, ident, &ty, container_namespace)?, member, ty, }) @@ -362,12 +365,17 @@ impl FieldDef { /// /// 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) -> Result { + pub(crate) fn from_extract( + meta: XmlFieldMeta, + index: u32, + ty: &Type, + container_namespace: &NamespaceRef, + ) -> Result { let span = meta.span(); Ok(Self { member: Member::Unnamed(Index { index, span }), ty: ty.clone(), - kind: FieldKind::from_meta(meta, None, ty)?, + kind: FieldKind::from_meta(meta, None, ty, container_namespace)?, }) } diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index 997feab4db806e9f6cab4cc5c3eb8ee0901750a7..ac9541b6082d786f7a47b3deefbe720f015a15bf 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -61,7 +61,7 @@ macro_rules! reject_key { pub(crate) use reject_key; /// Value for the `#[xml(namespace = ..)]` attribute. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum NamespaceRef { /// The XML namespace is specified as a string literal. LitStr(LitStr), diff --git a/xso-proc/src/structs.rs b/xso-proc/src/structs.rs index b10d5849df0f6ed552676adf7cbb4a6e5e7f1460..c0d8b78432eed53789ce78e78702c21b7cd2ecfe 100644 --- a/xso-proc/src/structs.rs +++ b/xso-proc/src/structs.rs @@ -75,9 +75,9 @@ impl StructDef { }; Ok(Self { + inner: Compound::from_fields(fields, &namespace)?, namespace, name, - inner: Compound::from_fields(fields)?, target_ty_ident: ident.clone(), builder_ty_ident, item_iter_ty_ident, diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index 021c2f0c4070d75b80f0fbf758f7db1f7fe19b16..3f8f24bed1e2445de43a79aba759829031329840 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -316,6 +316,12 @@ expanded to the corresponding namespace URI and the value for the `namespace` key is implied. Mixing a prefixed name with an explicit `namespace` key is not allowed. +Both `namespace` and `name` may be omitted. If `namespace` is omitted, it +defaults to the namespace of the surrounding container. If `name` is omitted +and the `extract` meta is being used on a named field, that field's name is +used. If `name` is omitted and `extract` is not used on a named field, an +error is emitted. + The sequence of field meta inside `fields` can be thought of as a nameless tuple-style struct. The macro generates serialisation/deserialisation code for that nameless tuple-style struct and uses it to serialise/deserialise