xml_disassembler/parsers/
parse_unique_id.rs1use serde_json::Value;
4use sha2::{Digest, Sha256};
5
6use crate::types::XmlElement;
7
8fn create_short_hash(element: &XmlElement) -> String {
11 let stringified = serde_json::to_string(element).unwrap_or_default();
12 let mut hasher = Sha256::new();
13 hasher.update(stringified.as_bytes());
14 let result = hasher.finalize();
15 const HEX: &[u8; 16] = b"0123456789abcdef";
16 let mut s = String::with_capacity(8);
17 for b in result.iter().take(4) {
18 s.push(HEX[(b >> 4) as usize] as char);
19 s.push(HEX[(b & 0xf) as usize] as char);
20 }
21 s
22}
23
24fn is_object(value: &Value) -> bool {
25 value.is_object() && !value.is_array()
26}
27
28fn value_as_string(value: &Value) -> Option<String> {
30 if let Some(s) = value.as_str() {
31 return Some(s.to_string());
32 }
33 if let Some(obj) = value.as_object() {
34 if let Some(text) = obj.get("#text").and_then(|v| v.as_str()) {
35 return Some(text.to_string());
36 }
37 }
38 None
39}
40
41fn find_direct_field_match(element: &XmlElement, field_names: &[&str]) -> Option<String> {
42 let obj = element.as_object()?;
43 for name in field_names {
44 if let Some(value) = obj.get(*name) {
45 if let Some(s) = value_as_string(value) {
46 return Some(s);
47 }
48 }
49 }
50 None
51}
52
53fn find_nested_field_match(element: &XmlElement, unique_id_elements: &str) -> Option<String> {
54 let obj = element.as_object()?;
55 for (_, child) in obj {
56 if is_object(child) {
57 let result = parse_unique_id_element(child, Some(unique_id_elements));
58 if !result.is_empty() {
59 return Some(result);
60 }
61 }
62 }
63 None
64}
65
66pub fn parse_unique_id_element(element: &XmlElement, unique_id_elements: Option<&str>) -> String {
68 if let Some(ids) = unique_id_elements {
69 let field_names: Vec<&str> = ids.split(',').map(|s| s.trim()).collect();
70 find_direct_field_match(element, &field_names)
71 .or_else(|| find_nested_field_match(element, ids))
72 .unwrap_or_else(|| create_short_hash(element))
73 } else {
74 create_short_hash(element)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use serde_json::json;
82
83 #[test]
84 fn finds_direct_field() {
85 let el = json!({ "name": "Get_Info", "label": "Get Info" });
86 assert_eq!(parse_unique_id_element(&el, Some("name")), "Get_Info");
87 }
88
89 #[test]
90 fn finds_deeply_nested_field() {
91 let el = json!({
93 "value": { "elementReference": "accts.accounts" },
94 "connector": { "targetReference": "X" }
95 });
96 assert_eq!(
97 parse_unique_id_element(&el, Some("elementReference")),
98 "accts.accounts"
99 );
100 }
101
102 #[test]
103 fn finds_id_in_grandchild() {
104 let el = json!({
105 "wrapper": {
106 "inner": { "name": "NestedName" }
107 }
108 });
109 assert_eq!(parse_unique_id_element(&el, Some("name")), "NestedName");
110 }
111
112 #[test]
113 fn finds_name_from_text_object() {
114 let el = json!({
116 "name": { "#text": "Get_Info" },
117 "label": { "#text": "Get Info" },
118 "actionName": { "#text": "GetFirstFromCollection" }
119 });
120 assert_eq!(parse_unique_id_element(&el, Some("name")), "Get_Info");
121 assert_eq!(
122 parse_unique_id_element(&el, Some("actionName")),
123 "GetFirstFromCollection"
124 );
125 }
126}