Repository: tafia/quick-xml
Pull request #702 (2024-01-10 21:36:27)

Exclude namespace from tag name when comparing against field names
Attempting to deserialize the following snippet to the following struct fails with "missing field `$value`" due to `not_in()` depending on the full name of the tag rather than the local name. This makes it impossible (or at least very difficult) to deserialize `xs:choice` values when they have a namespace prefix. Relying on local name addresses this without causing any noticeable complications.

```xml
              <t:FolderId Id="Zm9vYmFyCg==" ChangeKey="AQAAAA=="/>
              <t:ParentFolderId Id="Zm9vCg==" ChangeKey="AQAAAA=="/>
              <t:FolderClass>IPF.Note</t:FolderClass>
              <t:DisplayName>Inbox</t:DisplayName>
              <t:TotalCount>48</t:TotalCount>
              <t:ChildFolderCount>0</t:ChildFolderCount>
              <t:UnreadCount>46</t:UnreadCount>
```

```rs
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct FolderInner {
    pub folder_id: BaseFolderId,
    pub parent_folder_id: Option<ParentFolderId>,
    pub folder_class: Option<String>,
    pub display_name: Option<String>,
    total_count: Option<u32>,
    child_folder_count: Option<u32>,
    unread_count: Option<u32>,
}

#[derive(Debug, Deserialize)]
pub enum BaseFolderId {
    FolderId(FolderId),

    ...
}

#[derive(Debug, Deserialize)]
pub struct FolderId {
    #[serde(rename = "@Id")]
    pub id: String,

    #[serde(rename = "@ChangeKey")]
    pub change_key: Option<String>,
}
```


leftmostcat: This seems to address https://github.com/tafia/quick-xml/issues/683.
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/tafia/quick-xml/pull/702?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`de61fa1`)](https://app.codecov.io/gh/tafia/quick-xml/commit/de61fa1094dbeec0bb144568673ac719d9d359a6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 65.06% compared to head [(`a79f473`)](https://app.codecov.io/gh/tafia/quick-xml/pull/702?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 65.32%.


:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #702      +/-   ##
==========================================
+ Coverage   65.06%   65.32%   +0.25%     
==========================================
  Files          38       38              
  Lines       17954    18127     +173     
==========================================
+ Hits        11682    11841     +159     
- Misses       6272     6286      +14     
```

| [Flag](https://app.codecov.io/gh/tafia/quick-xml/pull/702/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/tafia/quick-xml/pull/702/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | `65.32% <100.00%> (+0.25%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tafia/quick-xml/pull/702?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

Mingun: I would like to:
1. have tests for that, at least this one in `serde-issues.rs`:
   ```rust
   /// Regression test for https://github.com/tafia/quick-xml/issues/683.
   #[test]
   fn issue683() {
         #[derive(Deserialize, Debug, PartialEq)]
         enum ScheduleLocation {
            #[serde(rename = "DT")]
            Destination,
         }

         #[derive(Deserialize, Debug, PartialEq)]
         #[allow(non_snake_case)]
         struct Schedule {
            cancelReason: Option<u32>,
            #[serde(rename = "$value")]
            locations: Vec<ScheduleLocation>,
         }
         let xml = r#"
            <schedule xmlns:ns2="http://www.thalesgroup.com/rtti/PushPort/Schedules/v3">
               <ns2:DT/>
               <ns2:cancelReason>918</ns2:cancelReason>
            </schedule>"#;
         let result = quick_xml::de::from_str::<Schedule>(xml);
         dbg!(&result);
         assert_eq!(
            result.unwrap(),
            Schedule {
               cancelReason: Some(918),
               locations: vec![ScheduleLocation::Destination],
            }
         );
   }
   ```
2. Have any other tests which could be affected by this change (I think, in new `tests/serde-namespaces.rs` file)
3. Have a changelog entry

Patch:
diff --git a/Changelog.md b/Changelog.md
index 666bcf8..9aecbb9 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -40,6 +40,7 @@ to get an offset of the error position. For `SyntaxError`s the range
 - [#684]: Fix incorrect position reported for `Error::IllFormed(DoubleHyphenInComment)`.
 - [#684]: Fix incorrect position reported for `Error::IllFormed(MissingDoctypeName)`.
 - [#704]: Fix empty tags with attributes not being expanded when `expand_empty_elements` is set to true.
+- [#683]: Use local tag name when check tag name against possible names for field.
 
 ### Misc Changes
 
@@ -72,6 +73,7 @@ to get an offset of the error position. For `SyntaxError`s the range
 [#629]: https://github.com/tafia/quick-xml/issues/629
 [#675]: https://github.com/tafia/quick-xml/pull/675
 [#677]: https://github.com/tafia/quick-xml/pull/677
+[#683]: https://github.com/tafia/quick-xml/issues/683
 [#684]: https://github.com/tafia/quick-xml/pull/684
 [#689]: https://github.com/tafia/quick-xml/pull/689
 [#704]: https://github.com/tafia/quick-xml/pull/704
diff --git a/src/de/map.rs b/src/de/map.rs
index 64ae6a6..3989a64 100644
--- a/src/de/map.rs
+++ b/src/de/map.rs
@@ -789,7 +789,7 @@ fn not_in(
     start: &BytesStart,
     decoder: Decoder,
 ) -> Result<bool, DeError> {
-    let tag = decoder.decode(start.name().into_inner())?;
+    let tag = decoder.decode(start.local_name().into_inner())?;
 
     Ok(fields.iter().all(|&field| field != tag.as_ref()))
 }
@@ -1181,6 +1181,8 @@ where
 
 #[test]
 fn test_not_in() {
+    use pretty_assertions::assert_eq;
+
     let tag = BytesStart::new("tag");
 
     assert_eq!(not_in(&[], &tag, Decoder::utf8()).unwrap(), true);
@@ -1192,4 +1194,18 @@ fn test_not_in() {
         not_in(&["some", "tag", "included"], &tag, Decoder::utf8()).unwrap(),
         false
     );
+
+    let tag_ns = BytesStart::new("ns1:tag");
+    assert_eq!(
+        not_in(&["no", "such", "tags"], &tag_ns, Decoder::utf8()).unwrap(),
+        true
+    );
+    assert_eq!(
+        not_in(&["some", "tag", "included"], &tag_ns, Decoder::utf8()).unwrap(),
+        false
+    );
+    assert_eq!(
+        not_in(&["some", "namespace", "ns1:tag"], &tag_ns, Decoder::utf8()).unwrap(),
+        true
+    );
 }


Test patch:
diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs
index a3e95b9..2726e45 100644
--- a/tests/serde-issues.rs
+++ b/tests/serde-issues.rs
@@ -462,3 +462,35 @@ fn issue580() {
         }
     );
 }
+
+/// Regression test for https://github.com/tafia/quick-xml/issues/683.
+#[test]
+fn issue683() {
+    #[derive(Deserialize, Debug, PartialEq)]
+    enum ScheduleLocation {
+        #[serde(rename = "DT")]
+        Destination,
+    }
+
+    #[derive(Deserialize, Debug, PartialEq)]
+    #[allow(non_snake_case)]
+    struct Schedule {
+        cancelReason: Option<u32>,
+        #[serde(rename = "$value")]
+        locations: Vec<ScheduleLocation>,
+    }
+    let xml = r#"
+        <schedule xmlns:ns2="http://www.thalesgroup.com/rtti/PushPort/Schedules/v3">
+            <ns2:DT/>
+            <ns2:cancelReason>918</ns2:cancelReason>
+        </schedule>"#;
+    let result = quick_xml::de::from_str::<Schedule>(xml);
+    dbg!(&result);
+    assert_eq!(
+        result.unwrap(),
+        Schedule {
+            cancelReason: Some(918),
+            locations: vec![ScheduleLocation::Destination],
+        }
+    );
+}