mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
xso_proc: add support for switching on an attribute key/value pair
Needed for the `http_upload::Header` enum, but could also be interesting to use on an IQ stanza in the future.
This commit is contained in:
parent
517339e375
commit
cac4e08484
|
@ -729,3 +729,111 @@ fn extract_field_with_super_negative_wrong_name() {
|
|||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "attr-switched-enum", attribute = "key", exhaustive)]
|
||||
pub enum AttributeSwitchedEnum {
|
||||
#[xml(value = "variant-1")]
|
||||
Variant1 {
|
||||
#[xml(attribute(name = "variant-1-attr"))]
|
||||
data: String,
|
||||
},
|
||||
#[xml(value = "variant-2")]
|
||||
Variant2 {
|
||||
#[xml(attribute(name = "variant-2-attr"))]
|
||||
data: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_roundtrip_variant_1() {
|
||||
crate::util::test::roundtrip_full::<AttributeSwitchedEnum>(
|
||||
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-1' variant-1-attr='data'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_roundtrip_variant_2() {
|
||||
crate::util::test::roundtrip_full::<AttributeSwitchedEnum>(
|
||||
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_matches_namespace() {
|
||||
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
||||
"<attr-switched-enum xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' key='variant-2' variant-2-attr='data'/>",
|
||||
) {
|
||||
Err(Error::TypeMismatch(_, _, _)) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_matches_name() {
|
||||
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
||||
"<other-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
||||
) {
|
||||
Err(Error::TypeMismatch(_, _, _)) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_rejects_unknown_value() {
|
||||
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
||||
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='quak'/>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("This is not a").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_rejects_missing_attribute() {
|
||||
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
||||
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' fnord='variant-2'/>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("discriminator attribute").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "attr-switched-enum-fallback", attribute = "key")]
|
||||
pub enum AttributeSwitchedEnumFallback {
|
||||
#[xml(value = "variant-1")]
|
||||
Variant1 {
|
||||
#[xml(attribute(name = "variant-1-attr"))]
|
||||
data: String,
|
||||
},
|
||||
#[xml(value = "variant-2", fallback)]
|
||||
Variant2 {
|
||||
#[xml(attribute(name = "variant-2-attr"))]
|
||||
data: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_fallback_roundtrip_variant_1() {
|
||||
crate::util::test::roundtrip_full::<AttributeSwitchedEnumFallback>(
|
||||
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-1' variant-1-attr='data'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_fallback_roundtrip_variant_2() {
|
||||
crate::util::test::roundtrip_full::<AttributeSwitchedEnumFallback>(
|
||||
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_switched_enum_fallback() {
|
||||
match crate::util::test::parse_str::<AttributeSwitchedEnumFallback>(
|
||||
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-3' variant-2-attr='data'/>",
|
||||
) {
|
||||
Ok(v) => assert_eq!(v, AttributeSwitchedEnumFallback::Variant2 { data: "data".to_string() }),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,17 +118,28 @@ impl Compound {
|
|||
/// `std::cmp::PartialEq<str>` context.
|
||||
///
|
||||
/// - `residual` must be the identifier at which the element is found.
|
||||
/// - `forgive_attributes` must be a (potentially empty) slice of XML
|
||||
/// attribute names to ignore during the unknown attribute check. This
|
||||
/// can be used to ignore attributes which have been used in element
|
||||
/// matching (e.g. enum discriminators).
|
||||
pub(crate) fn build_try_from_element(
|
||||
self,
|
||||
container_name: &ParentRef,
|
||||
container_namespace_expr: &Expr,
|
||||
residual: &Ident,
|
||||
forgive_attributes: &[&str],
|
||||
) -> Result<TokenStream> {
|
||||
let readable_name = container_name.to_string();
|
||||
|
||||
let mut init = quote! {};
|
||||
let mut tupinit = quote! {};
|
||||
let mut attrcheck = quote! {};
|
||||
let mut attrcheck = quote! {
|
||||
#(
|
||||
if key == #forgive_attributes {
|
||||
continue;
|
||||
}
|
||||
)*
|
||||
};
|
||||
let mut tempinit = quote! {};
|
||||
let mut childiter = quote! {};
|
||||
let mut childfallback = quote! {
|
||||
|
|
|
@ -52,11 +52,15 @@ fn build_ident_mapping<I: Iterator<Item = Member>>(
|
|||
(orig_names, mapped_names, Box::new(map_ident))
|
||||
}
|
||||
|
||||
/// Variant of an enum which switches on the XML element's name.
|
||||
/// Variant of an enum which switches on a string value extracted from the XML
|
||||
/// element.
|
||||
///
|
||||
/// The caller of the respective methods decides what string the variant
|
||||
/// matches against and how that match is carried out.
|
||||
#[cfg_attr(feature = "debug", derive(Debug))]
|
||||
struct XmlNameVariant {
|
||||
/// The XML name to match against.
|
||||
xml_name: Name,
|
||||
struct StrMatchedVariant {
|
||||
/// The string to match against.
|
||||
value: LitStr,
|
||||
|
||||
/// The identifier of the enum variant.
|
||||
ident: Ident,
|
||||
|
@ -68,18 +72,69 @@ struct XmlNameVariant {
|
|||
inner: Compound,
|
||||
}
|
||||
|
||||
impl XmlNameVariant {
|
||||
/// Parse a [`syn::Variant`] as XML name switched variant.
|
||||
fn new(variant: &Variant) -> Result<Self> {
|
||||
let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
|
||||
|
||||
if let Some(namespace) = meta.namespace {
|
||||
impl StrMatchedVariant {
|
||||
fn prevalidate(meta: &mut XmlCompoundMeta) -> Result<()> {
|
||||
if let Some(namespace) = meta.namespace.take() {
|
||||
return Err(Error::new_spanned(
|
||||
namespace,
|
||||
"`namespace` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(attribute) = meta.attribute.take() {
|
||||
return Err(Error::new_spanned(
|
||||
attribute,
|
||||
"`attribute` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Flag::Present(transparent) = meta.transparent.take() {
|
||||
return Err(Error::new(
|
||||
transparent,
|
||||
"`transparent` not allowed on enum variants in enums with a fixed namespace.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
|
||||
return Err(Error::new(
|
||||
exhaustive,
|
||||
"`exhaustive` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(validate) = meta.validate.take() {
|
||||
return Err(Error::new_spanned(
|
||||
validate,
|
||||
"`validate` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(prepare) = meta.prepare.take() {
|
||||
return Err(Error::new_spanned(
|
||||
prepare,
|
||||
"`validate` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Flag::Present(debug) = meta.debug.take() {
|
||||
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse a [`syn::Variant`] as XML name switched variant.
|
||||
fn new_xml_name_switched(variant: &Variant) -> Result<Self> {
|
||||
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
|
||||
Self::prevalidate(&mut meta)?;
|
||||
|
||||
if let Some(value) = meta.value {
|
||||
return Err(Error::new_spanned(
|
||||
value,
|
||||
"`value` not allowed on enum variants inside enums matching on XML name.",
|
||||
));
|
||||
}
|
||||
|
||||
let name = if let Some(name) = meta.name {
|
||||
name.into()
|
||||
} else {
|
||||
|
@ -91,40 +146,37 @@ impl XmlNameVariant {
|
|||
|
||||
let fallback = meta.fallback;
|
||||
|
||||
if let Flag::Present(transparent) = meta.transparent {
|
||||
return Err(Error::new(
|
||||
transparent,
|
||||
"`transparent` not allowed on enum variants in enums with a fixed namespace.",
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
value: name,
|
||||
ident: variant.ident.clone(),
|
||||
fallback,
|
||||
inner: Compound::from_fields(&variant.fields)?,
|
||||
})
|
||||
}
|
||||
|
||||
if let Flag::Present(exhaustive) = meta.exhaustive {
|
||||
return Err(Error::new(
|
||||
exhaustive,
|
||||
"`exhaustive` not allowed on enum variants.",
|
||||
));
|
||||
}
|
||||
/// Parse a [`syn::Variant`] as XML attribute value switched variant.
|
||||
fn new_xml_attribute_switched(variant: &Variant) -> Result<Self> {
|
||||
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
|
||||
Self::prevalidate(&mut meta)?;
|
||||
|
||||
if let Some(validate) = meta.validate {
|
||||
if let Some(name) = meta.name {
|
||||
return Err(Error::new_spanned(
|
||||
validate,
|
||||
"`validate` not allowed on enum variants.",
|
||||
name,
|
||||
"`name` not allowed on enum variants inside enums matching on attribute values.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(prepare) = meta.prepare {
|
||||
return Err(Error::new_spanned(
|
||||
prepare,
|
||||
"`validate` not allowed on enum variants.",
|
||||
let Some(value) = meta.value else {
|
||||
return Err(Error::new(
|
||||
meta.span,
|
||||
"`value` is required on enum variants in enums matching on attribute values.",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if let Flag::Present(debug) = meta.debug {
|
||||
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
|
||||
}
|
||||
let fallback = meta.fallback;
|
||||
|
||||
Ok(Self {
|
||||
xml_name: name,
|
||||
value,
|
||||
ident: variant.ident.clone(),
|
||||
fallback,
|
||||
inner: Compound::from_fields(&variant.fields)?,
|
||||
|
@ -207,7 +259,7 @@ struct XmlNameSwitched {
|
|||
namespace: StaticNamespace,
|
||||
|
||||
/// The enum's variants.
|
||||
variants: Vec<XmlNameVariant>,
|
||||
variants: Vec<StrMatchedVariant>,
|
||||
}
|
||||
|
||||
impl XmlNameSwitched {
|
||||
|
@ -228,7 +280,7 @@ impl XmlNameSwitched {
|
|||
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
|
||||
let mut had_fallback = false;
|
||||
for variant in input {
|
||||
let variant = XmlNameVariant::new(variant)?;
|
||||
let variant = StrMatchedVariant::new_xml_name_switched(variant)?;
|
||||
if let Flag::Present(fallback) = variant.fallback {
|
||||
if had_fallback {
|
||||
return Err(syn::Error::new(
|
||||
|
@ -282,7 +334,7 @@ impl XmlNameSwitched {
|
|||
|
||||
for variant in self.variants {
|
||||
let ident = variant.ident;
|
||||
let xml_name = variant.xml_name;
|
||||
let xml_name = variant.value;
|
||||
|
||||
let variant_impl = variant.inner.build_try_from_element(
|
||||
&(Path {
|
||||
|
@ -297,6 +349,7 @@ impl XmlNameSwitched {
|
|||
.into()),
|
||||
&namespace_expr,
|
||||
residual,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
let variant_impl = quote! {
|
||||
|
@ -351,7 +404,7 @@ impl XmlNameSwitched {
|
|||
let builder = Ident::new("builder", Span::call_site());
|
||||
let mut matchers = quote! {};
|
||||
for variant in self.variants {
|
||||
let xml_name = variant.xml_name;
|
||||
let xml_name = variant.value;
|
||||
let path = Path {
|
||||
leading_colon: None,
|
||||
segments: [
|
||||
|
@ -388,6 +441,218 @@ impl XmlNameSwitched {
|
|||
}
|
||||
}
|
||||
|
||||
/// An enum which switches on the value of an attribute of the XML element.
|
||||
#[cfg_attr(feature = "debug", derive(Debug))]
|
||||
struct XmlAttributeSwitched {
|
||||
/// The namespace the enum's XML element resides in.
|
||||
namespace: StaticNamespace,
|
||||
|
||||
/// The XML name of the element.
|
||||
name: Name,
|
||||
|
||||
/// The name of the XML attribute to read.
|
||||
attribute_name: LitStr,
|
||||
|
||||
/// The enum's variants.
|
||||
variants: Vec<StrMatchedVariant>,
|
||||
}
|
||||
|
||||
impl XmlAttributeSwitched {
|
||||
/// Construct a new XML name switched enum from parts.
|
||||
///
|
||||
/// - `exhaustive` must be the exhaustive flag. Currently, this must be
|
||||
/// set on [`XmlAttributeSwitched`] enums.
|
||||
///
|
||||
/// - `namespace` must be the XML namespace of the enum.
|
||||
///
|
||||
/// - `input` must be an iterator emitting borrowed [`syn::Variant`]
|
||||
/// structs to process.
|
||||
fn new<'x, I: Iterator<Item = &'x Variant>>(
|
||||
exhaustive: Flag,
|
||||
namespace: StaticNamespace,
|
||||
name: Name,
|
||||
attribute_name: LitStr,
|
||||
input: I,
|
||||
) -> Result<Self> {
|
||||
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
|
||||
let mut had_fallback = false;
|
||||
for variant in input {
|
||||
let variant = StrMatchedVariant::new_xml_attribute_switched(variant)?;
|
||||
if let Flag::Present(fallback) = variant.fallback {
|
||||
if had_fallback {
|
||||
return Err(syn::Error::new(
|
||||
fallback,
|
||||
"only one variant may be a fallback variant",
|
||||
));
|
||||
}
|
||||
had_fallback = true;
|
||||
}
|
||||
variants.push(variant);
|
||||
}
|
||||
|
||||
if let Flag::Present(exhaustive) = exhaustive {
|
||||
if had_fallback {
|
||||
return Err(syn::Error::new(exhaustive, "exhaustive cannot be sensibly combined with a fallback variant. choose one or the other."));
|
||||
}
|
||||
} else {
|
||||
if !had_fallback {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attribute_name,
|
||||
"enums switching on an attribute must be marked exhaustive or have a fallback variant."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
namespace,
|
||||
name,
|
||||
attribute_name,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct an expression which consumes the `minidom::Element` at
|
||||
/// `residual` and returns a `Result<T, Error>`.
|
||||
///
|
||||
/// - `enum_ident` must be the identifier of the enum's type.
|
||||
/// - `validate` must be a statement which takes a mutable reference to
|
||||
/// the identifier `result` and `return`'s with an error if it is not
|
||||
/// acceptable.
|
||||
/// - `residual` must be the identifier at which the element is found.
|
||||
fn build_try_from_element(
|
||||
self,
|
||||
enum_ident: &Ident,
|
||||
validate: Stmt,
|
||||
residual: &Ident,
|
||||
) -> Result<TokenStream> {
|
||||
let xml_namespace = self.namespace;
|
||||
let xml_name = self.name;
|
||||
let attribute_name = self.attribute_name.value();
|
||||
let namespace_expr = Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: xml_namespace.clone(),
|
||||
});
|
||||
|
||||
let mut fallback: Option<TokenStream> = None;
|
||||
let mut iter = quote! {};
|
||||
|
||||
for variant in self.variants {
|
||||
let ident = variant.ident;
|
||||
let xml_name = variant.value;
|
||||
|
||||
let variant_impl = variant.inner.build_try_from_element(
|
||||
&(Path {
|
||||
leading_colon: None,
|
||||
segments: [
|
||||
PathSegment::from(enum_ident.clone()),
|
||||
PathSegment::from(ident.clone()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
}
|
||||
.into()),
|
||||
&namespace_expr,
|
||||
residual,
|
||||
&[attribute_name.as_str()],
|
||||
)?;
|
||||
|
||||
let variant_impl = quote! {
|
||||
let mut result = #variant_impl;
|
||||
#validate
|
||||
Ok(result)
|
||||
};
|
||||
|
||||
if variant.fallback.is_set() {
|
||||
fallback = Some(quote! {
|
||||
_ => { #variant_impl },
|
||||
})
|
||||
}
|
||||
|
||||
iter = quote! {
|
||||
#iter
|
||||
#xml_name => { #variant_impl },
|
||||
};
|
||||
}
|
||||
|
||||
let fallback = fallback.unwrap_or_else(|| quote! {
|
||||
_ => Err(::xso::error::Error::ParseError(concat!("This is not a ", stringify!(#enum_ident), " element."))),
|
||||
});
|
||||
|
||||
let on_missing = format!(
|
||||
"Required discriminator attribute '{}' on enum {} element missing.",
|
||||
attribute_name, enum_ident,
|
||||
);
|
||||
|
||||
Ok(quote! {
|
||||
if #residual.is(#xml_name, #xml_namespace) {
|
||||
match #residual.attr(#attribute_name) {
|
||||
Some(v) => match v {
|
||||
#iter
|
||||
#fallback
|
||||
}
|
||||
None => Err(::xso::error::Error::ParseError(#on_missing)),
|
||||
}
|
||||
} else {
|
||||
Err(::xso::error::Error::TypeMismatch("", "", #residual))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a token stream which contains the arms of a `match`
|
||||
/// expression dissecting the enum and building `minidom::Element` objects
|
||||
/// for each variant.
|
||||
///
|
||||
/// `ty_ident` must be the identifier of the enum's type.
|
||||
fn build_into_element(self, ty_ident: &Ident) -> Result<TokenStream> {
|
||||
let xml_namespace = self.namespace;
|
||||
let xml_name = self.name;
|
||||
let attribute_name = self.attribute_name;
|
||||
let namespace_expr = Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: xml_namespace.clone(),
|
||||
});
|
||||
let builder = Ident::new("builder", Span::call_site());
|
||||
let mut matchers = quote! {};
|
||||
for variant in self.variants {
|
||||
let attribute_value = variant.value;
|
||||
let path = Path {
|
||||
leading_colon: None,
|
||||
segments: [
|
||||
PathSegment::from(ty_ident.clone()),
|
||||
PathSegment::from(variant.ident),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let (orig_names, mapped_names, map_ident) =
|
||||
build_ident_mapping(variant.inner.iter_members());
|
||||
|
||||
let into_element = variant.inner.build_into_element(
|
||||
&(Path::from(ty_ident.clone()).into()),
|
||||
&namespace_expr,
|
||||
&builder,
|
||||
map_ident,
|
||||
)?;
|
||||
|
||||
matchers = quote! {
|
||||
#matchers
|
||||
#path { #( #orig_names: #mapped_names ),* } => {
|
||||
let #builder = ::xso::exports::minidom::Element::builder(
|
||||
#xml_name,
|
||||
#namespace_expr,
|
||||
).attr(#attribute_name, #attribute_value);
|
||||
let #builder = #into_element;
|
||||
#builder.build()
|
||||
},
|
||||
};
|
||||
}
|
||||
Ok(matchers)
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum where each variant has completely independent matching.
|
||||
#[cfg_attr(feature = "debug", derive(Debug))]
|
||||
struct Dynamic {
|
||||
|
@ -501,6 +766,10 @@ enum EnumInner {
|
|||
/// Enum item where the variants switch on the XML element's name.
|
||||
XmlNameSwitched(XmlNameSwitched),
|
||||
|
||||
/// Enum item where the variants switch on the value of an attribute on
|
||||
/// the XML element.
|
||||
XmlAttributeSwitched(XmlAttributeSwitched),
|
||||
|
||||
/// Enum item which has no matcher on the enum itself, where each variant
|
||||
/// may be an entirely different XML element.
|
||||
Dynamic(Dynamic),
|
||||
|
@ -525,6 +794,9 @@ impl EnumInner {
|
|||
Self::XmlNameSwitched(inner) => {
|
||||
inner.build_try_from_element(enum_ident, validate, residual)
|
||||
}
|
||||
Self::XmlAttributeSwitched(inner) => {
|
||||
inner.build_try_from_element(enum_ident, validate, residual)
|
||||
}
|
||||
Self::Dynamic(inner) => inner.build_try_from_element(enum_ident, validate, residual),
|
||||
}
|
||||
}
|
||||
|
@ -537,6 +809,7 @@ impl EnumInner {
|
|||
fn build_into_element(self, ty_ident: &Ident) -> Result<TokenStream> {
|
||||
match self {
|
||||
Self::XmlNameSwitched(inner) => inner.build_into_element(ty_ident),
|
||||
Self::XmlAttributeSwitched(inner) => inner.build_into_element(ty_ident),
|
||||
Self::Dynamic(inner) => inner.build_into_element(ty_ident),
|
||||
}
|
||||
}
|
||||
|
@ -588,6 +861,13 @@ impl EnumDef {
|
|||
));
|
||||
}
|
||||
|
||||
if let Some(value) = meta.value {
|
||||
return Err(syn::Error::new_spanned(
|
||||
value,
|
||||
"`value` is not allowed on enums",
|
||||
));
|
||||
}
|
||||
|
||||
let namespace = match meta.namespace {
|
||||
None => {
|
||||
// no namespace -> must be dynamic variant. ensure the other
|
||||
|
@ -628,20 +908,40 @@ impl EnumDef {
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(name) = meta.name {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
"`name` cannot be set on enums (only on variants).",
|
||||
));
|
||||
}
|
||||
|
||||
let exhaustive = meta.exhaustive;
|
||||
|
||||
let name = match meta.name {
|
||||
None => {
|
||||
return Ok(Self {
|
||||
prepare,
|
||||
validate,
|
||||
debug,
|
||||
inner: EnumInner::XmlNameSwitched(XmlNameSwitched::new(
|
||||
exhaustive, namespace, input,
|
||||
)?),
|
||||
});
|
||||
}
|
||||
Some(name) => name,
|
||||
};
|
||||
|
||||
let Some(attribute_name) = meta.attribute else {
|
||||
return Err(syn::Error::new(
|
||||
meta.span,
|
||||
"`attribute` option with the name of the XML attribute to match is required for enums matching on attribute value",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
prepare,
|
||||
validate,
|
||||
debug,
|
||||
inner: EnumInner::XmlNameSwitched(XmlNameSwitched::new(exhaustive, namespace, input)?),
|
||||
inner: EnumInner::XmlAttributeSwitched(XmlAttributeSwitched::new(
|
||||
exhaustive,
|
||||
namespace,
|
||||
name.into(),
|
||||
attribute_name,
|
||||
input,
|
||||
)?),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -112,9 +112,9 @@ impl ExtractDef {
|
|||
quote! { data }
|
||||
};
|
||||
|
||||
let parse = self
|
||||
.parts
|
||||
.build_try_from_element(container_name, &namespace_expr, residual)?;
|
||||
let parse =
|
||||
self.parts
|
||||
.build_try_from_element(container_name, &namespace_expr, residual, &[])?;
|
||||
|
||||
let test_expr = match self.namespace {
|
||||
FieldNamespace::Static(xml_namespace) => quote! {
|
||||
|
|
|
@ -330,6 +330,12 @@ pub(crate) struct XmlCompoundMeta {
|
|||
/// The value assigned to `name` inside `#[xml(..)]`, if any.
|
||||
pub(crate) name: Option<NameRef>,
|
||||
|
||||
/// The value assigned to `attribute` inside `#[xml(..)]`, if any.
|
||||
pub(crate) attribute: Option<LitStr>,
|
||||
|
||||
/// The value assigned to `value` inside `#[xml(..)]`, if any.
|
||||
pub(crate) value: Option<LitStr>,
|
||||
|
||||
/// Flag indicating the presence of `fallback` inside `#[xml(..)].
|
||||
pub(crate) fallback: Flag,
|
||||
|
||||
|
@ -357,6 +363,8 @@ impl XmlCompoundMeta {
|
|||
fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
|
||||
let mut name: Option<NameRef> = None;
|
||||
let mut namespace: Option<NamespaceRef> = None;
|
||||
let mut attribute: Option<LitStr> = None;
|
||||
let mut value: Option<LitStr> = None;
|
||||
let mut fallback = Flag::Absent;
|
||||
let mut transparent = Flag::Absent;
|
||||
let mut exhaustive = Flag::Absent;
|
||||
|
@ -369,6 +377,8 @@ impl XmlCompoundMeta {
|
|||
meta,
|
||||
ParseValue("name", &mut name),
|
||||
ParseValue("namespace", &mut namespace),
|
||||
ParseValue("attribute", &mut attribute),
|
||||
ParseValue("value", &mut value),
|
||||
ParseFlag("fallback", &mut fallback),
|
||||
ParseFlag("transparent", &mut transparent),
|
||||
ParseFlag("exhaustive", &mut exhaustive),
|
||||
|
@ -390,6 +400,8 @@ impl XmlCompoundMeta {
|
|||
span: attr.span(),
|
||||
namespace,
|
||||
name,
|
||||
attribute,
|
||||
value,
|
||||
fallback,
|
||||
transparent,
|
||||
validate,
|
||||
|
|
|
@ -93,6 +93,8 @@ impl StructInner {
|
|||
assert!(meta.prepare.is_none());
|
||||
assert!(!meta.debug.is_set());
|
||||
assert!(!meta.fallback.is_set());
|
||||
assert!(meta.attribute.is_none());
|
||||
assert!(meta.value.is_none());
|
||||
|
||||
if let Flag::Present(_) = meta.transparent {
|
||||
if let Some(namespace) = meta.namespace {
|
||||
|
@ -264,7 +266,8 @@ impl StructInner {
|
|||
path: namespace_tempname.clone().into(),
|
||||
});
|
||||
|
||||
let body = inner.build_try_from_element(struct_name, &namespace_expr, residual)?;
|
||||
let body =
|
||||
inner.build_try_from_element(struct_name, &namespace_expr, residual, &[])?;
|
||||
|
||||
match namespace {
|
||||
StructNamespace::Dyn { ty, .. } => Ok(quote! {
|
||||
|
@ -421,13 +424,28 @@ impl StructDef {
|
|||
if let Flag::Present(fallback) = meta.fallback.take() {
|
||||
return Err(syn::Error::new(
|
||||
fallback,
|
||||
"fallback is not allowed on structs",
|
||||
"`fallback` is not allowed on structs",
|
||||
));
|
||||
}
|
||||
|
||||
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
|
||||
return Err(syn::Error::new(
|
||||
exhaustive,
|
||||
"exhaustive is not allowed on structs",
|
||||
"`exhaustive` is not allowed on structs",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(attribute) = meta.attribute.take() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attribute,
|
||||
"`attribute` is not allowed on structs",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(value) = meta.value.take() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
value,
|
||||
"`value` is not allowed on structs",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,11 @@ The following flavors exist:
|
|||
then specify the name. The variant is picked based on the name of the XML
|
||||
element.
|
||||
|
||||
- XML attribute matched: The enum itself defines a namespace, a name and an
|
||||
attribute name. Each variant must specify the attribute value. The variant
|
||||
is picked based on the value of the given attribute, provided that XML name
|
||||
and namespace of the element itself match.
|
||||
|
||||
The flavor is determined based on the attributes of the enum declaration.
|
||||
|
||||
### Dynamic enums
|
||||
|
@ -154,6 +159,42 @@ XML name matched enum variants support the following attributes:
|
|||
declared in the `name` attribute of the variant; the original XML name is
|
||||
lost.
|
||||
|
||||
### XML attribute matched enums
|
||||
|
||||
XML attribute matched enums support the following attributes:
|
||||
|
||||
- `namespace = ..` (required): This must be a path to a `&'static str`. It is
|
||||
the namespace of the enumeration.
|
||||
|
||||
- `name = ..` (required): This must be a string literal containing the XML
|
||||
name to match against.
|
||||
|
||||
- `attribute = ..` (required): This must be a string literal with the name of
|
||||
the XML attribute to match against.
|
||||
|
||||
- `exhaustive` (flag): Must currently be set unless a variant is marked as
|
||||
`fallback`. Support for non-exhaustive attribute-matched enums is not
|
||||
implemented yet.
|
||||
|
||||
This cannot be used if a variant is set as `fallback`.
|
||||
|
||||
This attribute has no relation to the Rust standard `#[non_exhaustive]`
|
||||
attribute.
|
||||
|
||||
#### XML attribute matched enum variants
|
||||
|
||||
XML attribute matched enum variants support the following attributes:
|
||||
|
||||
- `value = ..` (required): String literal with the attribute value to match
|
||||
against.
|
||||
|
||||
- `fallback` (flag): If present, the variant is parsed when no other variant
|
||||
matches.
|
||||
|
||||
*Note:* When the enum is reserialized to XML, the attribute value will be
|
||||
the one declared in the `value` attribute of the variant; the original value
|
||||
is lost.
|
||||
|
||||
## Field attributes
|
||||
|
||||
Field attributes are composed of a field kind, followed by a value or a list
|
||||
|
|
Loading…
Reference in New Issue