1#![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
11pub 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
41pub 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
71pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}