Detailed changes
@@ -24,7 +24,7 @@ pub struct Option_ {
pub label: Option<String>,
/// 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,
}
@@ -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<ParticipantId>,
/// 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,
}
@@ -1124,3 +1124,60 @@ fn nested_extract_roundtrip() {
};
roundtrip_full::<NestedExtract>("<parent xmlns='urn:example:ns1'><child><grandchild>hello world</grandchild></child></parent>")
}
+
+#[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::<ExtractOmitNamespace>(
+ "<parent xmlns='urn:example:ns1'><child>hello world!</child></parent>",
+ )
+}
+
+#[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::<ExtractOmitName>(
+ "<parent xmlns='urn:example:ns1'><contents>hello world!</contents></parent>",
+ )
+}
+
+#[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::<ExtractOmitNameAndNamespace>(
+ "<parent xmlns='urn:example:ns1'><contents>hello world!</contents></parent>",
+ )
+}
@@ -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<Self> {
+ pub(crate) fn from_fields(
+ compound_fields: &Fields,
+ container_namespace: &NamespaceRef,
+ ) -> Result<Self> {
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)
}))
}
@@ -33,7 +33,7 @@ struct NameVariant {
impl NameVariant {
/// Construct a new name-selected variant from its declaration.
- fn new(decl: &Variant) -> Result<Self> {
+ fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
// 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,
@@ -192,6 +192,28 @@ enum FieldKind {
},
}
+fn default_name(span: Span, name: Option<NameRef>, field_ident: Option<&Ident>) -> Result<NameRef> {
+ 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<Self> {
+ ///
+ /// `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<Self> {
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<Self> {
+ pub(crate) fn from_field(
+ field: &syn::Field,
+ index: u32,
+ container_namespace: &NamespaceRef,
+ ) -> Result<Self> {
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<Self> {
+ pub(crate) fn from_extract(
+ meta: XmlFieldMeta,
+ index: u32,
+ ty: &Type,
+ container_namespace: &NamespaceRef,
+ ) -> Result<Self> {
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)?,
})
}
@@ -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),
@@ -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,
@@ -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