1use serde_json::Value;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum MappingOverrideMode {
8 Merge,
10 Replace,
12}
13
14const VALID_TOP_LEVEL_KEYS: &[&str] = &["commands", "qualifiers"];
15
16const VALID_QUALIFIER_SUB_KEYS: &[&str] = &[
17 "request_key_map",
18 "request_key_value_map",
19 "request_value_map",
20 "response_key_map",
21 "response_value_map",
22];
23
24pub fn validate_mapping_overrides(overrides: &Value) -> Result<(), String> {
30 let obj = overrides
31 .as_object()
32 .ok_or("mapping_overrides must be a JSON object")?;
33 for key in obj.keys() {
34 if !VALID_TOP_LEVEL_KEYS.contains(&key.as_str()) {
35 return Err(format!(
36 "Invalid top-level key in mapping_overrides: {key:?} (expected subset of {VALID_TOP_LEVEL_KEYS:?})"
37 ));
38 }
39 }
40 validate_commands_section(obj.get("commands"))?;
41 validate_qualifiers_section(obj.get("qualifiers"))?;
42 Ok(())
43}
44
45fn validate_commands_section(commands: Option<&Value>) -> Result<(), String> {
46 let Some(commands) = commands else {
47 return Ok(());
48 };
49 let obj = commands
50 .as_object()
51 .ok_or("mapping_overrides['commands'] must be an object")?;
52 for (key, entry) in obj {
53 if !entry.is_object() {
54 return Err(format!(
55 "mapping_overrides['commands'][{key:?}] must be an object"
56 ));
57 }
58 }
59 Ok(())
60}
61
62fn validate_qualifiers_section(qualifiers: Option<&Value>) -> Result<(), String> {
63 let Some(qualifiers) = qualifiers else {
64 return Ok(());
65 };
66 let obj = qualifiers
67 .as_object()
68 .ok_or("mapping_overrides['qualifiers'] must be an object")?;
69 for (key, entry) in obj {
70 let entry_obj = entry
71 .as_object()
72 .ok_or_else(|| format!("mapping_overrides['qualifiers'][{key:?}] must be an object"))?;
73 validate_qualifier_entry(key, entry_obj)?;
74 }
75 Ok(())
76}
77
78fn validate_qualifier_entry(
79 qualifier_key: &str,
80 entry: &serde_json::Map<String, Value>,
81) -> Result<(), String> {
82 for sub_key in entry.keys() {
83 if !VALID_QUALIFIER_SUB_KEYS.contains(&sub_key.as_str()) {
84 return Err(format!(
85 "Invalid sub-key {sub_key:?} in mapping_overrides['qualifiers'][{qualifier_key:?}] \
86 (expected subset of {VALID_QUALIFIER_SUB_KEYS:?})"
87 ));
88 }
89 if !entry[sub_key].is_object() {
90 return Err(format!(
91 "mapping_overrides['qualifiers'][{qualifier_key:?}][{sub_key:?}] must be an object"
92 ));
93 }
94 }
95 Ok(())
96}
97
98#[must_use]
100pub fn merge_mapping_data(base: &Value, overrides: &Value) -> Value {
101 let mut merged = base.clone();
102 merge_commands(&mut merged, overrides.get("commands"));
103 merge_qualifiers(&mut merged, overrides.get("qualifiers"));
104 merged
105}
106
107fn merge_commands(merged: &mut Value, override_commands: Option<&Value>) {
108 let Some(override_obj) = override_commands.and_then(Value::as_object) else {
109 return;
110 };
111 let base_commands = merged
112 .as_object_mut()
113 .unwrap()
114 .entry("commands")
115 .or_insert_with(|| Value::Object(serde_json::Map::new()));
116 let base_obj = base_commands.as_object_mut().unwrap();
117 for (key, entry) in override_obj {
118 if let Some(entry_obj) = entry.as_object() {
119 if let Some(existing) = base_obj.get_mut(key).and_then(Value::as_object_mut) {
120 for (entry_key, entry_value) in entry_obj {
121 existing.insert(entry_key.clone(), entry_value.clone());
122 }
123 } else {
124 base_obj.insert(key.clone(), entry.clone());
125 }
126 }
127 }
128}
129
130fn merge_qualifiers(merged: &mut Value, override_qualifiers: Option<&Value>) {
131 let Some(override_obj) = override_qualifiers.and_then(Value::as_object) else {
132 return;
133 };
134 let base_qualifiers = merged
135 .as_object_mut()
136 .unwrap()
137 .entry("qualifiers")
138 .or_insert_with(|| Value::Object(serde_json::Map::new()));
139 let base_obj = base_qualifiers.as_object_mut().unwrap();
140 for (key, entry) in override_obj {
141 if let Some(entry_obj) = entry.as_object() {
142 if let Some(existing) = base_obj.get_mut(key).and_then(Value::as_object_mut) {
143 for (sub_key, sub_value) in entry_obj {
144 if let Some(sub_obj) = sub_value.as_object() {
145 if let Some(existing_sub) =
146 existing.get_mut(sub_key).and_then(Value::as_object_mut)
147 {
148 for (key, value) in sub_obj {
149 existing_sub.insert(key.clone(), value.clone());
150 }
151 } else {
152 existing.insert(sub_key.clone(), sub_value.clone());
153 }
154 }
155 }
156 } else {
157 base_obj.insert(key.clone(), entry.clone());
158 }
159 }
160 }
161}
162
163pub fn validate_mapping_overrides_complete(base: &Value, overrides: &Value) -> Result<(), String> {
170 let mut missing_parts = Vec::new();
171
172 if let Some(base_commands) = base.get("commands").and_then(Value::as_object) {
173 let override_commands = overrides
174 .get("commands")
175 .and_then(Value::as_object)
176 .cloned()
177 .unwrap_or_default();
178 let mut missing: Vec<&str> = base_commands
179 .keys()
180 .filter(|k| !override_commands.contains_key(k.as_str()))
181 .map(String::as_str)
182 .collect();
183 missing.sort_unstable();
184 for key in missing {
185 missing_parts.push(format!("commands: {key}"));
186 }
187 }
188
189 if let Some(base_qualifiers) = base.get("qualifiers").and_then(Value::as_object) {
190 let override_qualifiers = overrides
191 .get("qualifiers")
192 .and_then(Value::as_object)
193 .cloned()
194 .unwrap_or_default();
195 let mut missing: Vec<&str> = base_qualifiers
196 .keys()
197 .filter(|k| !override_qualifiers.contains_key(k.as_str()))
198 .map(String::as_str)
199 .collect();
200 missing.sort_unstable();
201 for key in missing {
202 missing_parts.push(format!("qualifiers: {key}"));
203 }
204 }
205
206 if !missing_parts.is_empty() {
207 let detail: Vec<String> = missing_parts.iter().map(|e| format!(" {e}")).collect();
208 return Err(format!(
209 "mapping_overrides is incomplete for REPLACE mode. Missing entries:\n{}",
210 detail.join("\n")
211 ));
212 }
213 Ok(())
214}
215
216#[must_use]
218pub fn replace_mapping_data(overrides: &Value) -> Value {
219 overrides.clone()
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use serde_json::json;
226
227 #[test]
230 fn validate_valid_overrides() {
231 let v = json!({"commands": {}, "qualifiers": {}});
232 assert!(validate_mapping_overrides(&v).is_ok());
233 }
234
235 #[test]
236 fn validate_not_object() {
237 let v = json!("string");
238 assert!(validate_mapping_overrides(&v).is_err());
239 }
240
241 #[test]
242 fn validate_invalid_top_key() {
243 let v = json!({"bogus": {}});
244 let err = validate_mapping_overrides(&v).unwrap_err();
245 assert!(err.contains("Invalid top-level key"));
246 }
247
248 #[test]
249 fn validate_commands_not_object() {
250 let v = json!({"commands": "bad"});
251 let err = validate_mapping_overrides(&v).unwrap_err();
252 assert!(err.contains("commands"));
253 }
254
255 #[test]
256 fn validate_commands_entry_not_object() {
257 let v = json!({"commands": {"DISPLAY QUEUE": "bad"}});
258 let err = validate_mapping_overrides(&v).unwrap_err();
259 assert!(err.contains("DISPLAY QUEUE"));
260 }
261
262 #[test]
263 fn validate_qualifiers_not_object() {
264 let v = json!({"qualifiers": "bad"});
265 let err = validate_mapping_overrides(&v).unwrap_err();
266 assert!(err.contains("qualifiers"));
267 }
268
269 #[test]
270 fn validate_qualifier_entry_not_object() {
271 let v = json!({"qualifiers": {"queue": "bad"}});
272 let err = validate_mapping_overrides(&v).unwrap_err();
273 assert!(err.contains("queue"));
274 }
275
276 #[test]
277 fn validate_qualifier_invalid_sub_key() {
278 let v = json!({"qualifiers": {"queue": {"bogus_key": {}}}});
279 let err = validate_mapping_overrides(&v).unwrap_err();
280 assert!(err.contains("bogus_key"));
281 }
282
283 #[test]
284 fn validate_qualifier_sub_value_not_object() {
285 let v = json!({"qualifiers": {"queue": {"request_key_map": "bad"}}});
286 let err = validate_mapping_overrides(&v).unwrap_err();
287 assert!(err.contains("request_key_map"));
288 }
289
290 #[test]
293 fn merge_new_command() {
294 let base = json!({"commands": {"A": {"q": "x"}}, "qualifiers": {}});
295 let overrides = json!({"commands": {"B": {"q": "y"}}});
296 let merged = merge_mapping_data(&base, &overrides);
297 assert!(merged["commands"]["A"].is_object());
298 assert!(merged["commands"]["B"].is_object());
299 }
300
301 #[test]
302 fn merge_existing_command() {
303 let base = json!({"commands": {"A": {"old": "1"}}, "qualifiers": {}});
304 let overrides = json!({"commands": {"A": {"new": "2"}}});
305 let merged = merge_mapping_data(&base, &overrides);
306 assert_eq!(merged["commands"]["A"]["old"], "1");
307 assert_eq!(merged["commands"]["A"]["new"], "2");
308 }
309
310 #[test]
311 fn merge_new_qualifier() {
312 let base = json!({"commands": {}, "qualifiers": {"q1": {"request_key_map": {"a": "A"}}}});
313 let overrides = json!({"qualifiers": {"q2": {"request_key_map": {"b": "B"}}}});
314 let merged = merge_mapping_data(&base, &overrides);
315 assert!(merged["qualifiers"]["q1"].is_object());
316 assert!(merged["qualifiers"]["q2"].is_object());
317 }
318
319 #[test]
320 fn merge_existing_qualifier_nested() {
321 let base = json!({"commands": {}, "qualifiers": {"q1": {"request_key_map": {"a": "A"}}}});
322 let overrides = json!({"qualifiers": {"q1": {"request_key_map": {"b": "B"}}}});
323 let merged = merge_mapping_data(&base, &overrides);
324 assert_eq!(merged["qualifiers"]["q1"]["request_key_map"]["a"], "A");
325 assert_eq!(merged["qualifiers"]["q1"]["request_key_map"]["b"], "B");
326 }
327
328 #[test]
329 fn merge_no_commands_override() {
330 let base = json!({"commands": {"A": {}}, "qualifiers": {}});
331 let overrides = json!({"qualifiers": {}});
332 let merged = merge_mapping_data(&base, &overrides);
333 assert!(merged["commands"]["A"].is_object());
334 }
335
336 #[test]
337 fn merge_no_qualifiers_override() {
338 let base = json!({"commands": {}, "qualifiers": {"q1": {}}});
339 let overrides = json!({"commands": {}});
340 let merged = merge_mapping_data(&base, &overrides);
341 assert!(merged["qualifiers"]["q1"].is_object());
342 }
343
344 #[test]
347 fn validate_complete_ok() {
348 let base = json!({"commands": {"A": {}}, "qualifiers": {"q1": {}}});
349 let overrides = json!({"commands": {"A": {}}, "qualifiers": {"q1": {}}});
350 assert!(validate_mapping_overrides_complete(&base, &overrides).is_ok());
351 }
352
353 #[test]
354 fn validate_complete_missing_commands() {
355 let base = json!({"commands": {"A": {}, "B": {}}, "qualifiers": {}});
356 let overrides = json!({"commands": {"A": {}}, "qualifiers": {}});
357 let err = validate_mapping_overrides_complete(&base, &overrides).unwrap_err();
358 assert!(err.contains("commands: B"));
359 }
360
361 #[test]
362 fn validate_complete_missing_qualifiers() {
363 let base = json!({"commands": {}, "qualifiers": {"q1": {}, "q2": {}}});
364 let overrides = json!({"commands": {}, "qualifiers": {"q1": {}}});
365 let err = validate_mapping_overrides_complete(&base, &overrides).unwrap_err();
366 assert!(err.contains("qualifiers: q2"));
367 }
368
369 #[test]
370 fn validate_complete_missing_both() {
371 let base = json!({"commands": {"A": {}}, "qualifiers": {"q1": {}}});
372 let overrides = json!({"commands": {}, "qualifiers": {}});
373 let err = validate_mapping_overrides_complete(&base, &overrides).unwrap_err();
374 assert!(err.contains("commands: A"));
375 assert!(err.contains("qualifiers: q1"));
376 }
377
378 #[test]
379 fn validate_qualifier_with_valid_sub_keys() {
380 let v = json!({
381 "qualifiers": {
382 "queue": {
383 "request_key_map": {"a": "A"},
384 "response_key_map": {"B": "b"}
385 }
386 }
387 });
388 assert!(validate_mapping_overrides(&v).is_ok());
389 }
390
391 #[test]
392 fn validate_commands_with_valid_entries() {
393 let v = json!({
394 "commands": {
395 "DISPLAY QUEUE": {"qualifier": "queue"},
396 "ALTER QMGR": {"qualifier": "qmgr"}
397 }
398 });
399 assert!(validate_mapping_overrides(&v).is_ok());
400 }
401
402 #[test]
403 fn validate_only_commands_key() {
404 let v = json!({"commands": {}});
405 assert!(validate_mapping_overrides(&v).is_ok());
406 }
407
408 #[test]
409 fn validate_only_qualifiers_key() {
410 let v = json!({"qualifiers": {}});
411 assert!(validate_mapping_overrides(&v).is_ok());
412 }
413
414 #[test]
415 fn validate_empty_object() {
416 let v = json!({});
417 assert!(validate_mapping_overrides(&v).is_ok());
418 }
419
420 #[test]
421 fn merge_qualifier_new_sub_key() {
422 let base = json!({
423 "commands": {},
424 "qualifiers": {
425 "q1": {
426 "request_key_map": {"a": "A"}
427 }
428 }
429 });
430 let overrides = json!({
431 "qualifiers": {
432 "q1": {
433 "response_key_map": {"B": "b"}
434 }
435 }
436 });
437 let merged = merge_mapping_data(&base, &overrides);
438 assert_eq!(merged["qualifiers"]["q1"]["request_key_map"]["a"], "A");
439 assert_eq!(merged["qualifiers"]["q1"]["response_key_map"]["B"], "b");
440 }
441
442 #[test]
443 fn merge_qualifier_non_object_sub_value_ignored() {
444 let base = json!({"commands": {}, "qualifiers": {"q1": {"request_key_map": {"a": "A"}}}});
445 let overrides = json!({"qualifiers": {"q1": {"request_key_map": "not_object"}}});
446 let merged = merge_mapping_data(&base, &overrides);
447 assert_eq!(merged["qualifiers"]["q1"]["request_key_map"]["a"], "A");
449 }
450
451 #[test]
452 fn merge_command_non_object_entry_ignored() {
453 let base = json!({"commands": {"A": {"old": "1"}}, "qualifiers": {}});
454 let overrides = json!({"commands": {"A": "not_object"}});
455 let merged = merge_mapping_data(&base, &overrides);
456 assert_eq!(merged["commands"]["A"]["old"], "1");
457 }
458
459 #[test]
460 fn merge_qualifier_non_object_entry_ignored() {
461 let base = json!({"commands": {}, "qualifiers": {"q1": {"request_key_map": {"a": "A"}}}});
462 let overrides = json!({"qualifiers": {"q1": "not_object"}});
463 let merged = merge_mapping_data(&base, &overrides);
464 assert_eq!(merged["qualifiers"]["q1"]["request_key_map"]["a"], "A");
465 }
466
467 #[test]
468 fn validate_complete_no_commands_in_base() {
469 let base = json!({"qualifiers": {"q1": {}}});
470 let overrides = json!({"qualifiers": {"q1": {}}});
471 assert!(validate_mapping_overrides_complete(&base, &overrides).is_ok());
472 }
473
474 #[test]
475 fn validate_complete_no_qualifiers_in_base() {
476 let base = json!({"commands": {"A": {}}});
477 let overrides = json!({"commands": {"A": {}}});
478 assert!(validate_mapping_overrides_complete(&base, &overrides).is_ok());
479 }
480
481 #[test]
484 fn replace_returns_clone() {
485 let overrides = json!({"commands": {"X": {}}, "qualifiers": {}});
486 let result = replace_mapping_data(&overrides);
487 assert_eq!(result, overrides);
488 }
489
490 #[test]
491 fn merge_into_base_missing_commands_key() {
492 let base = json!({"qualifiers": {}});
493 let overrides = json!({"commands": {"NEW_CMD": {"key": "value"}}});
494 let result = merge_mapping_data(&base, &overrides);
495 assert!(result.get("commands").unwrap().get("NEW_CMD").is_some());
496 }
497
498 #[test]
499 fn merge_into_base_missing_qualifiers_key() {
500 let base = json!({"commands": {}});
501 let overrides = json!({"qualifiers": {"NEW_QUAL": {"sub": {"k": "v"}}}});
502 let result = merge_mapping_data(&base, &overrides);
503 assert!(result.get("qualifiers").unwrap().get("NEW_QUAL").is_some());
504 }
505}