pact_verifier/
verification_result.rs

1//! Structs for storing and returning the result of the verification execution
2
3use std::collections::HashMap;
4use std::time::Duration;
5
6use itertools::Itertools;
7use serde_json::{json, Map, Value};
8
9use pact_matching::Mismatch;
10
11/// Result of verifying a Pact interaction
12#[derive(Clone, Debug)]
13pub struct VerificationInteractionResult {
14  /// Interaction ID, this will only be set if the Pact was loaded from a Pact broker
15  pub interaction_id: Option<String>,
16  /// Interaction key (this will be set if the Pact is a V4 Pact)
17  pub interaction_key: Option<String>,
18  /// Descriptive text of the verification that was preformed
19  pub description: String,
20  /// Interaction description from the Pact file
21  pub interaction_description: String,
22  /// Result of the verification
23  pub result: Result<(), crate::MismatchResult>,
24  /// If the Pact or interaction is pending
25  pub pending: bool,
26  /// Duration that the verification took
27  pub duration: Duration
28}
29
30/// Result of verifying a Pact
31#[derive(Clone, Debug)]
32pub struct VerificationResult {
33  /// Results that occurred
34  pub results: Vec<VerificationInteractionResult>,
35  /// Output from the verification
36  pub output: Vec<String>
37}
38
39/// Main struct for returning the total verification execution result
40#[derive(Debug, Clone, Default)]
41pub struct VerificationExecutionResult {
42  /// Overall pass/fail result
43  pub result: bool,
44  /// Notices provided by the Pact Broker
45  pub notices: Vec<HashMap<String, String>>,
46  /// Collected standard output
47  pub output: Vec<String>,
48  /// Errors that occurred, but are marked as pending
49  pub pending_errors: Vec<(String, VerificationMismatchResult)>,
50  /// Errors that occurred that are not considered pending
51  pub errors: Vec<(String, VerificationMismatchResult)>,
52  /// Result for each interaction that was verified
53  pub interaction_results: Vec<VerificationInteractionResult>
54}
55
56impl VerificationExecutionResult {
57  /// Create a new VerificationExecutionResult with default values
58  pub fn new() -> Self {
59    VerificationExecutionResult {
60      result: true,
61      notices: vec![],
62      output: vec![],
63      pending_errors: vec![],
64      errors: vec![],
65      interaction_results: vec![],
66    }
67  }
68}
69
70impl Into<Value> for &VerificationExecutionResult {
71  fn into(self) -> Value {
72    json!({
73      "result": self.result,
74      "notices": self.notices.iter().map(|m| Value::Object(
75        m.iter().map(|(k, v)| (k.clone(), Value::String(v.clone()))).collect()
76      )).collect_vec(),
77      "output": self.output,
78      "pendingErrors": self.pending_errors.iter().map(|(e, r)| {
79        let err: Value = r.into();
80        json!({
81          "interaction": e,
82          "mismatch": err
83        })
84      }).collect_vec(),
85      "errors": self.errors.iter().map(|(e, r)| {
86        let err: Value = r.into();
87        json!({
88          "interaction": e,
89          "mismatch": err
90        })
91      }).collect_vec(),
92      "interactionResults": self.interaction_results.iter().map(|r| {
93        let mut attributes = Map::new();
94        if let Some(interaction_id) = &r.interaction_id {
95          attributes.insert("interactionId".to_string(), Value::String(interaction_id.clone()));
96        }
97        if let Some(interaction_key) = &r.interaction_key {
98          attributes.insert("interactionKey".to_string(), Value::String(interaction_key.clone()));
99        }
100        attributes.insert("description".to_string(), Value::String(r.interaction_description.clone()));
101        match r.result {
102          Ok(_) => attributes.insert("result".to_string(), Value::String("OK".to_string())),
103          Err(_) => attributes.insert("result".to_string(), Value::String("Error".to_string()))
104        };
105        attributes.insert("duration".to_string(), Value::String(format!("{:?}", r.duration)));
106        Value::Object(attributes)
107      }).collect_vec()
108    })
109  }
110}
111
112impl Into<Value> for VerificationExecutionResult {
113  fn into(self) -> Value {
114    (&self).into()
115  }
116}
117
118/// Result of performing a match. This is a reduced version of crate::MismatchResult to make
119/// it thread and panic boundary safe
120#[derive(Debug, Clone, PartialEq, PartialOrd)]
121pub enum VerificationMismatchResult {
122  /// Response mismatches
123  Mismatches {
124    /// Mismatches that occurred
125    mismatches: Vec<Mismatch>,
126    /// Interaction ID if fetched from a pact broker
127    interaction_id: Option<String>
128  },
129  /// Error occurred
130  Error {
131    /// Error that occurred
132    error: String,
133    /// Interaction ID if fetched from a pact broker
134    interaction_id: Option<String>
135  }
136}
137
138impl From<&crate::MismatchResult> for VerificationMismatchResult {
139  fn from(result: &crate::MismatchResult) -> Self {
140    match result {
141      crate::MismatchResult::Mismatches { mismatches, interaction_id, .. } => {
142        VerificationMismatchResult::Mismatches {
143          mismatches: mismatches.clone(),
144          interaction_id: interaction_id.clone()
145        }
146      }
147      crate::MismatchResult::Error(error, interaction_id) => {
148        VerificationMismatchResult::Error {
149          error: error.clone(),
150          interaction_id: interaction_id.clone()
151        }
152      }
153    }
154  }
155}
156
157impl Into<Value> for &VerificationMismatchResult {
158  fn into(self) -> Value {
159    match self {
160      VerificationMismatchResult::Mismatches { mismatches, interaction_id } => {
161        json!({
162          "type": "mismatches",
163          "mismatches": mismatches.iter().map(|i| i.to_json()).collect_vec(),
164          "interactionId": interaction_id.clone().unwrap_or_default()
165        })
166      }
167      VerificationMismatchResult::Error { error, interaction_id } => {
168        json!({
169          "type": "error",
170          "message": error,
171          "interactionId": interaction_id.clone().unwrap_or_default()
172        })
173      }
174    }
175  }
176}
177
178impl Into<Value> for VerificationMismatchResult {
179  fn into(self) -> Value {
180    (&self).into()
181  }
182}
183
184#[cfg(test)]
185mod tests {
186  use expectest::prelude::*;
187  use maplit::hashmap;
188  use pretty_assertions::assert_eq;
189  use serde_json::{json, Value};
190
191  use pact_matching::Mismatch;
192
193  use crate::{MismatchResult, VerificationExecutionResult};
194  use crate::verification_result::{VerificationInteractionResult, VerificationMismatchResult};
195
196  #[test]
197  fn match_result_to_json() {
198    let mismatch = VerificationMismatchResult::Mismatches {
199      mismatches: vec![
200        Mismatch::BodyMismatch {
201          path: "1.2.3.4".to_string(),
202          expected: Some("100".into()),
203          actual: Some("200".into()),
204          mismatch: "Expected 100 but got 200".to_string()
205        }
206      ],
207      interaction_id: None
208    };
209    let json: Value = mismatch.into();
210    expect!(json).to(be_equal_to(json!({
211      "interactionId": "",
212      "mismatches": [
213        {
214          "actual": "200",
215          "expected": "100",
216          "mismatch": "Expected 100 but got 200",
217          "path": "1.2.3.4",
218          "type": "BodyMismatch"
219        }
220      ],
221      "type": "mismatches"
222    })));
223
224    let error = VerificationMismatchResult::Error {
225      error: "It went bang, Mate!".to_string(),
226      interaction_id: Some("1234".to_string())
227    };
228    let json: Value = error.into();
229    expect!(json).to(be_equal_to(json!({
230      "interactionId": "1234",
231      "message": "It went bang, Mate!",
232      "type": "error"
233    })));
234  }
235
236  #[test]
237  fn verification_execution_result_to_json() {
238    let result = VerificationExecutionResult {
239      result: false,
240      notices: vec![
241        hashmap!{
242          "comment".to_string() => "This is a comment".to_string()
243        }
244      ],
245      output: vec![
246        "line 1".to_string(),
247        "line 2".to_string(),
248        "line 3".to_string(),
249        "line 4".to_string()
250      ],
251      pending_errors: vec![
252        (
253          "interaction 1".to_string(),
254          VerificationMismatchResult::Error {
255            error: "Boom!".to_string(),
256            interaction_id: None
257          }
258        )
259      ],
260      errors: vec![
261        (
262          "interaction 2".to_string(),
263          VerificationMismatchResult::Error {
264            error: "Boom!".to_string(),
265            interaction_id: None
266          }
267        )
268      ],
269      interaction_results: vec![],
270    };
271
272    let json: Value = result.into();
273
274    assert_eq!(json, json!({
275      "errors": [
276        {
277          "interaction": "interaction 2".to_string(),
278          "mismatch": {
279            "interactionId": "".to_string(),
280            "message": "Boom!".to_string(),
281            "type": "error".to_string()
282          }
283        }
284      ],
285      "interactionResults": [],
286      "notices": [
287        {
288          "comment": "This is a comment".to_string()
289        }
290      ],
291      "output": [
292        "line 1".to_string(),
293        "line 2".to_string(),
294        "line 3".to_string(),
295        "line 4".to_string()
296      ],
297      "pendingErrors": [
298        {
299          "interaction": "interaction 1".to_string(),
300          "mismatch": {
301            "interactionId": "".to_string(),
302            "message": "Boom!".to_string(),
303            "type": "error".to_string()
304          }
305        }
306      ],
307      "result": false
308    }));
309  }
310
311  #[test]
312  fn verification_execution_result_to_json_includes_interaction_details() {
313    let result = VerificationExecutionResult {
314      interaction_results: vec![
315        VerificationInteractionResult {
316          interaction_id: None,
317          interaction_key: None,
318          description: "".to_string(),
319          interaction_description: "result-1".to_string(),
320          result: Ok(()),
321          pending: false,
322          duration: Default::default(),
323        },
324        VerificationInteractionResult {
325          interaction_id: None,
326          interaction_key: None,
327          description: "".to_string(),
328          interaction_description: "result-2".to_string(),
329          result: Err(MismatchResult::Error("test".to_string(), None)),
330          pending: false,
331          duration: Default::default(),
332        },
333        VerificationInteractionResult {
334          interaction_id: Some("test-id".to_string()),
335          interaction_key: None,
336          description: "".to_string(),
337          interaction_description: "result-3".to_string(),
338          result: Ok(()),
339          pending: false,
340          duration: Default::default(),
341        },
342        VerificationInteractionResult {
343          interaction_id: None,
344          interaction_key: Some("test-key".to_string()),
345          description: "".to_string(),
346          interaction_description: "result-4".to_string(),
347          result: Ok(()),
348          pending: false,
349          duration: Default::default(),
350        }
351      ],
352      .. VerificationExecutionResult::default()
353    };
354
355    let json: Value = result.into();
356
357    assert_eq!(json!({
358      "errors": [],
359      "interactionResults": [
360        {
361          "description": "result-1",
362          "duration": "0ns",
363          "result": "OK",
364        },
365        {
366          "description": "result-2",
367          "duration": "0ns",
368          "result": "Error",
369        },
370        {
371          "description": "result-3",
372          "duration": "0ns",
373          "interactionId": "test-id",
374          "result": "OK",
375        },
376        {
377          "description": "result-4",
378          "duration": "0ns",
379          "interactionKey": "test-key",
380          "result": "OK",
381        }
382      ],
383      "notices": [],
384      "output": [],
385      "pendingErrors": [],
386      "result": false
387    }), json);
388  }
389}