Allow direct String fields to match the empty string
Empty is not the same as missing, and the empty string crops up a lot in upnp/dlna.
This commit is contained in:
parent
dfc5894edf
commit
a1610f689f
|
@ -234,6 +234,7 @@ fn deserialize_struct(
|
||||||
let mut declare_values = TokenStream::new();
|
let mut declare_values = TokenStream::new();
|
||||||
let mut return_val = TokenStream::new();
|
let mut return_val = TokenStream::new();
|
||||||
let mut direct = TokenStream::new();
|
let mut direct = TokenStream::new();
|
||||||
|
let mut after_loop = TokenStream::new();
|
||||||
|
|
||||||
let mut borrowed = BTreeSet::new();
|
let mut borrowed = BTreeSet::new();
|
||||||
for (index, field) in fields.named.iter().enumerate() {
|
for (index, field) in fields.named.iter().enumerate() {
|
||||||
|
@ -263,6 +264,7 @@ fn deserialize_struct(
|
||||||
field_meta,
|
field_meta,
|
||||||
&input.ident,
|
&input.ident,
|
||||||
&container_meta,
|
&container_meta,
|
||||||
|
&mut after_loop,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
|
@ -361,6 +363,7 @@ fn deserialize_struct(
|
||||||
node => return Err(Error::UnexpectedNode(format!("{:?} in {}", node, #ident_str))),
|
node => return Err(Error::UnexpectedNode(format!("{:?} in {}", node, #ident_str))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#after_loop
|
||||||
|
|
||||||
*into = Some(Self { #return_val });
|
*into = Some(Self { #return_val });
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -401,6 +404,7 @@ fn deserialize_inline_struct(
|
||||||
let mut declare_values = TokenStream::new();
|
let mut declare_values = TokenStream::new();
|
||||||
let mut return_val = TokenStream::new();
|
let mut return_val = TokenStream::new();
|
||||||
let mut direct = TokenStream::new();
|
let mut direct = TokenStream::new();
|
||||||
|
let mut after_loop = TokenStream::new();
|
||||||
|
|
||||||
let mut borrowed = BTreeSet::new();
|
let mut borrowed = BTreeSet::new();
|
||||||
let mut matches = TokenStream::new();
|
let mut matches = TokenStream::new();
|
||||||
|
@ -433,6 +437,7 @@ fn deserialize_inline_struct(
|
||||||
field_meta,
|
field_meta,
|
||||||
&input.ident,
|
&input.ident,
|
||||||
&meta,
|
&meta,
|
||||||
|
&mut after_loop,
|
||||||
);
|
);
|
||||||
|
|
||||||
let data = match result {
|
let data = match result {
|
||||||
|
@ -548,6 +553,7 @@ fn named_field<'a>(
|
||||||
mut field_meta: FieldMeta,
|
mut field_meta: FieldMeta,
|
||||||
type_name: &Ident,
|
type_name: &Ident,
|
||||||
container_meta: &ContainerMeta,
|
container_meta: &ContainerMeta,
|
||||||
|
after_loop: &mut TokenStream,
|
||||||
) -> Result<FieldData<'a>, syn::Error> {
|
) -> Result<FieldData<'a>, syn::Error> {
|
||||||
let field_name = field.ident.as_ref().unwrap();
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
let field_tag = field_meta.tag;
|
let field_tag = field_meta.tag;
|
||||||
|
@ -598,6 +604,15 @@ fn named_field<'a>(
|
||||||
let mut #val_name = <#no_lifetime_type as FromXml>::Accumulator::default();
|
let mut #val_name = <#no_lifetime_type as FromXml>::Accumulator::default();
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if field_meta.direct {
|
||||||
|
declare_values.extend(quote!(
|
||||||
|
// We will synthesize an empty text node when processing a direct
|
||||||
|
// element. Without this, we can miscategorize an empty tag as
|
||||||
|
// a missing tag
|
||||||
|
let mut seen_direct = false;
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let deserialize_with = field_meta
|
let deserialize_with = field_meta
|
||||||
.deserialize_with
|
.deserialize_with
|
||||||
.map(|with| {
|
.map(|with| {
|
||||||
|
@ -630,10 +645,21 @@ fn named_field<'a>(
|
||||||
} else if field_meta.direct {
|
} else if field_meta.direct {
|
||||||
direct.extend(quote!(
|
direct.extend(quote!(
|
||||||
Node::Text(text) => {
|
Node::Text(text) => {
|
||||||
|
seen_direct = true;
|
||||||
let mut nested = deserializer.for_node(Node::Text(text));
|
let mut nested = deserializer.for_node(Node::Text(text));
|
||||||
<#no_lifetime_type>::deserialize(&mut #val_name, #field_str, &mut nested)?;
|
<#no_lifetime_type>::deserialize(&mut #val_name, #field_str, &mut nested)?;
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
// We can only enter this FromXml impl if the caller found the opening
|
||||||
|
// tag, so if we don't see the text node before the closing tag that is
|
||||||
|
// implied by terminating the loop, we need to populate the
|
||||||
|
// direct field with the implied empty text node.
|
||||||
|
after_loop.extend(quote!(
|
||||||
|
if !seen_direct {
|
||||||
|
let mut nested = deserializer.for_node(Node::Text("".into()));
|
||||||
|
<#no_lifetime_type>::deserialize(&mut #val_name, #field_str, &mut nested)?;
|
||||||
|
}
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
tokens.r#match.extend(quote!(
|
tokens.r#match.extend(quote!(
|
||||||
__Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND {
|
__Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND {
|
||||||
|
|
|
@ -270,10 +270,10 @@ impl<'xml> FromXml<'xml> for String {
|
||||||
return Err(Error::DuplicateValue(field));
|
return Err(Error::DuplicateValue(field));
|
||||||
}
|
}
|
||||||
|
|
||||||
match deserializer.take_str()? {
|
*into = Some(match deserializer.take_str()? {
|
||||||
Some(value) => *into = Some(value.into_owned()),
|
Some(value) => value.into_owned(),
|
||||||
None => return Ok(()),
|
None => String::new(),
|
||||||
}
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -300,12 +300,11 @@ impl<'xml, 'a> FromXml<'xml> for Cow<'a, str> {
|
||||||
return Err(Error::DuplicateValue(field));
|
return Err(Error::DuplicateValue(field));
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = match deserializer.take_str()? {
|
into.inner = Some(match deserializer.take_str()? {
|
||||||
Some(value) => value,
|
Some(value) => value.into_owned().into(),
|
||||||
None => return Ok(()),
|
None => "".into(),
|
||||||
};
|
});
|
||||||
|
|
||||||
into.inner = Some(value.into_owned().into());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use similar_asserts::assert_eq;
|
use similar_asserts::assert_eq;
|
||||||
|
|
||||||
use instant_xml::{from_str, Error, FromXml};
|
use instant_xml::{from_str, Error, FromXml};
|
||||||
|
@ -33,3 +35,70 @@ fn direct_namespaces() {
|
||||||
Err::<StructDirectNamespace, _>(Error::MissingValue("StructDirectNamespace::flag"))
|
Err::<StructDirectNamespace, _>(Error::MissingValue("StructDirectNamespace::flag"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, FromXml)]
|
||||||
|
struct DirectString {
|
||||||
|
s: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direct_string() {
|
||||||
|
assert_eq!(
|
||||||
|
from_str("<DirectString><s>hello</s></DirectString>"),
|
||||||
|
Ok(DirectString {
|
||||||
|
s: "hello".to_string()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, FromXml)]
|
||||||
|
struct DirectStr<'a> {
|
||||||
|
s: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direct_empty_str() {
|
||||||
|
assert_eq!(
|
||||||
|
from_str("<DirectStr><s></s></DirectStr>"),
|
||||||
|
Ok(DirectStr { s: "".into() })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direct_missing_string() {
|
||||||
|
assert_eq!(
|
||||||
|
from_str("<DirectString></DirectString>"),
|
||||||
|
Err::<DirectString, _>(Error::MissingValue("DirectString::s"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, FromXml)]
|
||||||
|
struct ArtUri {
|
||||||
|
#[xml(direct)]
|
||||||
|
uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, FromXml)]
|
||||||
|
struct Container {
|
||||||
|
art: Option<ArtUri>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn container_empty_string() {
|
||||||
|
assert_eq!(
|
||||||
|
from_str("<Container><ArtUri></ArtUri></Container>"),
|
||||||
|
Ok(Container {
|
||||||
|
art: Some(ArtUri {
|
||||||
|
uri: "".to_string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
from_str("<Container><ArtUri/></Container>"),
|
||||||
|
Ok(Container {
|
||||||
|
art: Some(ArtUri {
|
||||||
|
uri: "".to_string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue