xso-proc: add support for renaming attributes

This is akin to `#[serde(rename = ..)]` and thus useful.
This commit is contained in:
Jonas Schäfer 2024-06-23 10:09:59 +02:00
parent 0bae5d3346
commit 219d682295
4 changed files with 112 additions and 27 deletions

View file

@ -228,3 +228,20 @@ fn required_attribute_missing() {
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
} }
} }
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
#[xml(namespace = NS1, name = "attr")]
struct RenamedAttribute {
#[xml(attribute = "a1")]
foo: String,
}
#[test]
fn renamed_attribute_roundtrip() {
#[allow(unused_imports)]
use std::{
option::Option::{None, Some},
result::Result::{Err, Ok},
};
roundtrip_full::<RenamedAttribute>("<attr xmlns='urn:example:ns1' a1='bar'/>");
}

View file

@ -73,30 +73,30 @@ impl FieldKind {
/// it is not specified explicitly. /// it is not specified explicitly.
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> { fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
match meta { match meta {
XmlFieldMeta::Attribute { span } => { XmlFieldMeta::Attribute { span, name } => {
let Some(field_ident) = field_ident else { let xml_name = match name {
return Err(Error::new( Some(v) => v,
span, None => match field_ident {
"attribute extraction not supported on unnamed fields", None => return Err(Error::new(
)); span,
}; "attribute name must be explicitly specified using `#[xml(attribute = ..)] on unnamed fields",
)),
let xml_name = match NcName::try_from(field_ident.to_string()) { Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
Ok(v) => v, Ok(value) => NameRef::Literal {
Err(e) => { span: field_ident.span(),
return Err(Error::new( value,
field_ident.span(), },
format!("invalid XML attribute name: {}", e), Err(e) => {
)) return Err(Error::new(
field_ident.span(),
format!("invalid XML attribute name: {}", e),
))
}
},
} }
}; };
Ok(Self::Attribute { Ok(Self::Attribute { xml_name })
xml_name: NameRef::Literal {
span: field_ident.span(),
value: xml_name,
},
})
} }
} }
} }

View file

@ -203,15 +203,51 @@ pub(crate) enum XmlFieldMeta {
/// ///
/// This is useful for error messages. /// This is useful for error messages.
span: Span, span: Span,
/// The XML name supplied.
name: Option<NameRef>,
}, },
} }
impl XmlFieldMeta { impl XmlFieldMeta {
/// Parse a `#[xml(attribute(..))]` meta. /// Parse a `#[xml(attribute(..))]` meta.
///
/// That meta can have three distinct syntax styles:
/// - argument-less: `#[xml(attribute)]`
/// - shorthand: `#[xml(attribute = ..)]`
/// - full: `#[xml(attribute(..))]`
fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> { fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
Ok(Self::Attribute { if meta.input.peek(Token![=]) {
span: meta.path.span(), // shorthand syntax
}) Ok(Self::Attribute {
span: meta.path.span(),
name: Some(meta.value()?.parse()?),
})
} else if meta.input.peek(syn::token::Paren) {
// full syntax
let mut name: Option<NameRef> = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
if name.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
}
name = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Attribute {
span: meta.path.span(),
name,
})
} else {
// argument-less syntax
Ok(Self::Attribute {
span: meta.path.span(),
name: None,
})
}
} }
/// Parse [`Self`] from a nestd meta, switching on the identifier /// Parse [`Self`] from a nestd meta, switching on the identifier

View file

@ -62,6 +62,38 @@ The following mapping types are defined:
#### `attribute` meta #### `attribute` meta
The `attribute` meta does not support additional parameters. The field it is The `attribute` meta causes the field to be mapped to an XML attribute of the
used on is mapped to an XML attribute of the same name and must be of type same name. The field must be of type [`String`].
[`String`].
The following keys can be used inside the `#[xml(attribute(..))]` meta:
| Key | Value type | Description |
| --- | --- | --- |
| `name` | *string literal* or *path* | The name of the XML attribute to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
The `attribute` meta also supports a shorthand syntax,
`#[xml(attribute = ..)]`, where the value is treated as the value for the
`name` key.
##### Example
```rust
# use xso::FromXml;
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
#[xml(attribute)]
a: String,
#[xml(attribute = "bar")]
b: String,
#[xml(attribute(name = "baz"))]
c: String,
};
let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example' a='1' bar='2' baz='3'/>").unwrap();
assert_eq!(foo, Foo {
a: "1".to_string(),
b: "2".to_string(),
c: "3".to_string(),
});
```