1use std::collections::hash_map::DefaultHasher;
4use std::collections::HashMap;
5use std::fmt::{Display, Formatter};
6use std::hash::{Hash, Hasher};
7use std::panic::RefUnwindSafe;
8use std::sync::{Arc, Mutex};
9
10use anyhow::anyhow;
11use itertools::Itertools;
12use serde_json::{json, Map, Value};
13use tracing::warn;
14
15use crate::bodies::OptionalBody;
16use crate::content_types::ContentType;
17use crate::interaction::Interaction;
18use crate::json_utils::{is_empty, json_to_string};
19use crate::matchingrules::MatchingRules;
20use crate::message::Message;
21use crate::provider_states::ProviderState;
22use crate::sync_interaction::RequestResponseInteraction;
23use crate::v4::async_message::AsynchronousMessage;
24use crate::v4::interaction::{InteractionMarkup, parse_plugin_config, V4Interaction};
25use crate::v4::message_parts::MessageContents;
26use crate::v4::synch_http::SynchronousHttp;
27use crate::v4::V4InteractionType;
28
29#[derive(Debug, Clone, Eq)]
31pub struct SynchronousMessage {
32 pub id: Option<String>,
34 pub key: Option<String>,
36 pub description: String,
38 pub provider_states: Vec<ProviderState>,
41 pub comments: HashMap<String, Value>,
43 pub request: MessageContents,
45 pub response: Vec<MessageContents>,
47
48 pub pending: bool,
50
51 pub plugin_config: HashMap<String, HashMap<String, Value>>,
53
54 pub interaction_markup: InteractionMarkup,
56
57 pub transport: Option<String>
59}
60
61impl SynchronousMessage {
62 fn calc_hash(&self) -> String {
63 let mut s = DefaultHasher::new();
64 self.hash(&mut s);
65 format!("{:x}", s.finish())
66 }
67
68 pub fn with_key(&self) -> SynchronousMessage {
70 SynchronousMessage {
71 key: Some(self.calc_hash()),
72 .. self.clone()
73 }
74 }
75
76 pub fn from_json(json: &Value, index: usize) -> anyhow::Result<SynchronousMessage> {
78 if json.is_object() {
79 let id = json.get("_id").map(|id| json_to_string(id));
80 let key = json.get("key").map(|id| json_to_string(id));
81 let description = match json.get("description") {
82 Some(v) => match *v {
83 Value::String(ref s) => s.clone(),
84 _ => v.to_string()
85 },
86 None => format!("Interaction {}", index)
87 };
88
89 let comments = match json.get("comments") {
90 Some(v) => match v {
91 Value::Object(map) => map.iter()
92 .map(|(k, v)| (k.clone(), v.clone())).collect(),
93 _ => {
94 warn!("Interaction comments must be a JSON Object, but received {}. Ignoring", v);
95 Default::default()
96 }
97 },
98 None => Default::default()
99 };
100
101 let provider_states = ProviderState::from_json(json);
102 let request = json.get("request")
103 .ok_or_else(|| anyhow!("JSON for SynchronousMessages does not contain a 'request' object"))?;
104 let response = json.get("response")
105 .ok_or_else(|| anyhow!("JSON for SynchronousMessages does not contain a 'response' array"))?
106 .as_array()
107 .ok_or_else(|| anyhow!("JSON for SynchronousMessages does not contain a 'response' array"))?;
108 let responses =
109 response.iter()
110 .map(|message| MessageContents::from_json(message))
111 .collect::<Vec<anyhow::Result<MessageContents>>>();
112
113 let plugin_config = parse_plugin_config(json);
114 let interaction_markup = json.get("interactionMarkup")
115 .map(|markup| InteractionMarkup::from_json(markup)).unwrap_or_default();
116
117 let transport = json.get("transport").map(|value| {
118 match value {
119 Value::String(s) => s.clone(),
120 _ => value.to_string()
121 }
122 });
123
124 if responses.iter().any(|res| res.is_err()) {
125 let errors = responses.iter()
126 .filter(|res| res.is_err())
127 .map(|res| res.as_ref().unwrap_err().to_string())
128 .join(", ");
129 Err(anyhow!("Failed to parse SynchronousMessages responses - {}", errors))
130 } else {
131 Ok(SynchronousMessage {
132 id,
133 key,
134 description,
135 provider_states,
136 comments,
137 request: MessageContents::from_json(request)?,
138 response: responses.iter().map(|res| res.as_ref().unwrap().clone()).collect(),
139 pending: json.get("pending")
140 .map(|value| value.as_bool().unwrap_or_default()).unwrap_or_default(),
141 plugin_config,
142 interaction_markup,
143 transport
144 })
145 }
146 } else {
147 Err(anyhow!("Expected a JSON object for the interaction, got '{}'", json))
148 }
149 }
150}
151
152impl V4Interaction for SynchronousMessage {
153 fn to_json(&self) -> Value {
154 let mut json = json!({
155 "type": V4InteractionType::Synchronous_Messages.to_string(),
156 "description": self.description.clone(),
157 "pending": self.pending,
158 "request": self.request.to_json(),
159 "response": self.response.iter().map(|m| m.to_json()).collect_vec()
160 });
161 let map = json.as_object_mut().unwrap();
162
163 if let Some(key) = &self.key {
164 map.insert("key".to_string(), Value::String(key.clone()));
165 }
166
167 if !self.provider_states.is_empty() {
168 map.insert("providerStates".to_string(), Value::Array(
169 self.provider_states.iter().map(|p| p.to_json()).collect()));
170 }
171
172 let comments: Map<String, Value> = self.comments.iter()
173 .filter(|(_k, v)| !is_empty(v))
174 .map(|(k, v)| (k.clone(), v.clone()))
175 .collect();
176 if !comments.is_empty() {
177 map.insert("comments".to_string(), Value::Object(comments));
178 }
179
180 if !self.plugin_config.is_empty() {
181 map.insert("pluginConfiguration".to_string(), self.plugin_config.iter()
182 .map(|(k, v)|
183 (k.clone(), Value::Object(v.iter().map(|(k, v)| (k.clone(), v.clone())).collect()))
184 ).collect());
185 }
186
187 if !self.interaction_markup.is_empty() {
188 map.insert("interactionMarkup".to_string(), self.interaction_markup.to_json());
189 }
190
191 if let Some(transport) = &self.transport {
192 map.insert("transport".to_string(), Value::String(transport.clone()));
193 }
194
195 json
196 }
197
198 fn to_super(&self) -> &(dyn Interaction + Send + Sync + RefUnwindSafe) {
199 self
200 }
201
202 fn to_super_mut(&mut self) -> &mut (dyn Interaction + Send + Sync) {
203 self
204 }
205
206 fn key(&self) -> Option<String> {
207 self.key.clone()
208 }
209
210 fn boxed_v4(&self) -> Box<dyn V4Interaction + Send + Sync + RefUnwindSafe> {
211 Box::new(self.clone())
212 }
213
214 fn comments(&self) -> HashMap<String, Value> {
215 self.comments.clone()
216 }
217
218 fn comments_mut(&mut self) -> &mut HashMap<String, Value> {
219 &mut self.comments
220 }
221
222 fn v4_type(&self) -> V4InteractionType {
223 V4InteractionType::Synchronous_Messages
224 }
225
226 fn plugin_config(&self) -> HashMap<String, HashMap<String, Value>> {
227 self.plugin_config.clone()
228 }
229
230 fn plugin_config_mut(&mut self) -> &mut HashMap<String, HashMap<String, Value>> {
231 &mut self.plugin_config
232 }
233
234 fn interaction_markup(&self) -> InteractionMarkup {
235 self.interaction_markup.clone()
236 }
237
238 fn interaction_markup_mut(&mut self) -> &mut InteractionMarkup {
239 &mut self.interaction_markup
240 }
241
242 fn transport(&self) -> Option<String> {
243 self.transport.clone()
244 }
245
246 fn set_transport(&mut self, transport: Option<String>) {
247 self.transport = transport.clone();
248 }
249
250 fn with_unique_key(&self) -> Box<dyn V4Interaction + Send + Sync + RefUnwindSafe> {
251 Box::new(self.with_key())
252 }
253
254 fn unique_key(&self) -> String {
255 match &self.key {
256 None => self.calc_hash(),
257 Some(key) => key.clone()
258 }
259 }
260}
261
262impl Interaction for SynchronousMessage {
263 fn type_of(&self) -> String {
264 format!("V4 {}", self.v4_type())
265 }
266
267 fn is_request_response(&self) -> bool {
268 false
269 }
270
271 fn as_request_response(&self) -> Option<RequestResponseInteraction> {
272 None
273 }
274
275 fn is_message(&self) -> bool {
276 false
277 }
278
279 fn as_message(&self) -> Option<Message> {
280 None
281 }
282
283 fn id(&self) -> Option<String> {
284 self.id.clone()
285 }
286
287 fn description(&self) -> String {
288 self.description.clone()
289 }
290
291 fn set_id(&mut self, id: Option<String>) {
292 self.id = id;
293 }
294
295 fn set_description(&mut self, description: &str) {
296 self.description = description.to_string();
297 }
298
299 fn provider_states(&self) -> Vec<ProviderState> {
300 self.provider_states.clone()
301 }
302
303 fn provider_states_mut(&mut self) -> &mut Vec<ProviderState> {
304 &mut self.provider_states
305 }
306
307 fn contents(&self) -> OptionalBody {
308 OptionalBody::Missing
309 }
310
311 fn contents_for_verification(&self) -> OptionalBody {
312 self.response.first().map(|message| message.contents.clone()).unwrap_or_default()
313 }
314
315 fn content_type(&self) -> Option<ContentType> {
316 self.request.message_content_type()
317 }
318
319 fn is_v4(&self) -> bool {
320 true
321 }
322
323 fn as_v4(&self) -> Option<Box<dyn V4Interaction + Send + Sync + RefUnwindSafe>> {
324 Some(self.boxed_v4())
325 }
326
327 fn as_v4_mut(&mut self) -> Option<&mut dyn V4Interaction> {
328 Some(self)
329 }
330
331 fn as_v4_http(&self) -> Option<SynchronousHttp> {
332 None
333 }
334
335 fn as_v4_async_message(&self) -> Option<AsynchronousMessage> {
336 None
337 }
338
339 fn as_v4_sync_message(&self) -> Option<SynchronousMessage> {
340 Some(self.clone())
341 }
342
343 fn as_v4_http_mut(&mut self) -> Option<&mut SynchronousHttp> {
344 None
345 }
346
347 fn is_v4_sync_message(&self) -> bool {
348 true
349 }
350
351 fn as_v4_async_message_mut(&mut self) -> Option<&mut AsynchronousMessage> {
352 None
353 }
354
355 fn as_v4_sync_message_mut(&mut self) -> Option<&mut SynchronousMessage> {
356 Some(self)
357 }
358
359 fn boxed(&self) -> Box<dyn Interaction + Send + Sync + RefUnwindSafe> {
360 Box::new(self.clone())
361 }
362
363 fn arced(&self) -> Arc<dyn Interaction + Send + Sync + RefUnwindSafe> {
364 Arc::new(self.clone())
365 }
366
367 fn thread_safe(&self) -> Arc<Mutex<dyn Interaction + Send + Sync + RefUnwindSafe>> {
368 Arc::new(Mutex::new(self.clone()))
369 }
370
371 fn matching_rules(&self) -> Option<MatchingRules> {
372 None
373 }
374
375 fn pending(&self) -> bool {
376 self.pending
377 }
378}
379
380impl Default for SynchronousMessage {
381 fn default() -> Self {
382 SynchronousMessage {
383 id: None,
384 key: None,
385 description: "Synchronous/Message Interaction".to_string(),
386 provider_states: vec![],
387 comments: Default::default(),
388 request: Default::default(),
389 response: Default::default(),
390 pending: false,
391 plugin_config: Default::default(),
392 interaction_markup: Default::default(),
393 transport: None
394 }
395 }
396}
397
398impl PartialEq for SynchronousMessage {
399 fn eq(&self, other: &Self) -> bool {
400 self.key == other.key &&
401 self.description == other.description &&
402 self.provider_states == other.provider_states &&
403 self.request == other.request &&
404 self.response == other.response &&
405 self.pending == other.pending
406 }
407}
408
409impl Hash for SynchronousMessage {
410 fn hash<H: Hasher>(&self, state: &mut H) {
411 self.description.hash(state);
412 self.provider_states.hash(state);
413 self.request.hash(state);
414 self.response.hash(state);
415 self.pending.hash(state);
416 }
417}
418
419impl Display for SynchronousMessage {
420 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
421 let pending = if self.pending { " [PENDING]" } else { "" };
422 write!(f, "V4 Synchronous Message Interaction{} ( id: {:?}, description: \"{}\", provider_states: {:?}, request: {}, response: {:?} )",
423 pending, self.id, self.description, self.provider_states, self.request, self.response)
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use expectest::prelude::*;
430 use maplit::hashmap;
431 use pretty_assertions::{assert_eq, assert_ne};
432 use serde_json::{json, Value};
433
434 use crate::bodies::OptionalBody;
435 use crate::prelude::ProviderState;
436 use crate::v4::interaction::V4Interaction;
437 use crate::v4::message_parts::MessageContents;
438 use crate::v4::sync_message::SynchronousMessage;
439
440 #[test]
441 fn calculate_hash_test() {
442 let interaction = SynchronousMessage::from_json(&json!({
443 "description": "a retrieve Mallory request",
444 "pending": false,
445 "providerStates": [
446 {
447 "name": "there is some good mallory"
448 }
449 ],
450 "request": {
451 "contents": {
452 "content": "Mallory",
453 "contentType": "text/plain",
454 "encoded": false
455 },
456 "metadata": {
457 "Content-Type": [
458 "application/json"
459 ]
460 }
461 },
462 "response": [
463 {
464 "contents": {
465 "content": "That is some good Mallory.",
466 "contentType": "text/plain",
467 "encoded": false
468 },
469 "metadata": {
470 "Content-Type": [
471 "text/plain"
472 ]
473 }
474 }
475 ],
476 "type": "Synchronous/Messages"
477 }), 0).unwrap();
478 let hash = interaction.calc_hash();
479 expect!(interaction.calc_hash()).to(be_equal_to(hash.as_str()));
480
481 let interaction2 = interaction.with_key();
482 expect!(interaction2.key.as_ref().unwrap()).to(be_equal_to(hash.as_str()));
483
484 let json = interaction2.to_json();
485 assert_eq!(json!({
486 "description": "a retrieve Mallory request",
487 "key": "93f58446f133592f",
488 "pending": false,
489 "providerStates": [
490 {
491 "name": "there is some good mallory"
492 }
493 ],
494 "request": {
495 "contents": {
496 "content": "Mallory",
497 "contentType": "text/plain",
498 "encoded": false
499 },
500 "metadata": {
501 "Content-Type": [
502 "application/json"
503 ]
504 }
505 },
506 "response": [{
507 "contents": {
508 "content": "That is some good Mallory.",
509 "contentType": "text/plain",
510 "encoded": false
511 },
512 "metadata": {
513 "Content-Type": [
514 "text/plain"
515 ]
516 }
517 }],
518 "type": "Synchronous/Messages"
519 }), json);
520 }
521
522 #[test]
523 fn hash_test() {
524 let i1 = SynchronousMessage::default();
525 expect!(i1.calc_hash()).to(be_equal_to("2c18fa761d06be45"));
526
527 let i2 = SynchronousMessage {
528 description: "a retrieve Mallory request".to_string(),
529 .. SynchronousMessage::default()
530 };
531 expect!(i2.calc_hash()).to(be_equal_to("66fbdb308329891b"));
532
533 let i3 = SynchronousMessage {
534 description: "a retrieve Mallory request".to_string(),
535 provider_states: vec![ProviderState::default("there is some good mallory")],
536 .. SynchronousMessage::default()
537 };
538 expect!(i3.calc_hash()).to(be_equal_to("831a3fa6d0a7ea0c"));
539
540 let i4 = SynchronousMessage {
541 description: "a retrieve Mallory request".to_string(),
542 provider_states: vec![ProviderState::default("there is some good mallory")],
543 request: MessageContents {
544 contents: OptionalBody::from("That is some good Mallory."),
545 .. MessageContents::default()
546 },
547 .. SynchronousMessage::default()
548 };
549 expect!(i4.calc_hash()).to(be_equal_to("25420754ce64549d"));
550
551 let i5 = SynchronousMessage {
552 description: "a retrieve Mallory request".to_string(),
553 provider_states: vec![ProviderState::default("there is some good mallory")],
554 request: MessageContents {
555 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
556 contents: OptionalBody::from("That is some good Mallory."),
557 .. MessageContents::default()
558 },
559 .. SynchronousMessage::default()
560 };
561 expect!(i5.calc_hash()).to(be_equal_to("aefc777fdfa238b0"));
562
563 let i6 = SynchronousMessage {
564 description: "a retrieve Mallory request".to_string(),
565 provider_states: vec![ProviderState::default("there is some good mallory")],
566 request: MessageContents {
567 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
568 contents: OptionalBody::from("That is some good Mallory."),
569 .. MessageContents::default()
570 },
571 response: vec![MessageContents {
572 metadata: hashmap!{ "Content-Type".to_string() => Value::String("text/plain".to_string()) },
573 contents: OptionalBody::from("That is some good Mallory."),
574 .. MessageContents::default()
575 }],
576 .. SynchronousMessage::default()
577 };
578 expect!(i6.calc_hash()).to(be_equal_to("9338c66e694d3d80"));
579 }
580
581 #[test]
582 fn equals_test() {
583 let i1 = SynchronousMessage::default();
584 let i2 = SynchronousMessage {
585 description: "a retrieve Mallory request".to_string(),
586 .. SynchronousMessage::default()
587 };
588 let i3 = SynchronousMessage {
589 description: "a retrieve Mallory request".to_string(),
590 provider_states: vec![ProviderState::default("there is some good mallory")],
591 .. SynchronousMessage::default()
592 };
593 let i4 = SynchronousMessage {
594 description: "a retrieve Mallory request".to_string(),
595 provider_states: vec![ProviderState::default("there is some good mallory")],
596 request: MessageContents {
597 contents: OptionalBody::from("That is some good Mallory."),
598 .. MessageContents::default()
599 },
600 .. SynchronousMessage::default()
601 };
602 let i5 = SynchronousMessage {
603 description: "a retrieve Mallory request".to_string(),
604 provider_states: vec![ProviderState::default("there is some good mallory")],
605 request: MessageContents {
606 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
607 contents: OptionalBody::from("That is some good Mallory."),
608 .. MessageContents::default()
609 },
610 .. SynchronousMessage::default()
611 };
612 let i6 = SynchronousMessage {
613 description: "a retrieve Mallory request".to_string(),
614 provider_states: vec![ProviderState::default("there is some good mallory")],
615 request: MessageContents {
616 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
617 contents: OptionalBody::from("That is some good Mallory."),
618 .. MessageContents::default()
619 },
620 response: vec![MessageContents {
621 metadata: hashmap!{ "Content-Type".to_string() => Value::String("text/plain".to_string()) },
622 contents: OptionalBody::from("That is some good Mallory."),
623 .. MessageContents::default()
624 }],
625 .. SynchronousMessage::default()
626 };
627
628 assert_eq!(i1, i1);
629 assert_eq!(i2, i2);
630 assert_eq!(i3, i3);
631 assert_eq!(i4, i4);
632 assert_eq!(i5, i5);
633 assert_eq!(i6, i6);
634
635 assert_ne!(i1, i2);
636 assert_ne!(i1, i3);
637 assert_ne!(i1, i4);
638 assert_ne!(i1, i5);
639 assert_ne!(i1, i6);
640 assert_ne!(i2, i1);
641 assert_ne!(i2, i3);
642 assert_ne!(i2, i4);
643 assert_ne!(i2, i5);
644 assert_ne!(i2, i6);
645 }
646
647 #[test]
648 fn equals_test_with_different_keys() {
649 let i1 = SynchronousMessage {
650 key: Some("i1".to_string()),
651 description: "a retrieve Mallory request".to_string(),
652 provider_states: vec![ProviderState::default("there is some good mallory")],
653 request: MessageContents {
654 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
655 contents: OptionalBody::from("That is some good Mallory."),
656 .. MessageContents::default()
657 },
658 response: vec![MessageContents {
659 metadata: hashmap!{ "Content-Type".to_string() => Value::String("text/plain".to_string()) },
660 contents: OptionalBody::from("That is some good Mallory."),
661 .. MessageContents::default()
662 }],
663 .. SynchronousMessage::default()
664 };
665 let i2 = SynchronousMessage {
666 key: Some("i2".to_string()),
667 description: "a retrieve Mallory request".to_string(),
668 provider_states: vec![ProviderState::default("there is some good mallory")],
669 request: MessageContents {
670 metadata: hashmap!{ "Content-Type".to_string() => Value::String("application/json".to_string()) },
671 contents: OptionalBody::from("That is some good Mallory."),
672 .. MessageContents::default()
673 },
674 response: vec![MessageContents {
675 metadata: hashmap!{ "Content-Type".to_string() => Value::String("text/plain".to_string()) },
676 contents: OptionalBody::from("That is some good Mallory."),
677 .. MessageContents::default()
678 }],
679 .. SynchronousMessage::default()
680 };
681
682 assert_eq!(i1, i1);
683 assert_eq!(i2, i2);
684
685 assert_ne!(i1, i2);
686 assert_ne!(i2, i1);
687 }
688}