xso: allow omission of namespace and name on extracts
This is a quality-of-life improvement, as it may save lots of typing in the common case (see the diff in parsers).
This commit is contained in:
parent
48855843d4
commit
df63c2a78f
9 changed files with 129 additions and 54 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue