Repository: tafia/quick-xml
Pull request #571 (2023-03-04 13:05:24)

Borrow element names when deserialize using serde
Makes it possible to deserialize 
```xml
<root>
  <element>element content<element>
</root>
```
into `HashMap<&str, &str>` (`"element" => "element content"` in this case), for example


Patch:
diff --git a/Changelog.md b/Changelog.md
index 3cefb08..b3103f6 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -23,6 +23,8 @@
 - [#568]: Rename `Writter::inner` into `Writter::get_mut`
 - [#568]: Add method `Writter::get_ref`
 - [#569]: Rewrite the `Reader::read_event_into_async` as an async fn, making the future `Send` if possible.
+- [#571]: Borrow element names (`<element>`) when deserialize with serde.
+  This change allow to deserialize into `HashMap<&str, T>`, for example
 
 ### Bug Fixes
 
@@ -55,6 +57,7 @@
 [#565]: https://github.com/tafia/quick-xml/pull/565
 [#568]: https://github.com/tafia/quick-xml/pull/568
 [#569]: https://github.com/tafia/quick-xml/pull/569
+[#571]: https://github.com/tafia/quick-xml/pull/571
 
 ## 0.27.1 -- 2022-12-28
 
diff --git a/src/de/key.rs b/src/de/key.rs
index a3f182e..e3de28d 100644
--- a/src/de/key.rs
+++ b/src/de/key.rs
@@ -2,6 +2,7 @@ use crate::de::str2bool;
 use crate::encoding::Decoder;
 use crate::errors::serialize::DeError;
 use crate::name::QName;
+use crate::utils::CowRef;
 use serde::de::{DeserializeSeed, Deserializer, EnumAccess, VariantAccess, Visitor};
 use serde::{forward_to_deserialize_any, serde_if_integer128};
 use std::borrow::Cow;
@@ -60,18 +61,19 @@ fn decode_name<'n>(name: QName<'n>, decoder: Decoder) -> Result<Cow<'n, str>, De
 ///
 /// `deserialize_any()` returns the same result as `deserialize_identifier()`.
 ///
-/// # Lifetime
+/// # Lifetimes
 ///
+/// - `'i`: lifetime of the data that the deserializer borrows from the parsed input
 /// - `'d`: lifetime of a deserializer that holds a buffer with content of events
 ///
 /// [`attribute`]: Self::from_attr
 /// [`local_name()`]: QName::local_name
 /// [`Deserialize`]: serde::Deserialize
-pub struct QNameDeserializer<'d> {
-    name: Cow<'d, str>,
+pub struct QNameDeserializer<'i, 'd> {
+    name: CowRef<'i, 'd, str>,
 }
 
-impl<'d> QNameDeserializer<'d> {
+impl<'i, 'd> QNameDeserializer<'i, 'd> {
     /// Creates deserializer from name of an attribute
     pub fn from_attr(name: QName<'d>, decoder: Decoder) -> Result<Self, DeError> {
         // https://github.com/tafia/quick-xml/issues/537
@@ -83,19 +85,34 @@ impl<'d> QNameDeserializer<'d> {
         };
 
         Ok(Self {
-            name: Cow::Owned(format!("@{field}")),
+            name: CowRef::Owned(format!("@{field}")),
         })
     }
 
     /// Creates deserializer from name of an element
-    pub fn from_elem(name: QName<'d>, decoder: Decoder) -> Result<Self, DeError> {
-        let local = decode_name(name, decoder)?;
+    pub fn from_elem(name: CowRef<'i, 'd, [u8]>, decoder: Decoder) -> Result<Self, DeError> {
+        let local = match name {
+            CowRef::Input(borrowed) => match decode_name(QName(borrowed), decoder)? {
+                Cow::Borrowed(borrowed) => CowRef::Input(borrowed),
+                Cow::Owned(owned) => CowRef::Owned(owned),
+            },
+            CowRef::Slice(borrowed) => match decode_name(QName(borrowed), decoder)? {
+                Cow::Borrowed(borrowed) => CowRef::Slice(borrowed),
+                Cow::Owned(owned) => CowRef::Owned(owned),
+            },
+            CowRef::Owned(owned) => match decode_name(QName(&owned), decoder)? {
+                // SAFETY: Because result is borrowed, no changes was done
+                // and we can safely unwrap here
+                Cow::Borrowed(_) => CowRef::Owned(String::from_utf8(owned).unwrap()),
+                Cow::Owned(owned) => CowRef::Owned(owned),
+            },
+        };
 
         Ok(Self { name: local })
     }
 }
 
-impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'d> {
+impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'de, 'd> {
     type Error = DeError;
 
     forward_to_deserialize_any! {
@@ -202,8 +219,9 @@ impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'d> {
         V: Visitor<'de>,
     {
         match self.name {
-            Cow::Borrowed(name) => visitor.visit_str(name),
-            Cow::Owned(name) => visitor.visit_string(name),
+            CowRef::Input(name) => visitor.visit_borrowed_str(name),
+            CowRef::Slice(name) => visitor.visit_str(name),
+            CowRef::Owned(name) => visitor.visit_string(name),
         }
     }
 
@@ -220,7 +238,7 @@ impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'d> {
     }
 }
 
-impl<'de, 'd> EnumAccess<'de> for QNameDeserializer<'d> {
+impl<'de, 'd> EnumAccess<'de> for QNameDeserializer<'de, 'd> {
     type Error = DeError;
     type Variant = QNameUnitOnly;
 
@@ -337,7 +355,7 @@ mod tests {
             #[test]
             fn $name() {
                 let de = QNameDeserializer {
-                    name: Cow::Borrowed($input),
+                    name: CowRef::Input($input),
                 };
                 let data: $type = Deserialize::deserialize(de).unwrap();
 
@@ -352,7 +370,7 @@ mod tests {
             #[test]
             fn $name() {
                 let de = QNameDeserializer {
-                    name: Cow::Borrowed($input),
+                    name: CowRef::Input($input),
                 };
                 let data: $type = Deserialize::deserialize(de).unwrap();
 
@@ -377,7 +395,7 @@ mod tests {
             #[test]
             fn $name() {
                 let de = QNameDeserializer {
-                    name: Cow::Borrowed($input),
+                    name: CowRef::Input($input),
                 };
                 let err = <$type as Deserialize>::deserialize(de).unwrap_err();
 
@@ -420,8 +438,7 @@ mod tests {
         => Custom("invalid value: string \"&lt;\", expected a character"));
 
     deserialized_to!(string: String = "&lt;escaped&#x20;string" => "&lt;escaped&#x20;string");
-    err!(borrowed_str: &str = "name"
-        => Custom("invalid type: string \"name\", expected a borrowed string"));
+    deserialized_to!(borrowed_str: &str = "name" => "name");
 
     err!(byte_buf: ByteBuf = "&lt;escaped&#x20;string"
         => Custom("invalid type: string \"&lt;escaped&#x20;string\", expected byte data"));
diff --git a/src/de/map.rs b/src/de/map.rs
index 7feef94..8c13554 100644
--- a/src/de/map.rs
+++ b/src/de/map.rs
@@ -281,7 +281,7 @@ where
                 DeEvent::Start(e) => {
                     self.source = ValueSource::Nested;
 
-                    let de = QNameDeserializer::from_elem(e.name(), decoder)?;
+                    let de = QNameDeserializer::from_elem(e.raw_name(), decoder)?;
                     seed.deserialize(de).map(Some)
                 }
                 // Stop iteration after reaching a closing tag
diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs
index bd6759a..8146271 100644
--- a/src/de/simple_type.rs
+++ b/src/de/simple_type.rs
@@ -7,11 +7,12 @@ use crate::de::{deserialize_bool, str2bool};
 use crate::encoding::Decoder;
 use crate::errors::serialize::DeError;
 use crate::escape::unescape;
+use crate::utils::CowRef;
 use memchr::memchr;
 use serde::de::{DeserializeSeed, Deserializer, EnumAccess, SeqAccess, VariantAccess, Visitor};
 use serde::{self, serde_if_integer128};
 use std::borrow::Cow;
-use std::ops::{Deref, Range};
+use std::ops::Range;
 
 macro_rules! deserialize_num {
     ($method:ident, $visit:ident) => {
@@ -467,32 +468,6 @@ impl<'de, 'a> SeqAccess<'de> for ListIter<'de, 'a> {
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
-/// A version of [`Cow`] that can borrow from two different buffers, one of them
-/// is a deserializer input.
-///
-/// # Lifetimes
-/// - `'de` -- lifetime of the data that deserializer borrow from the parsed input
-/// - `'a` -- lifetime of the data that owned by a deserializer
-enum CowRef<'de, 'a> {
-    /// An input borrowed from the parsed data
-    Input(&'de [u8]),
-    /// An input borrowed from the buffer owned by another deserializer
-    Slice(&'a [u8]),
-    /// An input taken from an external deserializer, owned by that deserializer
-    Owned(Vec<u8>),
-}
-impl<'de, 'a> Deref for CowRef<'de, 'a> {
-    type Target = [u8];
-
-    fn deref(&self) -> &[u8] {
-        match self {
-            Self::Input(slice) => slice,
-            Self::Slice(slice) => slice,
-            Self::Owned(ref v) => v,
-        }
-    }
-}
-
 /// A deserializer for an xml probably escaped and encoded value of XSD [simple types].
 /// This deserializer will borrow from the input as much as possible.
 ///
@@ -510,7 +485,7 @@ impl<'de, 'a> Deref for CowRef<'de, 'a> {
 pub struct SimpleTypeDeserializer<'de, 'a> {
     /// - In case of attribute contains escaped attribute value
     /// - In case of text contains unescaped text value
-    content: CowRef<'de, 'a>,
+    content: CowRef<'de, 'a, [u8]>,
     /// If `true`, `content` in escaped form and should be unescaped before use
     escaped: bool,
     /// Decoder used to deserialize string data, numeric and boolean data.
@@ -545,7 +520,7 @@ impl<'de, 'a> SimpleTypeDeserializer<'de, 'a> {
 
     /// Constructor for tests
     #[inline]
-    fn new(content: CowRef<'de, 'a>, escaped: bool, decoder: Decoder) -> Self {
+    fn new(content: CowRef<'de, 'a, [u8]>, escaped: bool, decoder: Decoder) -> Self {
         Self {
             content,
             escaped,
diff --git a/src/de/var.rs b/src/de/var.rs
index 3e20090..3229525 100644
--- a/src/de/var.rs
+++ b/src/de/var.rs
@@ -38,7 +38,7 @@ where
         let decoder = self.de.reader.decoder();
         let (name, is_text) = match self.de.peek()? {
             DeEvent::Start(e) => (
-                seed.deserialize(QNameDeserializer::from_elem(e.name(), decoder)?)?,
+                seed.deserialize(QNameDeserializer::from_elem(e.raw_name(), decoder)?)?,
                 false,
             ),
             DeEvent::Text(_) => (
diff --git a/src/events/mod.rs b/src/events/mod.rs
index ab7c1fd..743d05e 100644
--- a/src/events/mod.rs
+++ b/src/events/mod.rs
@@ -49,7 +49,7 @@ use crate::errors::{Error, Result};
 use crate::escape::{escape, partial_escape, unescape_with};
 use crate::name::{LocalName, QName};
 use crate::reader::is_whitespace;
-use crate::utils::write_cow_string;
+use crate::utils::{write_cow_string, CowRef};
 use attributes::{Attribute, Attributes};
 use std::mem::replace;
 
@@ -189,6 +189,21 @@ impl<'a> BytesStart<'a> {
         self.name_len = name.len();
         self
     }
+
+    /// Gets the undecoded raw tag name, as present in the input stream, which
+    /// is borrowed either to the input, or to the event.
+    ///
+    /// # Lifetimes
+    ///
+    /// - `'a`: Lifetime of the input data from which this event is borrow
+    /// - `'e`: Lifetime of the concrete event instance
+    // TODO: We should made this is a part of public API, but with safe wrapped for a name
+    pub(crate) fn raw_name<'e>(&'e self) -> CowRef<'a, 'e, [u8]> {
+        match self.buf {
+            Cow::Borrowed(b) => CowRef::Input(&b[..self.name_len]),
+            Cow::Owned(ref o) => CowRef::Slice(&o[..self.name_len]),
+        }
+    }
 }
 
 /// Attribute-related methods
diff --git a/src/utils.rs b/src/utils.rs
index 75f4ced..3381d73 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,5 +1,6 @@
-use std::borrow::Cow;
+use std::borrow::{Borrow, Cow};
 use std::fmt::{self, Debug, Formatter};
+use std::ops::Deref;
 
 #[cfg(feature = "serialize")]
 use serde::de::{Deserialize, Deserializer, Error, Visitor};
@@ -36,6 +37,56 @@ pub fn write_byte_string(f: &mut Formatter, byte_string: &[u8]) -> fmt::Result {
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
+/// A version of [`Cow`] that can borrow from two different buffers, one of them
+/// is a deserializer input.
+///
+/// # Lifetimes
+///
+/// - `'i`: lifetime of the data that deserializer borrow from the parsed input
+/// - `'s`: lifetime of the data that owned by a deserializer
+pub enum CowRef<'i, 's, B>
+where
+    B: ToOwned + ?Sized,
+{
+    /// An input borrowed from the parsed data
+    Input(&'i B),
+    /// An input borrowed from the buffer owned by another deserializer
+    Slice(&'s B),
+    /// An input taken from an external deserializer, owned by that deserializer
+    Owned(<B as ToOwned>::Owned),
+}
+impl<'i, 's, B> Deref for CowRef<'i, 's, B>
+where
+    B: ToOwned + ?Sized,
+    B::Owned: Borrow<B>,
+{
+    type Target = B;
+
+    fn deref(&self) -> &B {
+        match *self {
+            Self::Input(borrowed) => borrowed,
+            Self::Slice(borrowed) => borrowed,
+            Self::Owned(ref owned) => owned.borrow(),
+        }
+    }
+}
+
+impl<'i, 's, B> Debug for CowRef<'i, 's, B>
+where
+    B: ToOwned + ?Sized + Debug,
+    B::Owned: Debug,
+{
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match *self {
+            Self::Input(borrowed) => Debug::fmt(borrowed, f),
+            Self::Slice(borrowed) => Debug::fmt(borrowed, f),
+            Self::Owned(ref owned) => Debug::fmt(owned, f),
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /// Wrapper around `Vec<u8>` that has a human-readable debug representation:
 /// printable ASCII symbols output as is, all other output in HEX notation.
 ///


Test patch:
diff --git a/tests/serde-de.rs b/tests/serde-de.rs
index fa92694..907a2cc 100644
--- a/tests/serde-de.rs
+++ b/tests/serde-de.rs
@@ -6285,6 +6285,9 @@ fn from_str_should_ignore_encoding() {
 /// Checks that deserializer is able to borrow data from the input
 mod borrow {
     use super::*;
+    use pretty_assertions::assert_eq;
+    use std::collections::BTreeMap;
+    use std::iter::FromIterator;
 
     /// Struct that should borrow input to be able to deserialize successfully.
     /// serde implicitly borrow `&str` and `&[u8]` even without `#[serde(borrow)]`
@@ -6403,4 +6406,24 @@ mod borrow {
             }
         }
     }
+
+    #[test]
+    fn element_name() {
+        let data: BTreeMap<&str, &str> = from_str(
+            r#"
+            <root>
+                <element>element content</element>
+                text content
+            </root>"#,
+        )
+        .unwrap();
+        assert_eq!(
+            data,
+            BTreeMap::from_iter([
+                // Comment to prevent formatting in one line
+                ("element", "element content"),
+                ("$text", "text content"),
+            ])
+        );
+    }
 }
diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs
index 1d33b16..62c3850 100644
--- a/tests/serde-issues.rs
+++ b/tests/serde-issues.rs
@@ -142,6 +142,23 @@ fn issue349() {
     );
 }
 
+/// Regression test for https://github.com/tafia/quick-xml/issues/352.
+#[test]
+fn issue352() {
+    use std::borrow::Cow;
+
+    #[derive(Deserialize)]
+    struct Root<'a> {
+        #[serde(borrow)]
+        #[serde(rename = "@attribute")]
+        attribute: Cow<'a, str>,
+    }
+
+    let r: Root = from_str("<Root attribute='borrowed value'></Root>").unwrap();
+
+    assert!(matches!(r.attribute, Cow::Borrowed(_)));
+}
+
 /// Regression test for https://github.com/tafia/quick-xml/issues/429.
 #[test]
 fn issue429() {