diff --git a/Cargo.toml b/Cargo.toml index 6d517fe9..a1fe074d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -212,6 +212,11 @@ name = "serde-migrated" required-features = ["serialize"] path = "tests/serde-migrated.rs" +[[test]] +name = "serde-issues" +required-features = ["serialize"] +path = "tests/serde-issues.rs" + [[test]] name = "async-tokio" required-features = ["async-tokio"] diff --git a/Changelog.md b/Changelog.md index 364ff29a..8b30ab08 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,8 +14,14 @@ ### Bug Fixes +- [#537]: Restore ability to deserialize attributes that represents XML namespace + mappings (`xmlns:xxx`) that was broken since [#490] + ### Misc Changes +[#490]: https://github.com/tafia/quick-xml/pull/490 +[#537]: https://github.com/tafia/quick-xml/issues/537 + ## 0.27.1 -- 2022-12-28 ### Bug Fixes diff --git a/src/de/key.rs b/src/de/key.rs index b4905d12..a3f182ea 100644 --- a/src/de/key.rs +++ b/src/de/key.rs @@ -35,9 +35,11 @@ fn decode_name<'n>(name: QName<'n>, decoder: Decoder) -> Result, De /// Converts a name to an identifier string using the following rules: /// /// - if it is an [`attribute`] name, put `@` in front of the identifier +/// - if it is a namespace binding (`xmlns` or `xmlns:xxx`) put the decoded name +/// to the identifier /// - put the decoded [`local_name()`] of a name to the identifier /// -/// The final identifier looks like `[@]local_name` +/// The final identifier looks like `[@]local_name`, or `@xmlns`, or `@xmlns:binding` /// (where `[]` means optional element). /// /// The deserializer also supports deserializing names as other primitive types: @@ -72,10 +74,16 @@ pub struct QNameDeserializer<'d> { impl<'d> QNameDeserializer<'d> { /// Creates deserializer from name of an attribute pub fn from_attr(name: QName<'d>, decoder: Decoder) -> Result { - let local = decode_name(name, decoder)?; + // https://github.com/tafia/quick-xml/issues/537 + // Namespace bindings (xmlns:xxx) map to `@xmlns:xxx` instead of `@xxx` + let field = if name.as_namespace_binding().is_some() { + decoder.decode(name.into_inner())? + } else { + decode_name(name, decoder)? + }; Ok(Self { - name: Cow::Owned(format!("@{local}")), + name: Cow::Owned(format!("@{field}")), }) } diff --git a/tests/issues.rs b/tests/issues.rs index 9e4230df..21689bbf 100644 --- a/tests/issues.rs +++ b/tests/issues.rs @@ -1,6 +1,6 @@ -//! Regression tests found in various issues +//! Regression tests found in various issues. //! -//! Name each test as `issue` +//! Name each module / test as `issue` and keep sorted by issue number use quick_xml::events::{BytesStart, Event}; use quick_xml::reader::Reader; diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs new file mode 100644 index 00000000..05733d4c --- /dev/null +++ b/tests/serde-issues.rs @@ -0,0 +1,60 @@ +//! Regression tests found in various issues with serde integration. +//! +//! Name each module / test as `issue` and keep sorted by issue number + +use quick_xml::de::from_str; +use quick_xml::se::to_string; +use serde::{Deserialize, Serialize}; + +/// Regression test for https://github.com/tafia/quick-xml/issues/537. +/// +/// This test checks that special `xmlns:xxx` attributes uses full name of +/// an attribute (xmlns:xxx) as a field name instead of just local name of +/// an attribute (xxx) +mod issue537 { + use super::*; + use pretty_assertions::assert_eq; + + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct Bindings<'a> { + /// Default namespace binding + #[serde(rename = "@xmlns")] + xmlns: &'a str, + + /// Named namespace binding + #[serde(rename = "@xmlns:named")] + xmlns_named: &'a str, + + /// Usual attribute + #[serde(rename = "@attribute")] + attribute: &'a str, + } + + #[test] + fn de() { + assert_eq!( + from_str::( + r#""# + ) + .unwrap(), + Bindings { + xmlns: "default", + xmlns_named: "named", + attribute: "attribute", + } + ); + } + + #[test] + fn se() { + assert_eq!( + to_string(&Bindings { + xmlns: "default", + xmlns_named: "named", + attribute: "attribute", + }) + .unwrap(), + r#""# + ); + } +}