Skip to main content

mq_rest_admin/
mapping.rs

1//! Runtime attribute mapping for MQSC <-> `snake_case` translations.
2#![allow(clippy::implicit_hasher)]
3
4use std::collections::HashMap;
5
6use serde_json::Value;
7
8use crate::error::{MappingError, MappingIssue};
9use crate::mapping_data::MAPPING_DATA;
10
11/// Map request attributes from `snake_case` into MQSC parameter names.
12///
13/// # Errors
14///
15/// Returns a `MappingError` in strict mode when an attribute name or value
16/// cannot be mapped.
17pub fn map_request_attributes(
18    qualifier: &str,
19    attributes: &HashMap<String, Value>,
20    strict: bool,
21    mapping_data: Option<&Value>,
22) -> std::result::Result<HashMap<String, Value>, MappingError> {
23    let data = mapping_data.unwrap_or(&MAPPING_DATA);
24    let Some(qualifier_data) = get_qualifier_data(qualifier, data) else {
25        return handle_unknown_qualifier(qualifier, attributes, "request", strict);
26    };
27    let key_map = get_string_map(qualifier_data, "request_key_map");
28    let key_value_map = get_key_value_map(qualifier_data, "request_key_value_map");
29    let value_map = get_nested_string_map(qualifier_data, "request_value_map");
30    map_attributes(
31        qualifier,
32        attributes,
33        &key_map,
34        &key_value_map,
35        &value_map,
36        "request",
37        strict,
38    )
39}
40
41/// Map response attributes from MQSC parameter names to `snake_case`.
42///
43/// # Errors
44///
45/// Returns a `MappingError` in strict mode when an attribute name or value
46/// cannot be mapped.
47pub fn map_response_attributes(
48    qualifier: &str,
49    attributes: &HashMap<String, Value>,
50    strict: bool,
51    mapping_data: Option<&Value>,
52) -> std::result::Result<HashMap<String, Value>, MappingError> {
53    let data = mapping_data.unwrap_or(&MAPPING_DATA);
54    let Some(qualifier_data) = get_qualifier_data(qualifier, data) else {
55        return handle_unknown_qualifier(qualifier, attributes, "response", strict);
56    };
57    let key_map = get_string_map(qualifier_data, "response_key_map");
58    let value_map = get_nested_string_map(qualifier_data, "response_value_map");
59    let empty_key_value_map = HashMap::new();
60    map_attributes(
61        qualifier,
62        attributes,
63        &key_map,
64        &empty_key_value_map,
65        &value_map,
66        "response",
67        strict,
68    )
69}
70
71/// Map a list of response objects from MQSC names to `snake_case`.
72///
73/// # Errors
74///
75/// Returns a `MappingError` in strict mode when an attribute name or value
76/// cannot be mapped.
77pub fn map_response_list(
78    qualifier: &str,
79    objects: &[HashMap<String, Value>],
80    strict: bool,
81    mapping_data: Option<&Value>,
82) -> std::result::Result<Vec<HashMap<String, Value>>, MappingError> {
83    let data = mapping_data.unwrap_or(&MAPPING_DATA);
84    let Some(qualifier_data) = get_qualifier_data(qualifier, data) else {
85        return handle_unknown_qualifier_list(qualifier, objects, "response", strict);
86    };
87    let key_map = get_string_map(qualifier_data, "response_key_map");
88    let value_map = get_nested_string_map(qualifier_data, "response_value_map");
89    let empty_key_value_map = HashMap::new();
90    let mut mapped_objects = Vec::with_capacity(objects.len());
91    let mut issues = Vec::new();
92    for (object_index, attributes) in objects.iter().enumerate() {
93        let (mapped, attr_issues) = map_attributes_internal(
94            qualifier,
95            attributes,
96            &key_map,
97            &empty_key_value_map,
98            &value_map,
99            "response",
100            Some(object_index),
101        );
102        mapped_objects.push(mapped);
103        issues.extend(attr_issues);
104    }
105    if strict && !issues.is_empty() {
106        return Err(MappingError::new(issues));
107    }
108    Ok(mapped_objects)
109}
110
111fn get_qualifier_data<'a>(qualifier: &str, data: &'a Value) -> Option<&'a Value> {
112    data.get("qualifiers").and_then(|q| q.get(qualifier))
113}
114
115fn get_string_map(qualifier_data: &Value, map_name: &str) -> HashMap<String, String> {
116    let mut result = HashMap::new();
117    if let Some(obj) = qualifier_data.get(map_name).and_then(Value::as_object) {
118        for (key, value) in obj {
119            if let Some(s) = value.as_str() {
120                result.insert(key.clone(), s.to_owned());
121            }
122        }
123    }
124    result
125}
126
127fn get_nested_string_map(
128    qualifier_data: &Value,
129    map_name: &str,
130) -> HashMap<String, HashMap<String, String>> {
131    let mut result = HashMap::new();
132    if let Some(obj) = qualifier_data.get(map_name).and_then(Value::as_object) {
133        for (key, value) in obj {
134            if let Some(inner) = value.as_object() {
135                let mut inner_map = HashMap::new();
136                for (inner_key, inner_value) in inner {
137                    if let Some(s) = inner_value.as_str() {
138                        inner_map.insert(inner_key.clone(), s.to_owned());
139                    }
140                }
141                result.insert(key.clone(), inner_map);
142            }
143        }
144    }
145    result
146}
147
148type KeyValueMap = HashMap<String, HashMap<String, HashMap<String, String>>>;
149
150fn get_key_value_map(qualifier_data: &Value, map_name: &str) -> KeyValueMap {
151    let mut result = HashMap::new();
152    if let Some(obj) = qualifier_data.get(map_name).and_then(Value::as_object) {
153        for (key, value) in obj {
154            if let Some(inner) = value.as_object() {
155                let mut inner_map = HashMap::new();
156                for (inner_key, inner_value) in inner {
157                    if let Some(nested) = inner_value.as_object() {
158                        let mut nested_map = HashMap::new();
159                        for (nested_key, nested_value) in nested {
160                            if let Some(s) = nested_value.as_str() {
161                                nested_map.insert(nested_key.clone(), s.to_owned());
162                            }
163                        }
164                        inner_map.insert(inner_key.clone(), nested_map);
165                    }
166                }
167                result.insert(key.clone(), inner_map);
168            }
169        }
170    }
171    result
172}
173
174fn handle_unknown_qualifier(
175    qualifier: &str,
176    attributes: &HashMap<String, Value>,
177    direction: &str,
178    strict: bool,
179) -> std::result::Result<HashMap<String, Value>, MappingError> {
180    if !strict {
181        return Ok(attributes.clone());
182    }
183    Err(MappingError::new(vec![MappingIssue {
184        direction: direction.into(),
185        reason: "unknown_qualifier".into(),
186        attribute_name: "*".into(),
187        attribute_value: None,
188        object_index: None,
189        qualifier: Some(qualifier.into()),
190    }]))
191}
192
193fn handle_unknown_qualifier_list(
194    qualifier: &str,
195    objects: &[HashMap<String, Value>],
196    direction: &str,
197    strict: bool,
198) -> std::result::Result<Vec<HashMap<String, Value>>, MappingError> {
199    if !strict {
200        return Ok(objects.to_vec());
201    }
202    Err(MappingError::new(vec![MappingIssue {
203        direction: direction.into(),
204        reason: "unknown_qualifier".into(),
205        attribute_name: "*".into(),
206        attribute_value: None,
207        object_index: None,
208        qualifier: Some(qualifier.into()),
209    }]))
210}
211
212fn map_attributes(
213    qualifier: &str,
214    attributes: &HashMap<String, Value>,
215    key_map: &HashMap<String, String>,
216    key_value_map: &KeyValueMap,
217    value_map: &HashMap<String, HashMap<String, String>>,
218    direction: &str,
219    strict: bool,
220) -> std::result::Result<HashMap<String, Value>, MappingError> {
221    let (mapped, issues) = map_attributes_internal(
222        qualifier,
223        attributes,
224        key_map,
225        key_value_map,
226        value_map,
227        direction,
228        None,
229    );
230    if strict && !issues.is_empty() {
231        return Err(MappingError::new(issues));
232    }
233    Ok(mapped)
234}
235
236fn map_attributes_internal(
237    qualifier: &str,
238    attributes: &HashMap<String, Value>,
239    key_map: &HashMap<String, String>,
240    key_value_map: &KeyValueMap,
241    value_map: &HashMap<String, HashMap<String, String>>,
242    direction: &str,
243    object_index: Option<usize>,
244) -> (HashMap<String, Value>, Vec<MappingIssue>) {
245    let mut mapped = HashMap::new();
246    let mut issues = Vec::new();
247    for (attr_name, attr_value) in attributes {
248        if direction == "request"
249            && let Some(kv_map) = key_value_map.get(attr_name.as_str())
250        {
251            if let Some(str_val) = attr_value.as_str()
252                && let Some(mapping) = kv_map.get(str_val)
253                && let (Some(key), Some(value)) = (mapping.get("key"), mapping.get("value"))
254            {
255                mapped.insert(key.clone(), Value::String(value.clone()));
256                continue;
257            }
258            issues.push(MappingIssue {
259                direction: direction.into(),
260                reason: "unknown_value".into(),
261                attribute_name: attr_name.clone(),
262                attribute_value: Some(attr_value.clone()),
263                object_index,
264                qualifier: Some(qualifier.into()),
265            });
266            mapped.insert(attr_name.clone(), attr_value.clone());
267            continue;
268        }
269        if let Some(mapped_key) = key_map.get(attr_name.as_str()) {
270            let (mapped_value, value_issues) = map_value(
271                qualifier,
272                attr_name,
273                attr_value,
274                value_map,
275                direction,
276                object_index,
277            );
278            mapped.insert(mapped_key.clone(), mapped_value);
279            issues.extend(value_issues);
280        } else {
281            issues.push(MappingIssue {
282                direction: direction.into(),
283                reason: "unknown_key".into(),
284                attribute_name: attr_name.clone(),
285                attribute_value: Some(attr_value.clone()),
286                object_index,
287                qualifier: Some(qualifier.into()),
288            });
289            mapped.insert(attr_name.clone(), attr_value.clone());
290        }
291    }
292    (mapped, issues)
293}
294
295#[allow(clippy::option_if_let_else)]
296fn map_value(
297    qualifier: &str,
298    attribute_name: &str,
299    attribute_value: &Value,
300    value_map: &HashMap<String, HashMap<String, String>>,
301    direction: &str,
302    object_index: Option<usize>,
303) -> (Value, Vec<MappingIssue>) {
304    let Some(value_mappings) = value_map.get(attribute_name) else {
305        return (attribute_value.clone(), Vec::new());
306    };
307    if let Some(str_val) = attribute_value.as_str() {
308        if let Some(mapped) = value_mappings.get(str_val) {
309            (Value::String(mapped.clone()), Vec::new())
310        } else {
311            (
312                attribute_value.clone(),
313                vec![MappingIssue {
314                    direction: direction.into(),
315                    reason: "unknown_value".into(),
316                    attribute_name: attribute_name.into(),
317                    attribute_value: Some(attribute_value.clone()),
318                    object_index,
319                    qualifier: Some(qualifier.into()),
320                }],
321            )
322        }
323    } else if let Some(arr) = attribute_value.as_array() {
324        map_value_list(
325            qualifier,
326            attribute_name,
327            arr,
328            value_mappings,
329            direction,
330            object_index,
331        )
332    } else {
333        (attribute_value.clone(), Vec::new())
334    }
335}
336
337fn map_value_list(
338    qualifier: &str,
339    attribute_name: &str,
340    attribute_values: &[Value],
341    value_mappings: &HashMap<String, String>,
342    direction: &str,
343    object_index: Option<usize>,
344) -> (Value, Vec<MappingIssue>) {
345    let mut mapped_values = Vec::with_capacity(attribute_values.len());
346    let mut issues = Vec::new();
347    for attr_value in attribute_values {
348        if let Some(str_val) = attr_value.as_str() {
349            if let Some(mapped) = value_mappings.get(str_val) {
350                mapped_values.push(Value::String(mapped.clone()));
351            } else {
352                issues.push(MappingIssue {
353                    direction: direction.into(),
354                    reason: "unknown_value".into(),
355                    attribute_name: attribute_name.into(),
356                    attribute_value: Some(attr_value.clone()),
357                    object_index,
358                    qualifier: Some(qualifier.into()),
359                });
360                mapped_values.push(attr_value.clone());
361            }
362        } else {
363            mapped_values.push(attr_value.clone());
364        }
365    }
366    (Value::Array(mapped_values), issues)
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use serde_json::json;
373
374    fn test_mapping_data() -> Value {
375        json!({
376            "qualifiers": {
377                "testq": {
378                    "request_key_map": {
379                        "description": "DESCR",
380                        "max_depth": "MAXDEPTH"
381                    },
382                    "request_value_map": {
383                        "description": {
384                            "default_value": "DEFVAL"
385                        }
386                    },
387                    "request_key_value_map": {
388                        "queue_type": {
389                            "local": {"key": "QTYPE", "value": "QLOCAL"},
390                            "remote": {"key": "QTYPE", "value": "QREMOTE"}
391                        }
392                    },
393                    "response_key_map": {
394                        "DESCR": "description",
395                        "MAXDEPTH": "max_depth"
396                    },
397                    "response_value_map": {
398                        "DESCR": {
399                            "DEFVAL": "default_value"
400                        }
401                    }
402                }
403            }
404        })
405    }
406
407    // ---- map_request_attributes ----
408
409    #[test]
410    fn map_request_key_mapping() {
411        let data = test_mapping_data();
412        let mut attrs = HashMap::new();
413        attrs.insert("description".into(), json!("hello"));
414        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
415        assert_eq!(result.get("DESCR").unwrap(), &json!("hello"));
416    }
417
418    #[test]
419    fn map_request_value_mapping() {
420        let data = test_mapping_data();
421        let mut attrs = HashMap::new();
422        attrs.insert("description".into(), json!("default_value"));
423        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
424        assert_eq!(result.get("DESCR").unwrap(), &json!("DEFVAL"));
425    }
426
427    #[test]
428    fn map_request_key_value_mapping() {
429        let data = test_mapping_data();
430        let mut attrs = HashMap::new();
431        attrs.insert("queue_type".into(), json!("local"));
432        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
433        assert_eq!(result.get("QTYPE").unwrap(), &json!("QLOCAL"));
434    }
435
436    #[test]
437    fn map_request_key_value_unknown_value() {
438        let data = test_mapping_data();
439        let mut attrs = HashMap::new();
440        attrs.insert("queue_type".into(), json!("bogus"));
441        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
442        assert_eq!(result.get("queue_type").unwrap(), &json!("bogus"));
443    }
444
445    #[test]
446    fn map_request_key_value_non_string_value() {
447        let data = test_mapping_data();
448        let mut attrs = HashMap::new();
449        attrs.insert("queue_type".into(), json!(42));
450        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
451        assert_eq!(result.get("queue_type").unwrap(), &json!(42));
452    }
453
454    #[test]
455    fn map_request_unknown_key_non_strict() {
456        let data = test_mapping_data();
457        let mut attrs = HashMap::new();
458        attrs.insert("unknown_attr".into(), json!("val"));
459        let result = map_request_attributes("testq", &attrs, false, Some(&data)).unwrap();
460        assert_eq!(result.get("unknown_attr").unwrap(), &json!("val"));
461    }
462
463    #[test]
464    fn map_request_unknown_key_strict() {
465        let data = test_mapping_data();
466        let mut attrs = HashMap::new();
467        attrs.insert("unknown_attr".into(), json!("val"));
468        let result = map_request_attributes("testq", &attrs, true, Some(&data));
469        assert!(result.is_err());
470    }
471
472    #[test]
473    fn map_request_unknown_qualifier_non_strict() {
474        let data = test_mapping_data();
475        let mut attrs = HashMap::new();
476        attrs.insert("foo".into(), json!("bar"));
477        let result = map_request_attributes("noexist", &attrs, false, Some(&data)).unwrap();
478        assert_eq!(result.get("foo").unwrap(), &json!("bar"));
479    }
480
481    #[test]
482    fn map_request_unknown_qualifier_strict() {
483        let data = test_mapping_data();
484        let attrs = HashMap::new();
485        let result = map_request_attributes("noexist", &attrs, true, Some(&data));
486        assert!(result.is_err());
487    }
488
489    // ---- map_response_attributes ----
490
491    #[test]
492    fn map_response_key_and_value() {
493        let data = test_mapping_data();
494        let mut attrs = HashMap::new();
495        attrs.insert("DESCR".into(), json!("DEFVAL"));
496        let result = map_response_attributes("testq", &attrs, false, Some(&data)).unwrap();
497        assert_eq!(result.get("description").unwrap(), &json!("default_value"));
498    }
499
500    #[test]
501    fn map_response_unknown_value_non_strict() {
502        let data = test_mapping_data();
503        let mut attrs = HashMap::new();
504        attrs.insert("DESCR".into(), json!("NOMATCH"));
505        let result = map_response_attributes("testq", &attrs, false, Some(&data)).unwrap();
506        assert_eq!(result.get("description").unwrap(), &json!("NOMATCH"));
507    }
508
509    #[test]
510    fn map_response_unknown_qualifier_strict() {
511        let data = test_mapping_data();
512        let attrs = HashMap::new();
513        let result = map_response_attributes("noexist", &attrs, true, Some(&data));
514        assert!(result.is_err());
515    }
516
517    // ---- map_response_list ----
518
519    #[test]
520    fn map_response_list_multiple_objects() {
521        let data = test_mapping_data();
522        let obj1 = {
523            let mut m = HashMap::new();
524            m.insert("DESCR".into(), json!("DEFVAL"));
525            m
526        };
527        let obj2 = {
528            let mut m = HashMap::new();
529            m.insert("MAXDEPTH".into(), json!("5000"));
530            m
531        };
532        let result = map_response_list("testq", &[obj1, obj2], false, Some(&data)).unwrap();
533        assert_eq!(result.len(), 2);
534        assert_eq!(
535            result[0].get("description").unwrap(),
536            &json!("default_value")
537        );
538        assert_eq!(result[1].get("max_depth").unwrap(), &json!("5000"));
539    }
540
541    #[test]
542    fn map_response_list_unknown_qualifier_non_strict() {
543        let data = test_mapping_data();
544        let obj = HashMap::new();
545        let result = map_response_list("noexist", &[obj], false, Some(&data)).unwrap();
546        assert_eq!(result.len(), 1);
547    }
548
549    #[test]
550    fn map_response_list_unknown_qualifier_strict() {
551        let data = test_mapping_data();
552        let obj = HashMap::new();
553        let result = map_response_list("noexist", &[obj], true, Some(&data));
554        assert!(result.is_err());
555    }
556
557    #[test]
558    fn map_response_list_strict_with_unknown_keys() {
559        let data = test_mapping_data();
560        let mut obj = HashMap::new();
561        obj.insert("UNKNOWN".into(), json!("val"));
562        let result = map_response_list("testq", &[obj], true, Some(&data));
563        assert!(result.is_err());
564    }
565
566    // ---- Private helper: map_value ----
567
568    #[test]
569    fn map_value_string_hit() {
570        let mut value_map = HashMap::new();
571        let mut inner = HashMap::new();
572        inner.insert("YES".into(), "yes".into());
573        value_map.insert("attr".into(), inner);
574        let (result, issues) = map_value("q", "attr", &json!("YES"), &value_map, "response", None);
575        assert_eq!(result, json!("yes"));
576        assert!(issues.is_empty());
577    }
578
579    #[test]
580    fn map_value_string_miss() {
581        let mut value_map = HashMap::new();
582        let mut inner = HashMap::new();
583        inner.insert("YES".into(), "yes".into());
584        value_map.insert("attr".into(), inner);
585        let (result, issues) = map_value("q", "attr", &json!("NOPE"), &value_map, "response", None);
586        assert_eq!(result, json!("NOPE"));
587        assert_eq!(issues.len(), 1);
588    }
589
590    #[test]
591    fn map_value_array() {
592        let mut value_map = HashMap::new();
593        let mut inner = HashMap::new();
594        inner.insert("A".into(), "a".into());
595        value_map.insert("attr".into(), inner);
596        let arr = json!(["A", "B"]);
597        let (result, issues) = map_value("q", "attr", &arr, &value_map, "response", Some(0));
598        let arr_result = result.as_array().unwrap();
599        assert_eq!(arr_result[0], json!("a"));
600        assert_eq!(arr_result[1], json!("B"));
601        assert_eq!(issues.len(), 1);
602    }
603
604    #[test]
605    fn map_value_non_string_passthrough() {
606        let mut value_map = HashMap::new();
607        value_map.insert("attr".into(), HashMap::new());
608        let (result, issues) = map_value("q", "attr", &json!(42), &value_map, "response", None);
609        assert_eq!(result, json!(42));
610        assert!(issues.is_empty());
611    }
612
613    #[test]
614    fn map_value_no_entry() {
615        let value_map = HashMap::new();
616        let (result, issues) = map_value("q", "attr", &json!("val"), &value_map, "response", None);
617        assert_eq!(result, json!("val"));
618        assert!(issues.is_empty());
619    }
620
621    // ---- map_value_list ----
622
623    #[test]
624    fn map_value_list_mixed_types() {
625        let mut mappings = HashMap::new();
626        mappings.insert("A".into(), "mapped_a".into());
627        let values = vec![json!("A"), json!(123), json!("UNKNOWN")];
628        let (result, issues) = map_value_list("q", "attr", &values, &mappings, "response", None);
629        let arr = result.as_array().unwrap();
630        assert_eq!(arr[0], json!("mapped_a"));
631        assert_eq!(arr[1], json!(123));
632        assert_eq!(arr[2], json!("UNKNOWN"));
633        assert_eq!(issues.len(), 1);
634    }
635
636    // ---- get_string_map / get_nested_string_map / get_key_value_map ----
637
638    #[test]
639    fn get_string_map_missing_field() {
640        let data = json!({});
641        let result = get_string_map(&data, "nonexistent");
642        assert!(result.is_empty());
643    }
644
645    #[test]
646    fn get_string_map_non_string_values_ignored() {
647        let data = json!({"test_map": {"a": "b", "c": 42}});
648        let result = get_string_map(&data, "test_map");
649        assert_eq!(result.len(), 1);
650        assert_eq!(result["a"], "b");
651    }
652
653    #[test]
654    fn get_nested_string_map_missing() {
655        let data = json!({});
656        let result = get_nested_string_map(&data, "nonexistent");
657        assert!(result.is_empty());
658    }
659
660    #[test]
661    fn get_nested_string_map_non_object_inner_ignored() {
662        let data = json!({"vm": {"key": "not_an_object"}});
663        let result = get_nested_string_map(&data, "vm");
664        assert!(result.is_empty());
665    }
666
667    #[test]
668    fn get_key_value_map_missing() {
669        let data = json!({});
670        let result = get_key_value_map(&data, "nonexistent");
671        assert!(result.is_empty());
672    }
673
674    #[test]
675    fn get_key_value_map_valid() {
676        let data = json!({
677            "kvm": {
678                "queue_type": {
679                    "local": {"key": "QTYPE", "value": "QLOCAL"}
680                }
681            }
682        });
683        let result = get_key_value_map(&data, "kvm");
684        assert_eq!(result["queue_type"]["local"]["key"], "QTYPE");
685    }
686
687    #[test]
688    fn get_key_value_map_non_object_inner_ignored() {
689        let data = json!({"kvm": {"key": "string_not_object"}});
690        let result = get_key_value_map(&data, "kvm");
691        assert!(result.is_empty());
692    }
693
694    #[test]
695    fn get_key_value_map_non_object_nested_ignored() {
696        let data = json!({"kvm": {"key": {"inner": "not_object"}}});
697        let result = get_key_value_map(&data, "kvm");
698        assert!(result["key"].is_empty());
699    }
700
701    #[test]
702    fn get_nested_string_map_non_string_inner_value() {
703        let data = json!({"vm": {"key": {"valid": "mapped", "invalid": 42}}});
704        let result = get_nested_string_map(&data, "vm");
705        assert_eq!(result.len(), 1);
706        assert_eq!(result["key"].len(), 1);
707        assert_eq!(result["key"]["valid"], "mapped");
708    }
709
710    #[test]
711    fn get_key_value_map_non_string_nested_value() {
712        let data = json!({"kvm": {"key": {"inner": {"valid": "v", "invalid": 42}}}});
713        let result = get_key_value_map(&data, "kvm");
714        assert_eq!(result["key"]["inner"].len(), 1);
715        assert_eq!(result["key"]["inner"]["valid"], "v");
716    }
717
718    #[test]
719    fn get_qualifier_data_qualifiers_exist_but_qualifier_missing() {
720        let data = json!({"qualifiers": {"other": {}}});
721        assert!(get_qualifier_data("nonexist", &data).is_none());
722    }
723
724    // ---- Uses MAPPING_DATA (integration-level) ----
725
726    #[test]
727    fn map_request_with_bundled_data() {
728        let mut attrs = HashMap::new();
729        attrs.insert("description".into(), json!("test"));
730        let result = map_request_attributes("queue", &attrs, false, None).unwrap();
731        assert!(result.contains_key("DESCR"));
732    }
733
734    #[test]
735    fn map_response_with_bundled_data() {
736        let mut attrs = HashMap::new();
737        attrs.insert("DESCR".into(), json!("test"));
738        let result = map_response_attributes("queue", &attrs, false, None).unwrap();
739        assert!(result.contains_key("description"));
740    }
741}