mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
xso-proc: add support for defaulting in attribute parsing
This commit is contained in:
parent
cea246a0fc
commit
0c57be3e61
5 changed files with 178 additions and 3 deletions
|
@ -330,3 +330,53 @@ fn required_non_string_attribute_roundtrip() {
|
||||||
};
|
};
|
||||||
roundtrip_full::<RequiredNonStringAttribute>("<attr xmlns='urn:example:ns1' foo='-16'/>");
|
roundtrip_full::<RequiredNonStringAttribute>("<attr xmlns='urn:example:ns1' foo='-16'/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "attr")]
|
||||||
|
struct DefaultAttribute {
|
||||||
|
#[xml(attribute(default))]
|
||||||
|
foo: std::option::Option<String>,
|
||||||
|
|
||||||
|
#[xml(attribute(default))]
|
||||||
|
bar: std::option::Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_attribute_roundtrip_aa() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1'/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_attribute_roundtrip_pa() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz'/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_attribute_roundtrip_ap() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' bar='16'/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_attribute_roundtrip_pp() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz' bar='16'/>");
|
||||||
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ use syn::{spanned::Spanned, *};
|
||||||
use rxml_validation::NcName;
|
use rxml_validation::NcName;
|
||||||
|
|
||||||
use crate::error_message::{self, ParentRef};
|
use crate::error_message::{self, ParentRef};
|
||||||
use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta};
|
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
||||||
use crate::scope::{FromEventsScope, IntoEventsScope};
|
use crate::scope::{FromEventsScope, IntoEventsScope};
|
||||||
use crate::types::{from_xml_text_fn, into_optional_xml_text_fn};
|
use crate::types::{default_fn, from_xml_text_fn, into_optional_xml_text_fn};
|
||||||
|
|
||||||
/// Code slices necessary for declaring and initializing a temporary variable
|
/// Code slices necessary for declaring and initializing a temporary variable
|
||||||
/// for parsing purposes.
|
/// for parsing purposes.
|
||||||
|
@ -67,6 +67,10 @@ enum FieldKind {
|
||||||
|
|
||||||
/// The XML name of the attribute.
|
/// The XML name of the attribute.
|
||||||
xml_name: NameRef,
|
xml_name: NameRef,
|
||||||
|
|
||||||
|
// Flag indicating whether the value should be defaulted if the
|
||||||
|
// attribute is absent.
|
||||||
|
default_: Flag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +85,7 @@ impl FieldKind {
|
||||||
span,
|
span,
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
|
default_,
|
||||||
} => {
|
} => {
|
||||||
let xml_name = match name {
|
let xml_name = match name {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
|
@ -107,6 +112,7 @@ impl FieldKind {
|
||||||
Ok(Self::Attribute {
|
Ok(Self::Attribute {
|
||||||
xml_name,
|
xml_name,
|
||||||
xml_namespace: namespace,
|
xml_namespace: namespace,
|
||||||
|
default_,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,6 +187,7 @@ impl FieldDef {
|
||||||
FieldKind::Attribute {
|
FieldKind::Attribute {
|
||||||
ref xml_name,
|
ref xml_name,
|
||||||
ref xml_namespace,
|
ref xml_namespace,
|
||||||
|
ref default_,
|
||||||
} => {
|
} => {
|
||||||
let FromEventsScope { ref attrs, .. } = scope;
|
let FromEventsScope { ref attrs, .. } = scope;
|
||||||
let ty = self.ty.clone();
|
let ty = self.ty.clone();
|
||||||
|
@ -196,12 +203,24 @@ impl FieldDef {
|
||||||
|
|
||||||
let from_xml_text = from_xml_text_fn(ty.clone());
|
let from_xml_text = from_xml_text_fn(ty.clone());
|
||||||
|
|
||||||
|
let on_absent = match default_ {
|
||||||
|
Flag::Absent => quote! {
|
||||||
|
return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
|
||||||
|
},
|
||||||
|
Flag::Present(_) => {
|
||||||
|
let default_ = default_fn(ty.clone());
|
||||||
|
quote! {
|
||||||
|
#default_()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return Ok(FieldBuilderPart::Init {
|
return Ok(FieldBuilderPart::Init {
|
||||||
value: FieldTempInit {
|
value: FieldTempInit {
|
||||||
init: quote! {
|
init: quote! {
|
||||||
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
|
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
|
||||||
::core::option::Option::Some(v) => v,
|
::core::option::Option::Some(v) => v,
|
||||||
::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
|
::core::option::Option::None => #on_absent,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ty: self.ty.clone(),
|
ty: self.ty.clone(),
|
||||||
|
@ -224,6 +243,7 @@ impl FieldDef {
|
||||||
FieldKind::Attribute {
|
FieldKind::Attribute {
|
||||||
ref xml_name,
|
ref xml_name,
|
||||||
ref xml_namespace,
|
ref xml_namespace,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let IntoEventsScope { ref attrs, .. } = scope;
|
let IntoEventsScope { ref attrs, .. } = scope;
|
||||||
|
|
||||||
|
@ -237,6 +257,9 @@ impl FieldDef {
|
||||||
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
|
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
|
||||||
|
|
||||||
return Ok(FieldIteratorPart::Header {
|
return Ok(FieldIteratorPart::Header {
|
||||||
|
// This is a neat little trick:
|
||||||
|
// Option::from(x) converts x to an Option<T> *unless* it
|
||||||
|
// already is an Option<_>.
|
||||||
setter: quote! {
|
setter: quote! {
|
||||||
#into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert(
|
#into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert(
|
||||||
#xml_namespace,
|
#xml_namespace,
|
||||||
|
|
|
@ -108,6 +108,39 @@ impl quote::ToTokens for NameRef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) enum Flag {
|
||||||
|
/// The flag is not set.
|
||||||
|
Absent,
|
||||||
|
|
||||||
|
/// The flag was set.
|
||||||
|
Present(
|
||||||
|
/// The span of the syntax element which enabled the flag.
|
||||||
|
///
|
||||||
|
/// This is used to generate useful error messages by pointing at the
|
||||||
|
/// specific place the flag was activated.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flag {
|
||||||
|
/// Return true if the flag is set, false otherwise.
|
||||||
|
pub(crate) fn is_set(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Absent => false,
|
||||||
|
Self::Present(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Spanned> From<T> for Flag {
|
||||||
|
fn from(other: T) -> Flag {
|
||||||
|
Flag::Present(other.span())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
|
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct XmlCompoundMeta {
|
pub(crate) struct XmlCompoundMeta {
|
||||||
|
@ -261,6 +294,9 @@ pub(crate) enum XmlFieldMeta {
|
||||||
|
|
||||||
/// The XML name supplied.
|
/// The XML name supplied.
|
||||||
name: Option<NameRef>,
|
name: Option<NameRef>,
|
||||||
|
|
||||||
|
/// The `default` flag.
|
||||||
|
default_: Flag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,11 +315,13 @@ impl XmlFieldMeta {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
namespace,
|
namespace,
|
||||||
|
default_: Flag::Absent,
|
||||||
})
|
})
|
||||||
} else if meta.input.peek(syn::token::Paren) {
|
} else if meta.input.peek(syn::token::Paren) {
|
||||||
// full syntax
|
// full syntax
|
||||||
let mut name: Option<NameRef> = None;
|
let mut name: Option<NameRef> = None;
|
||||||
let mut namespace: Option<NamespaceRef> = None;
|
let mut namespace: Option<NamespaceRef> = None;
|
||||||
|
let mut default_ = Flag::Absent;
|
||||||
meta.parse_nested_meta(|meta| {
|
meta.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("name") {
|
if meta.path.is_ident("name") {
|
||||||
if name.is_some() {
|
if name.is_some() {
|
||||||
|
@ -312,6 +350,12 @@ impl XmlFieldMeta {
|
||||||
}
|
}
|
||||||
namespace = Some(meta.value()?.parse()?);
|
namespace = Some(meta.value()?.parse()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if meta.path.is_ident("default") {
|
||||||
|
if default_.is_set() {
|
||||||
|
return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
|
||||||
|
}
|
||||||
|
default_ = (&meta.path).into();
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||||
}
|
}
|
||||||
|
@ -320,6 +364,7 @@ impl XmlFieldMeta {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
|
default_,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// argument-less syntax
|
// argument-less syntax
|
||||||
|
@ -327,6 +372,7 @@ impl XmlFieldMeta {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name: None,
|
name: None,
|
||||||
namespace: None,
|
namespace: None,
|
||||||
|
default_: Flag::Absent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,3 +114,44 @@ pub(crate) fn into_optional_xml_text_fn(ty: Type) -> Expr {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Expr`] referring to
|
||||||
|
/// `<#of_ty as ::std::default::Default>::default`.
|
||||||
|
pub(crate) fn default_fn(of_ty: Type) -> Expr {
|
||||||
|
let span = of_ty.span();
|
||||||
|
Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: Some(QSelf {
|
||||||
|
lt_token: syn::token::Lt { spans: [span] },
|
||||||
|
ty: Box::new(of_ty),
|
||||||
|
position: 3,
|
||||||
|
as_token: Some(syn::token::As { span }),
|
||||||
|
gt_token: syn::token::Gt { spans: [span] },
|
||||||
|
}),
|
||||||
|
path: Path {
|
||||||
|
leading_colon: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
segments: [
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("std", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("default", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("Default", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("default", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,15 @@ syntax construct *meta*.
|
||||||
All key-value pairs interpreted by these derive macros must be wrapped in a
|
All key-value pairs interpreted by these derive macros must be wrapped in a
|
||||||
`#[xml( ... )]` *meta*.
|
`#[xml( ... )]` *meta*.
|
||||||
|
|
||||||
|
The values associated with the keys may be of different types, defined as
|
||||||
|
such:
|
||||||
|
|
||||||
|
- *path*: A Rust path, like `some_crate::foo::Bar`. Note that `foo` on its own
|
||||||
|
is also a path.
|
||||||
|
- *string literal*: A string literal, like `"hello world!"`.
|
||||||
|
- flag: Has no value. The key's mere presence has relevance and it must not be
|
||||||
|
followed by a `=` sign.
|
||||||
|
|
||||||
### Struct meta
|
### Struct meta
|
||||||
|
|
||||||
The following keys are defined on structs:
|
The following keys are defined on structs:
|
||||||
|
@ -72,6 +81,7 @@ The following keys can be used inside the `#[xml(attribute(..))]` meta:
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `namespace` | *string literal* or *path* | The optional namespace of the XML attribute to match. If it is a *path*, it must point at a `&'static str`. Note that attributes, unlike elements, are unnamespaced by default. |
|
| `namespace` | *string literal* or *path* | The optional namespace of the XML attribute to match. If it is a *path*, it must point at a `&'static str`. Note that attributes, unlike elements, are unnamespaced by default. |
|
||||||
| `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`. |
|
| `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`. |
|
||||||
|
| `default` | flag | If present, an absent attribute will substitute the default value instead of raising an error. |
|
||||||
|
|
||||||
If the `name` key contains a namespace prefix, it must be one of the prefixes
|
If the `name` key contains a namespace prefix, it must be one of the prefixes
|
||||||
defined as built-in in the XML specifications. That prefix will then be
|
defined as built-in in the XML specifications. That prefix will then be
|
||||||
|
@ -84,6 +94,11 @@ The `attribute` meta also supports a shorthand syntax,
|
||||||
`name` key (with optional prefix as described above, and unnamespaced
|
`name` key (with optional prefix as described above, and unnamespaced
|
||||||
otherwise).
|
otherwise).
|
||||||
|
|
||||||
|
If `default` is specified and the attribute is absent in the source, the value
|
||||||
|
is generated using [`std::default::Default`], requiring the field type to
|
||||||
|
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
||||||
|
influence on `IntoXml`.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
Loading…
Reference in a new issue