1use std::collections::HashMap;
4
5use serde_json::Value;
6
7use crate::error::{MqRestError, Result};
8use crate::session::MqRestSession;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EnsureAction {
13 Created,
15 Updated,
17 Unchanged,
19}
20
21#[derive(Debug, Clone)]
23pub struct EnsureResult {
24 pub action: EnsureAction,
26 pub changed: Vec<String>,
28}
29
30impl MqRestSession {
31 pub fn ensure_qmgr(
37 &mut self,
38 request_parameters: Option<&HashMap<String, Value>>,
39 ) -> Result<EnsureResult> {
40 let params: HashMap<String, Value> = request_parameters.cloned().unwrap_or_default();
41 if params.is_empty() {
42 return Ok(EnsureResult {
43 action: EnsureAction::Unchanged,
44 changed: Vec::new(),
45 });
46 }
47
48 let all_params: &[&str] = &["all"];
49 let current_objects =
50 self.mqsc_command("DISPLAY", "QMGR", None, None, Some(all_params), None)?;
51
52 let current = current_objects.into_iter().next().unwrap_or_default();
53 let mut changed: HashMap<String, Value> = HashMap::new();
54 for (key, desired_value) in ¶ms {
55 let current_value = current.get(key);
56 if !values_match(desired_value, current_value) {
57 changed.insert(key.clone(), desired_value.clone());
58 }
59 }
60
61 if changed.is_empty() {
62 return Ok(EnsureResult {
63 action: EnsureAction::Unchanged,
64 changed: Vec::new(),
65 });
66 }
67
68 self.mqsc_command("ALTER", "QMGR", None, Some(&changed), None, None)?;
69 Ok(EnsureResult {
70 action: EnsureAction::Updated,
71 changed: changed.keys().cloned().collect(),
72 })
73 }
74
75 pub fn ensure_qlocal(
81 &mut self,
82 name: &str,
83 request_parameters: Option<&HashMap<String, Value>>,
84 ) -> Result<EnsureResult> {
85 self.ensure_object(name, request_parameters, "QUEUE", "QLOCAL", "QLOCAL")
86 }
87
88 pub fn ensure_qremote(
94 &mut self,
95 name: &str,
96 request_parameters: Option<&HashMap<String, Value>>,
97 ) -> Result<EnsureResult> {
98 self.ensure_object(name, request_parameters, "QUEUE", "QREMOTE", "QREMOTE")
99 }
100
101 pub fn ensure_qalias(
107 &mut self,
108 name: &str,
109 request_parameters: Option<&HashMap<String, Value>>,
110 ) -> Result<EnsureResult> {
111 self.ensure_object(name, request_parameters, "QUEUE", "QALIAS", "QALIAS")
112 }
113
114 pub fn ensure_qmodel(
120 &mut self,
121 name: &str,
122 request_parameters: Option<&HashMap<String, Value>>,
123 ) -> Result<EnsureResult> {
124 self.ensure_object(name, request_parameters, "QUEUE", "QMODEL", "QMODEL")
125 }
126
127 pub fn ensure_channel(
133 &mut self,
134 name: &str,
135 request_parameters: Option<&HashMap<String, Value>>,
136 ) -> Result<EnsureResult> {
137 self.ensure_object(name, request_parameters, "CHANNEL", "CHANNEL", "CHANNEL")
138 }
139
140 pub fn ensure_authinfo(
146 &mut self,
147 name: &str,
148 request_parameters: Option<&HashMap<String, Value>>,
149 ) -> Result<EnsureResult> {
150 self.ensure_object(name, request_parameters, "AUTHINFO", "AUTHINFO", "AUTHINFO")
151 }
152
153 pub fn ensure_listener(
159 &mut self,
160 name: &str,
161 request_parameters: Option<&HashMap<String, Value>>,
162 ) -> Result<EnsureResult> {
163 self.ensure_object(name, request_parameters, "LISTENER", "LISTENER", "LISTENER")
164 }
165
166 pub fn ensure_namelist(
172 &mut self,
173 name: &str,
174 request_parameters: Option<&HashMap<String, Value>>,
175 ) -> Result<EnsureResult> {
176 self.ensure_object(name, request_parameters, "NAMELIST", "NAMELIST", "NAMELIST")
177 }
178
179 pub fn ensure_process(
185 &mut self,
186 name: &str,
187 request_parameters: Option<&HashMap<String, Value>>,
188 ) -> Result<EnsureResult> {
189 self.ensure_object(name, request_parameters, "PROCESS", "PROCESS", "PROCESS")
190 }
191
192 pub fn ensure_service(
198 &mut self,
199 name: &str,
200 request_parameters: Option<&HashMap<String, Value>>,
201 ) -> Result<EnsureResult> {
202 self.ensure_object(name, request_parameters, "SERVICE", "SERVICE", "SERVICE")
203 }
204
205 pub fn ensure_topic(
211 &mut self,
212 name: &str,
213 request_parameters: Option<&HashMap<String, Value>>,
214 ) -> Result<EnsureResult> {
215 self.ensure_object(name, request_parameters, "TOPIC", "TOPIC", "TOPIC")
216 }
217
218 pub fn ensure_sub(
224 &mut self,
225 name: &str,
226 request_parameters: Option<&HashMap<String, Value>>,
227 ) -> Result<EnsureResult> {
228 self.ensure_object(name, request_parameters, "SUB", "SUB", "SUB")
229 }
230
231 pub fn ensure_stgclass(
237 &mut self,
238 name: &str,
239 request_parameters: Option<&HashMap<String, Value>>,
240 ) -> Result<EnsureResult> {
241 self.ensure_object(name, request_parameters, "STGCLASS", "STGCLASS", "STGCLASS")
242 }
243
244 pub fn ensure_comminfo(
250 &mut self,
251 name: &str,
252 request_parameters: Option<&HashMap<String, Value>>,
253 ) -> Result<EnsureResult> {
254 self.ensure_object(name, request_parameters, "COMMINFO", "COMMINFO", "COMMINFO")
255 }
256
257 pub fn ensure_cfstruct(
263 &mut self,
264 name: &str,
265 request_parameters: Option<&HashMap<String, Value>>,
266 ) -> Result<EnsureResult> {
267 self.ensure_object(name, request_parameters, "CFSTRUCT", "CFSTRUCT", "CFSTRUCT")
268 }
269
270 fn ensure_object(
271 &mut self,
272 name: &str,
273 request_parameters: Option<&HashMap<String, Value>>,
274 display_qualifier: &str,
275 define_qualifier: &str,
276 alter_qualifier: &str,
277 ) -> Result<EnsureResult> {
278 let all_params: &[&str] = &["all"];
279 let current_objects = match self.mqsc_command(
280 "DISPLAY",
281 display_qualifier,
282 Some(name),
283 None,
284 Some(all_params),
285 None,
286 ) {
287 Ok(objects) => objects,
288 Err(MqRestError::Command { .. }) => Vec::new(),
289 Err(e) => return Err(e),
290 };
291
292 let params: HashMap<String, Value> = request_parameters.cloned().unwrap_or_default();
293
294 if current_objects.is_empty() {
295 let define_params = if params.is_empty() {
296 None
297 } else {
298 Some(¶ms)
299 };
300 self.mqsc_command(
301 "DEFINE",
302 define_qualifier,
303 Some(name),
304 define_params,
305 None,
306 None,
307 )?;
308 return Ok(EnsureResult {
309 action: EnsureAction::Created,
310 changed: Vec::new(),
311 });
312 }
313
314 if params.is_empty() {
315 return Ok(EnsureResult {
316 action: EnsureAction::Unchanged,
317 changed: Vec::new(),
318 });
319 }
320
321 let current = ¤t_objects[0];
322 let mut changed: HashMap<String, Value> = HashMap::new();
323 for (key, desired_value) in ¶ms {
324 let current_value = current.get(key);
325 if !values_match(desired_value, current_value) {
326 changed.insert(key.clone(), desired_value.clone());
327 }
328 }
329
330 if changed.is_empty() {
331 return Ok(EnsureResult {
332 action: EnsureAction::Unchanged,
333 changed: Vec::new(),
334 });
335 }
336
337 self.mqsc_command(
338 "ALTER",
339 alter_qualifier,
340 Some(name),
341 Some(&changed),
342 None,
343 None,
344 )?;
345 Ok(EnsureResult {
346 action: EnsureAction::Updated,
347 changed: changed.keys().cloned().collect(),
348 })
349 }
350}
351
352fn values_match(desired: &Value, current: Option<&Value>) -> bool {
353 let Some(current) = current else {
354 return false;
355 };
356 let desired_str = value_to_string(desired);
357 let current_str = value_to_string(current);
358 desired_str.trim().eq_ignore_ascii_case(current_str.trim())
359}
360
361fn value_to_string(value: &Value) -> String {
362 match value {
363 Value::String(s) => s.clone(),
364 Value::Number(n) => n.to_string(),
365 Value::Bool(b) => b.to_string(),
366 Value::Null => String::new(),
367 other => other.to_string(),
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::test_helpers::{
375 MockTransport, command_error_response, empty_success_response, mock_session,
376 success_response,
377 };
378 use serde_json::json;
379
380 #[test]
383 fn ensure_qmgr_empty_params_unchanged() {
384 let transport = MockTransport::new(vec![]);
385 let mut session = mock_session(transport);
386 let result = session.ensure_qmgr(None).unwrap();
387 assert_eq!(result.action, EnsureAction::Unchanged);
388 }
389
390 #[test]
391 fn ensure_qmgr_matching_params_unchanged() {
392 let mut current = HashMap::new();
393 current.insert("DESCR".into(), json!("test"));
394 let transport = MockTransport::new(vec![success_response(vec![current])]);
395 let mut session = mock_session(transport);
396 let mut params = HashMap::new();
397 params.insert("DESCR".into(), json!("test"));
398 let result = session.ensure_qmgr(Some(¶ms)).unwrap();
399 assert_eq!(result.action, EnsureAction::Unchanged);
400 }
401
402 #[test]
403 fn ensure_qmgr_differing_params_updated() {
404 let mut current = HashMap::new();
405 current.insert("DESCR".into(), json!("old"));
406 let transport = MockTransport::new(vec![
407 success_response(vec![current]),
408 empty_success_response(),
409 ]);
410 let mut session = mock_session(transport);
411 let mut params = HashMap::new();
412 params.insert("DESCR".into(), json!("new"));
413 let result = session.ensure_qmgr(Some(¶ms)).unwrap();
414 assert_eq!(result.action, EnsureAction::Updated);
415 assert!(result.changed.contains(&"DESCR".to_owned()));
416 }
417
418 #[test]
421 fn ensure_qlocal_not_found_created() {
422 let transport =
423 MockTransport::new(vec![command_error_response(), empty_success_response()]);
424 let mut session = mock_session(transport);
425 let result = session.ensure_qlocal("MY.Q", None).unwrap();
426 assert_eq!(result.action, EnsureAction::Created);
427 }
428
429 #[test]
430 fn ensure_qlocal_exists_unchanged() {
431 let mut current = HashMap::new();
432 current.insert("DESCR".into(), json!("test"));
433 let transport = MockTransport::new(vec![success_response(vec![current])]);
434 let mut session = mock_session(transport);
435 let mut params = HashMap::new();
436 params.insert("DESCR".into(), json!("test"));
437 let result = session.ensure_qlocal("MY.Q", Some(¶ms)).unwrap();
438 assert_eq!(result.action, EnsureAction::Unchanged);
439 }
440
441 #[test]
442 fn ensure_qlocal_exists_updated() {
443 let mut current = HashMap::new();
444 current.insert("DESCR".into(), json!("old"));
445 let transport = MockTransport::new(vec![
446 success_response(vec![current]),
447 empty_success_response(),
448 ]);
449 let mut session = mock_session(transport);
450 let mut params = HashMap::new();
451 params.insert("DESCR".into(), json!("new"));
452 let result = session.ensure_qlocal("MY.Q", Some(¶ms)).unwrap();
453 assert_eq!(result.action, EnsureAction::Updated);
454 }
455
456 #[test]
457 fn ensure_qlocal_empty_params_not_found_created() {
458 let transport =
459 MockTransport::new(vec![command_error_response(), empty_success_response()]);
460 let mut session = mock_session(transport);
461 let result = session.ensure_qlocal("MY.Q", None).unwrap();
462 assert_eq!(result.action, EnsureAction::Created);
463 }
464
465 #[test]
466 fn ensure_qlocal_empty_params_exists_unchanged() {
467 let current = HashMap::new();
468 let transport = MockTransport::new(vec![success_response(vec![current])]);
469 let mut session = mock_session(transport);
470 let result = session.ensure_qlocal("MY.Q", None).unwrap();
471 assert_eq!(result.action, EnsureAction::Unchanged);
472 }
473
474 #[test]
475 fn ensure_qlocal_non_command_error_propagated() {
476 let transport = MockTransport::new(vec![]);
477 let mut session = mock_session(transport);
478 let result = session.ensure_qlocal("MY.Q", None);
479 assert!(result.is_err());
480 }
481
482 #[test]
485 fn values_match_case_insensitive() {
486 assert!(values_match(&json!("YES"), Some(&json!("yes"))));
487 }
488
489 #[test]
490 fn values_match_trimmed() {
491 assert!(values_match(&json!("YES"), Some(&json!(" YES "))));
492 }
493
494 #[test]
495 fn values_match_no_match() {
496 assert!(!values_match(&json!("YES"), Some(&json!("NO"))));
497 }
498
499 #[test]
500 fn values_match_none_current() {
501 assert!(!values_match(&json!("YES"), None));
502 }
503
504 #[test]
507 fn value_to_string_string() {
508 assert_eq!(value_to_string(&json!("hello")), "hello");
509 }
510
511 #[test]
512 fn value_to_string_number() {
513 assert_eq!(value_to_string(&json!(42)), "42");
514 }
515
516 #[test]
517 fn value_to_string_bool() {
518 assert_eq!(value_to_string(&json!(true)), "true");
519 }
520
521 #[test]
522 fn value_to_string_null() {
523 assert_eq!(value_to_string(&json!(null)), "");
524 }
525
526 #[test]
527 fn value_to_string_other() {
528 let val = json!({"key": "val"});
529 let result = value_to_string(&val);
530 assert!(result.contains("key"));
531 }
532
533 macro_rules! test_ensure_created {
536 ($method:ident) => {
537 paste::paste! {
538 #[test]
539 fn [<test_ $method _created>]() {
540 let transport = MockTransport::new(vec![
541 command_error_response(),
542 empty_success_response(),
543 ]);
544 let mut session = mock_session(transport);
545 let result = session.$method("OBJ", None).unwrap();
546 assert_eq!(result.action, EnsureAction::Created);
547 }
548 }
549 };
550 }
551
552 test_ensure_created!(ensure_qlocal);
553 test_ensure_created!(ensure_qremote);
554 test_ensure_created!(ensure_qalias);
555 test_ensure_created!(ensure_qmodel);
556 test_ensure_created!(ensure_channel);
557 test_ensure_created!(ensure_authinfo);
558 test_ensure_created!(ensure_listener);
559 test_ensure_created!(ensure_namelist);
560 test_ensure_created!(ensure_process);
561 test_ensure_created!(ensure_service);
562 test_ensure_created!(ensure_topic);
563 test_ensure_created!(ensure_sub);
564 test_ensure_created!(ensure_stgclass);
565 test_ensure_created!(ensure_comminfo);
566 test_ensure_created!(ensure_cfstruct);
567
568 #[test]
569 fn ensure_qlocal_not_found_with_params_created() {
570 let mut params = HashMap::new();
571 params.insert("DESCR".into(), json!("my queue"));
572 let transport =
573 MockTransport::new(vec![command_error_response(), empty_success_response()]);
574 let mut session = mock_session(transport);
575 let result = session.ensure_qlocal("MY.Q", Some(¶ms)).unwrap();
576 assert_eq!(result.action, EnsureAction::Created);
577 }
578
579 #[test]
580 fn ensure_qmgr_display_transport_error() {
581 let transport = MockTransport::new(vec![]);
582 let mut session = mock_session(transport);
583 let mut params = HashMap::new();
584 params.insert("DESCR".into(), json!("new"));
585 let result = session.ensure_qmgr(Some(¶ms));
586 assert!(result.is_err());
587 }
588
589 #[test]
590 fn ensure_qmgr_alter_transport_error() {
591 let mut current = HashMap::new();
592 current.insert("DESCR".into(), json!("old"));
593 let transport = MockTransport::new(vec![
594 success_response(vec![current]),
595 ]);
597 let mut session = mock_session(transport);
598 let mut params = HashMap::new();
599 params.insert("DESCR".into(), json!("new"));
600 let result = session.ensure_qmgr(Some(¶ms));
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn ensure_qlocal_define_fails() {
606 let transport = MockTransport::new(vec![
608 command_error_response(),
609 ]);
611 let mut session = mock_session(transport);
612 let result = session.ensure_qlocal("MY.Q", None);
613 assert!(result.is_err());
614 }
615
616 #[test]
617 fn ensure_qlocal_alter_fails() {
618 let mut current = HashMap::new();
620 current.insert("DESCR".into(), json!("old"));
621 let transport = MockTransport::new(vec![
622 success_response(vec![current]),
623 ]);
625 let mut session = mock_session(transport);
626 let mut params = HashMap::new();
627 params.insert("DESCR".into(), json!("new"));
628 let result = session.ensure_qlocal("MY.Q", Some(¶ms));
629 assert!(result.is_err());
630 }
631}