Skip to content

Commit 440e8fe

Browse files
authored
Merge pull request #672 from Mingun/fix-671
Always deserialize simpleTypes (for example, attribute values) as Some(…)
2 parents 569ac22 + ab8d519 commit 440e8fe

File tree

3 files changed

+81
-37
lines changed

3 files changed

+81
-37
lines changed

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ serde >= 1.0.181
3030
unit structs, unit variants). `<int>123<something-else/></int>` is no longer valid
3131
content. Previously all data after `123` up to closing tag would be silently skipped.
3232
- [#567]: Fixed incorrect deserialization of vectors of enums from sequences of tags.
33+
- [#671]: Fixed deserialization of empty `simpleType`s (for example, attributes) into
34+
`Option` fields: now they are always deserialized as `Some("")`.
3335

3436
### Misc Changes
3537

@@ -58,6 +60,7 @@ serde >= 1.0.181
5860
[#661]: https://github.com/tafia/quick-xml/pull/661
5961
[#662]: https://github.com/tafia/quick-xml/pull/662
6062
[#665]: https://github.com/tafia/quick-xml/pull/665
63+
[#671]: https://github.com/tafia/quick-xml/issues/671
6164

6265

6366
## 0.30.0 -- 2023-07-23

src/de/simple_type.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,32 @@ impl<'de, 'a> SeqAccess<'de> for ListIter<'de, 'a> {
481481
/// - attribute values (`<... ...="value" ...>`)
482482
/// - mixed text / CDATA content (`<...>text<![CDATA[cdata]]></...>`)
483483
///
484+
/// This deserializer processes items as following:
485+
/// - numbers are parsed from a text content using [`FromStr`];
486+
/// - booleans converted from the text according to the XML [specification]:
487+
/// - `"true"` and `"1"` converted to `true`;
488+
/// - `"false"` and `"0"` converted to `false`;
489+
/// - strings returned as is;
490+
/// - characters also returned as strings. If string contain more than one character
491+
/// or empty, it is responsibility of a type to return an error;
492+
/// - `Option` always deserialized as `Some` using the same deserializer.
493+
/// If attribute or text content is missed, then the deserializer even wouldn't
494+
/// be used, so if it is used, then the value should be;
495+
/// - units (`()`) and unit structs always deserialized successfully;
496+
/// - newtype structs forwards deserialization to the inner type using the same
497+
/// deserializer;
498+
/// - sequences, tuples and tuple structs are deserialized as `xs:list`s. Only
499+
/// sequences of primitive types is possible to deserialize this way and they
500+
/// should be delimited by a space (` `, `\t`, `\r`, or `\n`);
501+
/// - structs and maps returns [`DeError::Unsupported`];
502+
/// - enums:
503+
/// - unit variants: just return `()`;
504+
/// - all other variants returns [`DeError::Unsupported`];
505+
/// - identifiers are deserialized as strings.
506+
///
484507
/// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition
508+
/// [`FromStr`]: std::str::FromStr
509+
/// [specification]: https://www.w3.org/TR/xmlschema11-2/#boolean
485510
pub struct SimpleTypeDeserializer<'de, 'a> {
486511
/// - In case of attribute contains escaped attribute value
487512
/// - In case of text contains unescaped text value
@@ -642,11 +667,7 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> {
642667
where
643668
V: Visitor<'de>,
644669
{
645-
if self.content.is_empty() {
646-
visitor.visit_none()
647-
} else {
648-
visitor.visit_some(self)
649-
}
670+
visitor.visit_some(self)
650671
}
651672

652673
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@@ -1225,7 +1246,7 @@ mod tests {
12251246
err!(utf8, borrowed_bytes: Bytes = "&lt;escaped&#32;string"
12261247
=> Unsupported("binary data content is not supported by XML format"));
12271248

1228-
simple!(utf8, option_none: Option<&str> = "" => None);
1249+
simple!(utf8, option_none: Option<&str> = "" => Some(""));
12291250
simple!(utf8, option_some: Option<&str> = "non-escaped string" => Some("non-escaped string"));
12301251

12311252
simple_only!(utf8, unit: () = "any data" => ());
@@ -1311,7 +1332,7 @@ mod tests {
13111332
unsupported!(borrowed_bytes: Bytes = "&lt;escaped&#32;string"
13121333
=> "binary data content is not supported by XML format");
13131334

1314-
utf16!(option_none: Option<()> = "" => None);
1335+
utf16!(option_none: Option<()> = "" => Some(()));
13151336
utf16!(option_some: Option<()> = "any data" => Some(()));
13161337

13171338
utf16!(unit: () = "any data" => ());

tests/serde-issues.rs

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod issue252 {
1616

1717
#[test]
1818
fn attributes() {
19-
#[derive(Serialize, Debug, PartialEq)]
19+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
2020
struct OptionalAttributes {
2121
#[serde(rename = "@a")]
2222
a: Option<&'static str>,
@@ -26,58 +26,78 @@ mod issue252 {
2626
b: Option<&'static str>,
2727
}
2828

29+
// Writing `a=""` for a `None` we reflects serde_json behavior which also
30+
// writes `a: null` for `None`, and reflect they deserialization asymmetry
31+
let xml = r#"<OptionalAttributes a=""/>"#;
2932
assert_eq!(
3033
to_string(&OptionalAttributes { a: None, b: None }).unwrap(),
31-
r#"<OptionalAttributes a=""/>"#
34+
xml
3235
);
3336
assert_eq!(
34-
to_string(&OptionalAttributes {
37+
from_str::<OptionalAttributes>(xml).unwrap(),
38+
OptionalAttributes {
3539
a: Some(""),
36-
b: Some("")
37-
})
38-
.unwrap(),
39-
r#"<OptionalAttributes a="" b=""/>"#
40-
);
41-
assert_eq!(
42-
to_string(&OptionalAttributes {
43-
a: Some("a"),
44-
b: Some("b")
45-
})
46-
.unwrap(),
47-
r#"<OptionalAttributes a="a" b="b"/>"#
40+
b: None
41+
}
4842
);
43+
44+
let value = OptionalAttributes {
45+
a: Some(""),
46+
b: Some(""),
47+
};
48+
let xml = r#"<OptionalAttributes a="" b=""/>"#;
49+
assert_eq!(to_string(&value).unwrap(), xml);
50+
assert_eq!(from_str::<OptionalAttributes>(xml).unwrap(), value);
51+
52+
let value = OptionalAttributes {
53+
a: Some("a"),
54+
b: Some("b"),
55+
};
56+
let xml = r#"<OptionalAttributes a="a" b="b"/>"#;
57+
assert_eq!(to_string(&value).unwrap(), xml);
58+
assert_eq!(from_str::<OptionalAttributes>(xml).unwrap(), value);
4959
}
5060

5161
#[test]
5262
fn elements() {
53-
#[derive(Serialize, Debug, PartialEq)]
63+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
5464
struct OptionalElements {
5565
a: Option<&'static str>,
5666

5767
#[serde(skip_serializing_if = "Option::is_none")]
5868
b: Option<&'static str>,
5969
}
6070

71+
// Writing `<a/>` for a `None` we reflects serde_json behavior which also
72+
// writes `a: null` for `None`, and reflect they deserialization asymmetry
73+
let xml = "<OptionalElements><a/></OptionalElements>";
6174
assert_eq!(
6275
to_string(&OptionalElements { a: None, b: None }).unwrap(),
63-
r#"<OptionalElements><a/></OptionalElements>"#
76+
xml
6477
);
6578
assert_eq!(
66-
to_string(&OptionalElements {
79+
from_str::<OptionalElements>(xml).unwrap(),
80+
OptionalElements {
6781
a: Some(""),
68-
b: Some("")
69-
})
70-
.unwrap(),
71-
r#"<OptionalElements><a/><b/></OptionalElements>"#
72-
);
73-
assert_eq!(
74-
to_string(&OptionalElements {
75-
a: Some("a"),
76-
b: Some("b")
77-
})
78-
.unwrap(),
79-
r#"<OptionalElements><a>a</a><b>b</b></OptionalElements>"#
82+
b: None
83+
}
8084
);
85+
86+
let value = OptionalElements {
87+
a: Some(""),
88+
b: Some(""),
89+
};
90+
let xml = "<OptionalElements><a/><b/></OptionalElements>";
91+
assert_eq!(to_string(&value).unwrap(), xml);
92+
assert_eq!(from_str::<OptionalElements>(xml).unwrap(), value);
93+
94+
let value = OptionalElements {
95+
a: Some("a"),
96+
b: Some("b"),
97+
};
98+
let xml = "<OptionalElements><a>a</a><b>b</b></OptionalElements>";
99+
assert_eq!(to_string(&value).unwrap(), xml);
100+
assert_eq!(from_str::<OptionalElements>(xml).unwrap(), value);
81101
}
82102
}
83103

0 commit comments

Comments
 (0)