pact_matching/
lib.rs

1//! The `pact_matching` crate provides the core logic to performing matching on HTTP requests
2//! and responses. It implements the [V3 Pact specification](https://github.com/pact-foundation/pact-specification/tree/version-3)
3//! and [V4 Pact specification](https://github.com/pact-foundation/pact-specification/tree/version-4).
4//!
5//! ## To use it
6//!
7//! To use it, add it to your dependencies in your cargo manifest.
8//!
9//! This crate provides three functions: [`match_request`](fn.match_request.html), [`match_response`](fn.match_response.html)
10//! and [`match_message`](fn.match_message.html).
11//! These functions take an expected and actual request, response or message
12//! model from the [`models`)(models/index.html) module, and return a vector of mismatches.
13//!
14//! To compare any incoming request, it first needs to be converted to a [`models::Request`](models/struct.Request.html) and then can be compared. Same for
15//! any response.
16//!
17//! ## Crate features
18//! All features are enabled by default.
19//!
20//! * `datetime`: Enables support of date and time expressions and generators. This will add the `chronos` crate as a dependency.
21//! * `xml`: Enables support for parsing XML documents. This feature will add the `sxd-document` crate as a dependency.
22//! * `plugins`: Enables support for using plugins. This feature will add the `pact-plugin-driver` crate as a dependency.
23//! * `multipart`: Enables support for MIME multipart bodies. This feature will add the `multer` crate as a dependency.
24//!
25//! ## Reading and writing Pact files
26//!
27//! The [`Pact`](models/struct.Pact.html) struct in the [`models`)(models/index.html) module has methods to read and write pact JSON files. It supports all the specification
28//! versions up to V4, but will convert a V1, V1.1 or V2 spec file to a V3 format.
29//!
30//! ## Matching request and response parts
31//!
32//! V3 specification matching is supported for both JSON and XML bodies, headers, query strings and request paths.
33//!
34//! To understand the basic rules of matching, see [Matching Gotchas](https://docs.pact.io/getting_started/matching/gotchas).
35//! For example test cases for matching, see the [Pact Specification Project, version 3](https://github.com/bethesque/pact-specification/tree/version-3).
36//!
37//! By default, Pact will use string equality matching following Postel's Law. This means
38//! that for an actual value to match an expected one, they both must consist of the same
39//! sequence of characters. For collections (basically Maps and Lists), they must have the
40//! same elements that match in the same sequence, with cases where the additional elements
41//! in an actual Map are ignored.
42//!
43//! Matching rules can be defined for both request and response elements based on a pseudo JSON-Path
44//! syntax.
45//!
46//! ### Matching Bodies
47//!
48//! For the most part, matching involves matching request and response bodies in JSON or XML format.
49//! Other formats will either have their own matching rules, or will follow the JSON one.
50//!
51//! #### JSON body matching rules
52//!
53//! Bodies consist of Objects (Maps of Key-Value pairs), Arrays (Lists) and values (Strings, Numbers, true, false, null).
54//! Body matching rules are prefixed with `$`.
55//!
56//! The following method is used to determine if two bodies match:
57//!
58//! 1. If both the actual body and expected body are empty, the bodies match.
59//! 2. If the actual body is non-empty, and the expected body empty, the bodies match.
60//! 3. If the actual body is empty, and the expected body non-empty, the bodies don't match.
61//! 4. Otherwise do a comparison on the contents of the bodies.
62//!
63//! ##### For the body contents comparison:
64//!
65//! 1. If the actual and expected values are both Objects, compare as Maps.
66//! 2. If the actual and expected values are both Arrays, compare as Lists.
67//! 3. If the expected value is an Object, and the actual is not, they don't match.
68//! 4. If the expected value is an Array, and the actual is not, they don't match.
69//! 5. Otherwise, compare the values
70//!
71//! ##### For comparing Maps
72//!
73//! 1. If the actual map is non-empty while the expected is empty, they don't match.
74//! 2. If we allow unexpected keys, and the number of expected keys is greater than the actual keys,
75//! they don't match.
76//! 3. If we don't allow unexpected keys, and the expected and actual maps don't have the
77//! same number of keys, they don't match.
78//! 4. Otherwise, for each expected key and value pair:
79//!     1. if the actual map contains the key, compare the values
80//!     2. otherwise they don't match
81//!
82//! Postel's law governs if we allow unexpected keys or not.
83//!
84//! ##### For comparing lists
85//!
86//! 1. If there is a body matcher defined that matches the path to the list, default
87//! to that matcher and then compare the list contents.
88//! 2. If the expected list is empty and the actual one is not, the lists don't match.
89//! 3. Otherwise
90//!     1. compare the list sizes
91//!     2. compare the list contents
92//!
93//! ###### For comparing list contents
94//!
95//! 1. For each value in the expected list:
96//!     1. If the index of the value is less than the actual list's size, compare the value
97//!        with the actual value at the same index using the method for comparing values.
98//!     2. Otherwise the value doesn't match
99//!
100//! ##### For comparing values
101//!
102//! 1. If there is a matcher defined that matches the path to the value, default to that
103//! matcher
104//! 2. Otherwise compare the values using equality.
105//!
106//! #### XML body matching rules
107//!
108//! Bodies consist of a root element, Elements (Lists with children), Attributes (Maps) and values (Strings).
109//! Body matching rules are prefixed with `$`.
110//!
111//! The following method is used to determine if two bodies match:
112//!
113//! 1. If both the actual body and expected body are empty, the bodies match.
114//! 2. If the actual body is non-empty, and the expected body empty, the bodies match.
115//! 3. If the actual body is empty, and the expected body non-empty, the bodies don't match.
116//! 4. Otherwise do a comparison on the contents of the bodies.
117//!
118//! ##### For the body contents comparison:
119//!
120//! Start by comparing the root element.
121//!
122//! ##### For comparing elements
123//!
124//! 1. If there is a body matcher defined that matches the path to the element, default
125//! to that matcher on the elements name or children.
126//! 2. Otherwise the elements match if they have the same name.
127//!
128//! Then, if there are no mismatches:
129//!
130//! 1. compare the attributes of the element
131//! 2. compare the child elements
132//! 3. compare the text nodes
133//!
134//! ##### For comparing attributes
135//!
136//! Attributes are treated as a map of key-value pairs.
137//!
138//! 1. If the actual map is non-empty while the expected is empty, they don't match.
139//! 2. If we allow unexpected keys, and the number of expected keys is greater than the actual keys,
140//! they don't match.
141//! 3. If we don't allow unexpected keys, and the expected and actual maps don't have the
142//! same number of keys, they don't match.
143//!
144//! Then, for each expected key and value pair:
145//!
146//! 1. if the actual map contains the key, compare the values
147//! 2. otherwise they don't match
148//!
149//! Postel's law governs if we allow unexpected keys or not. Note for matching paths, attribute names are prefixed with an `@`.
150//!
151//! ###### For comparing child elements
152//!
153//! 1. If there is a matcher defined for the path to the child elements, then pad out the expected child elements to have the
154//! same size as the actual child elements.
155//! 2. Otherwise
156//!     1. If the actual children is non-empty while the expected is empty, they don't match.
157//!     2. If we allow unexpected keys, and the number of expected children is greater than the actual children,
158//!     they don't match.
159//!     3. If we don't allow unexpected keys, and the expected and actual children don't have the
160//!     same number of elements, they don't match.
161//!
162//! Then, for each expected and actual element pair, compare them using the rules for comparing elements.
163//!
164//! ##### For comparing text nodes
165//!
166//! Text nodes are combined into a single string and then compared as values.
167//!
168//! 1. If there is a matcher defined that matches the path to the text node (text node paths end with `#text`), default to that
169//! matcher
170//! 2. Otherwise compare the text using equality.
171//!
172//!
173//! ##### For comparing values
174//!
175//! 1. If there is a matcher defined that matches the path to the value, default to that
176//! matcher
177//! 2. Otherwise compare the values using equality.
178//!
179//! ### Matching Paths
180//!
181//! Paths are matched by the following:
182//!
183//! 1. If there is a matcher defined for `path`, default to that matcher.
184//! 2. Otherwise paths are compared as Strings
185//!
186//! ### Matching Queries
187//!
188//! 1. If the actual and expected query strings are empty, they match.
189//! 2. If the actual is not empty while the expected is, they don't match.
190//! 3. If the actual is empty while the expected is not, they don't match.
191//! 4. Otherwise convert both into a Map of keys mapped to a list values, and compare those.
192//!
193//! #### Matching Query Maps
194//!
195//! Query strings are parsed into a Map of keys mapped to lists of values. Key value
196//! pairs can be in any order, but when the same key appears more than once the values
197//! are compared in the order they appear in the query string.
198//!
199//! ### Matching Headers
200//!
201//! 1. Do a case-insensitive sort of the headers by keys
202//! 2. For each expected header in the sorted list:
203//!     1. If the actual headers contain that key, compare the header values
204//!     2. Otherwise the header does not match
205//!
206//! For matching header values:
207//!
208//! 1. If there is a matcher defined for `header.<HEADER_KEY>`, default to that matcher
209//! 2. Otherwise strip all whitespace after commas and compare the resulting strings.
210//!
211//! #### Matching Request Headers
212//!
213//! Request headers are matched by excluding the cookie header.
214//!
215//! #### Matching Request cookies
216//!
217//! If the list of expected cookies contains all the actual cookies, the cookies match.
218//!
219//! ### Matching Status Codes
220//!
221//! Status codes are compared as integer values.
222//!
223//! ### Matching HTTP Methods
224//!
225//! The actual and expected methods are compared as case-insensitive strings.
226//!
227//! ## Matching Rules
228//!
229//! Pact supports extending the matching rules on each type of object (Request or Response) with a `matchingRules` element in the pact file.
230//! This is a map of JSON path strings to a matcher. When an item is being compared, if there is an entry in the matching
231//! rules that corresponds to the path to the item, the comparison will be delegated to the defined matcher. Note that the
232//! matching rules cascade, so a rule can be specified on a value and will apply to all children of that value.
233//!
234//! ## Matcher Path expressions
235//!
236//! Pact does not support the full JSON path expressions, only ones that match the following rules:
237//!
238//! 1. All paths start with a dollar (`$`), representing the root.
239//! 2. All path elements are separated by periods (`.`), except array indices which use square brackets (`[]`).
240//! 3. Path elements represent keys.
241//! 4. A star (`*`) can be used to match all keys of a map or all items of an array (one level only).
242//!
243//! So the expression `$.item1.level[2].id` will match the highlighted item in the following body:
244//!
245//! ```js,ignore
246//! {
247//!   "item1": {
248//!     "level": [
249//!       {
250//!         "id": 100
251//!       },
252//!       {
253//!         "id": 101
254//!       },
255//!       {
256//!         "id": 102 // <---- $.item1.level[2].id
257//!       },
258//!       {
259//!         "id": 103
260//!       }
261//!     ]
262//!   }
263//! }
264//! ```
265//!
266//! while `$.*.level[*].id` will match all the ids of all the levels for all items.
267//!
268//! ### Matcher selection algorithm
269//!
270//! Due to the star notation, there can be multiple matcher paths defined that correspond to an item. The first, most
271//! specific expression is selected by assigning weightings to each path element and taking the product of the weightings.
272//! The matcher with the path with the largest weighting is used.
273//!
274//! * The root node (`$`) is assigned the value 2.
275//! * Any path element that does not match is assigned the value 0.
276//! * Any property name that matches a path element is assigned the value 2.
277//! * Any array index that matches a path element is assigned the value 2.
278//! * Any star (`*`) that matches a property or array index is assigned the value 1.
279//! * Everything else is assigned the value 0.
280//!
281//! So for the body with highlighted item:
282//!
283//! ```js,ignore
284//! {
285//!   "item1": {
286//!     "level": [
287//!       {
288//!         "id": 100
289//!       },
290//!       {
291//!         "id": 101
292//!       },
293//!       {
294//!         "id": 102 // <--- Item under consideration
295//!       },
296//!       {
297//!         "id": 103
298//!       }
299//!     ]
300//!   }
301//! }
302//! ```
303//!
304//! The expressions will have the following weightings:
305//!
306//! | expression | weighting calculation | weighting |
307//! |------------|-----------------------|-----------|
308//! | $ | $(2) | 2 |
309//! | $.item1 | $(2).item1(2) | 4 |
310//! | $.item2 | $(2).item2(0) | 0 |
311//! | $.item1.level | $(2).item1(2).level(2) | 8 |
312//! | $.item1.level\[1\] | $(2).item1(2).level(2)\[1(2)\] | 16 |
313//! | $.item1.level\[1\].id | $(2).item1(2).level(2)\[1(2)\].id(2) | 32 |
314//! | $.item1.level\[1\].name | $(2).item1(2).level(2)\[1(2)\].name(0) | 0 |
315//! | $.item1.level\[2\] | $(2).item1(2).level(2)\[2(0)\] | 0 |
316//! | $.item1.level\[2\].id | $(2).item1(2).level(2)\[2(0)\].id(2) | 0 |
317//! | $.item1.level\[*\].id | $(2).item1(2).level(2)\[*(1)\].id(2) | 16 |
318//! | $.\*.level\[\*\].id | $(2).*(1).level(2)\[*(1)\].id(2) | 8 |
319//!
320//! So for the item with id 102, the matcher with path `$.item1.level\[1\].id` and weighting 32 will be selected.
321//!
322//! ## Supported matchers
323//!
324//! The following matchers are supported:
325//!
326//! | matcher | Spec Version | example configuration | description |
327//! |---------|--------------|-----------------------|-------------|
328//! | Equality | V1 | `{ "match": "equality" }` | This is the default matcher, and relies on the equals operator |
329//! | Regex | V2 | `{ "match": "regex", "regex": "\\d+" }` | This executes a regular expression match against the string representation of a values. |
330//! | Type | V2 | `{ "match": "type" }` | This executes a type based match against the values, that is, they are equal if they are the same type. |
331//! | MinType | V2 | `{ "match": "type", "min": 2 }` | This executes a type based match against the values, that is, they are equal if they are the same type. In addition, if the values represent a collection, the length of the actual value is compared against the minimum. |
332//! | MaxType | V2 | `{ "match": "type", "max": 10 }` | This executes a type based match against the values, that is, they are equal if they are the same type. In addition, if the values represent a collection, the length of the actual value is compared against the maximum. |
333//! | MinMaxType | V2 | `{ "match": "type", "max": 10, "min": 2 }` | This executes a type based match against the values, that is, they are equal if they are the same type. In addition, if the values represent a collection, the length of the actual value is compared against the minimum and maximum. |
334//! | Include | V3 | `{ "match": "include", "value": "substr" }` | This checks if the string representation of a value contains the substring. |
335//! | Integer | V3 | `{ "match": "integer" }` | This checks if the type of the value is an integer. |
336//! | Decimal | V3 | `{ "match": "decimal" }` | This checks if the type of the value is a number with decimal places. |
337//! | Number | V3 | `{ "match": "number" }` | This checks if the type of the value is a number. |
338//! | Timestamp | V3 | `{ "match": "datetime", "format": "yyyy-MM-dd HH:ss:mm" }` | Matches the string representation of a value against the datetime format |
339//! | Time  | V3 | `{ "match": "time", "format": "HH:ss:mm" }` | Matches the string representation of a value against the time format |
340//! | Date  | V3 | `{ "match": "date", "format": "yyyy-MM-dd" }` | Matches the string representation of a value against the date format |
341//! | Null  | V3 | `{ "match": "null" }` | Match if the value is a null value (this is content specific, for JSON will match a JSON null) |
342//! | Boolean  | V3 | `{ "match": "boolean" }` | Match if the value is a boolean value (booleans and the string values `true` and `false`) |
343//! | ContentType  | V3 | `{ "match": "contentType", "value": "image/jpeg" }` | Match binary data by its content type (magic file check) |
344//! | Values  | V3 | `{ "match": "values" }` | Match the values in a map, ignoring the keys |
345//! | ArrayContains | V4 | `{ "match": "arrayContains", "variants": [...] }` | Checks if all the variants are present in an array. |
346//! | StatusCode | V4 | `{ "match": "statusCode", "status": "success" }` | Matches the response status code. |
347//! | NotEmpty | V4 | `{ "match": "notEmpty" }` | Value must be present and not empty (not null or the empty string) |
348//! | Semver | V4 | `{ "match": "semver" }` | Value must be valid based on the semver specification |
349//! | Semver | V4 | `{ "match": "semver" }` | Value must be valid based on the semver specification |
350//! | EachKey | V4 | `{ "match": "eachKey", "rules": [{"match": "regex", "regex": "\\$(\\.\\w+)+"}], "value": "$.test.one" }` | Allows defining matching rules to apply to the keys in a map |
351//! | EachValue | V4 | `{ "match": "eachValue", "rules": [{"match": "regex", "regex": "\\$(\\.\\w+)+"}], "value": "$.test.one" }` | Allows defining matching rules to apply to the values in a collection. For maps, delgates to the Values matcher. |
352
353#![warn(missing_docs)]
354
355use std::collections::{BTreeSet, HashMap, HashSet};
356use std::fmt::{Debug, Display};
357use std::fmt::Formatter;
358use std::hash::Hash;
359use std::panic::RefUnwindSafe;
360use std::str;
361use std::str::from_utf8;
362
363use ansi_term::*;
364use ansi_term::Colour::*;
365use anyhow::anyhow;
366use bytes::Bytes;
367use itertools::{Either, Itertools};
368use lazy_static::*;
369use maplit::{hashmap, hashset};
370#[cfg(feature = "plugins")] use pact_plugin_driver::catalogue_manager::find_content_matcher;
371#[cfg(feature = "plugins")] use pact_plugin_driver::plugin_models::PluginInteractionConfig;
372use serde_json::{json, Value};
373#[allow(unused_imports)] use tracing::{debug, error, info, instrument, trace, warn};
374
375use pact_models::bodies::OptionalBody;
376use pact_models::content_types::ContentType;
377use pact_models::generators::{apply_generators, GenerateValue, GeneratorCategory, GeneratorTestMode, VariantMatcher};
378use pact_models::http_parts::HttpPart;
379use pact_models::interaction::Interaction;
380use pact_models::json_utils::json_to_string;
381use pact_models::matchingrules::{Category, MatchingRule, MatchingRuleCategory, RuleList};
382use pact_models::pact::Pact;
383use pact_models::PactSpecification;
384use pact_models::path_exp::DocPath;
385use pact_models::v4::http_parts::{HttpRequest, HttpResponse};
386use pact_models::v4::message_parts::MessageContents;
387use pact_models::v4::sync_message::SynchronousMessage;
388
389use crate::engine::{
390  build_request_plan,
391  execute_request_plan,
392  ExecutionPlan,
393  NodeResult,
394  PlanNodeType,
395  Terminator
396};
397use crate::engine::context::{MatchingConfiguration, PlanMatchingContext};
398use crate::generators::bodies::generators_process_body;
399use crate::generators::DefaultVariantMatcher;
400use crate::headers::{match_header_value, match_headers};
401#[cfg(feature = "plugins")] use crate::json::match_json;
402use crate::matchers::*;
403use crate::matchingrules::DisplayForMismatch;
404use crate::Mismatch::{BodyMismatch, HeaderMismatch, QueryMismatch};
405#[cfg(feature = "plugins")] use crate::plugin_support::{InteractionPart, setup_plugin_config};
406use crate::query::match_query_maps;
407
408/// Simple macro to convert a string slice to a `String` struct.
409#[macro_export]
410macro_rules! s {
411    ($e:expr) => ($e.to_string())
412}
413
414/// Version of the library
415pub const PACT_RUST_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
416
417pub mod matchers;
418pub mod json;
419pub mod matchingrules;
420pub mod metrics;
421pub mod generators;
422pub mod engine;
423
424#[cfg(feature = "xml")] mod xml;
425pub mod binary_utils;
426pub mod headers;
427pub mod query;
428pub mod form_urlencoded;
429#[cfg(feature = "plugins")] mod plugin_support;
430
431#[cfg(not(feature = "plugins"))]
432#[derive(Clone, Debug, PartialEq)]
433/// Stub for when plugins feature is not enabled
434pub struct PluginInteractionConfig {}
435
436/// Context used to apply matching logic
437pub trait MatchingContext: Debug {
438  /// If there is a matcher defined at the path in this context
439  fn matcher_is_defined(&self, path: &DocPath) -> bool;
440
441  /// Selected the best matcher from the context for the given path
442  fn select_best_matcher(&self, path: &DocPath) -> RuleList;
443
444  /// If there is a type matcher defined at the path in this context
445  fn type_matcher_defined(&self, path: &DocPath) -> bool;
446
447  /// If there is a values matcher defined at the path in this context
448  fn values_matcher_defined(&self, path: &DocPath) -> bool;
449
450  /// If a matcher defined at the path (ignoring parents)
451  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool;
452
453  /// Matches the keys of the expected and actual maps
454  fn match_keys(&self, path: &DocPath, expected: &BTreeSet<String>, actual: &BTreeSet<String>) -> Result<(), Vec<CommonMismatch>>;
455
456  /// Returns the plugin configuration associated with the context
457  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig>;
458
459  /// Returns the matching rules for the matching context
460  fn matchers(&self) -> &MatchingRuleCategory;
461
462  /// Configuration to apply when matching with the context
463  fn config(&self) -> DiffConfig;
464
465  /// Clones the current context with the provided matching rules
466  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync>;
467}
468
469#[derive(Debug, Clone)]
470/// Core implementation of a matching context
471pub struct CoreMatchingContext {
472  /// Matching rules that apply when matching with the context
473  pub matchers: MatchingRuleCategory,
474  /// Configuration to apply when matching with the context
475  pub config: DiffConfig,
476  /// Specification version to apply when matching with the context
477  pub matching_spec: PactSpecification,
478  /// Any plugin configuration available for the interaction
479  pub plugin_configuration: HashMap<String, PluginInteractionConfig>
480}
481
482impl CoreMatchingContext {
483  /// Creates a new context with the given config and matching rules
484  pub fn new(
485    config: DiffConfig,
486    matchers: &MatchingRuleCategory,
487    plugin_configuration: &HashMap<String, PluginInteractionConfig>
488  ) -> Self {
489    CoreMatchingContext {
490      matchers: matchers.clone(),
491      config,
492      plugin_configuration: plugin_configuration.clone(),
493      .. CoreMatchingContext::default()
494    }
495  }
496
497  /// Creates a new empty context with the given config
498  pub fn with_config(config: DiffConfig) -> Self {
499    CoreMatchingContext {
500      config,
501      .. CoreMatchingContext::default()
502    }
503  }
504
505  fn matchers_for_exact_path(&self, path: &DocPath) -> MatchingRuleCategory {
506    match self.matchers.name {
507      Category::HEADER | Category::QUERY => self.matchers.filter(|&(val, _)| {
508        path.len() == 1 && path.first_field() == val.first_field()
509      }),
510      Category::BODY => self.matchers.filter(|&(val, _)| {
511        let p = path.to_vec();
512        let p_slice = p.iter().map(|p| p.as_str()).collect_vec();
513        val.matches_path_exactly(p_slice.as_slice())
514      }),
515      _ => self.matchers.filter(|_| false)
516    }
517  }
518
519  #[allow(dead_code)]
520  pub(crate) fn clone_from(context: &(dyn MatchingContext + Send + Sync)) -> Self {
521    CoreMatchingContext {
522      matchers: context.matchers().clone(),
523      config: context.config().clone(),
524      plugin_configuration: context.plugin_configuration().clone(),
525      .. CoreMatchingContext::default()
526    }
527  }
528}
529
530impl Default for CoreMatchingContext {
531  fn default() -> Self {
532    CoreMatchingContext {
533      matchers: Default::default(),
534      config: DiffConfig::AllowUnexpectedKeys,
535      matching_spec: PactSpecification::V3,
536      plugin_configuration: Default::default()
537    }
538  }
539}
540
541impl MatchingContext for CoreMatchingContext {
542  #[instrument(level = "trace", ret, skip_all, fields(path, matchers = ?self.matchers))]
543  fn matcher_is_defined(&self, path: &DocPath) -> bool {
544    let path = path.to_vec();
545    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
546    self.matchers.matcher_is_defined(path_slice.as_slice())
547  }
548
549  fn select_best_matcher(&self, path: &DocPath) -> RuleList {
550    let path = path.to_vec();
551    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
552    self.matchers.select_best_matcher(path_slice.as_slice())
553  }
554
555  fn type_matcher_defined(&self, path: &DocPath) -> bool {
556    let path = path.to_vec();
557    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
558    self.matchers.resolve_matchers_for_path(path_slice.as_slice()).type_matcher_defined()
559  }
560
561  fn values_matcher_defined(&self, path: &DocPath) -> bool {
562    self.matchers_for_exact_path(path).values_matcher_defined()
563  }
564
565  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool {
566    let actual = self.matchers_for_exact_path(path);
567    if matchers.is_empty() {
568      actual.is_not_empty()
569    } else {
570      actual.as_rule_list().rules.iter().any(|r| matchers.contains(r.name().as_str()))
571    }
572  }
573
574  fn match_keys(&self, path: &DocPath, expected: &BTreeSet<String>, actual: &BTreeSet<String>) -> Result<(), Vec<CommonMismatch>> {
575    let mut expected_keys = expected.iter().cloned().collect::<Vec<String>>();
576    expected_keys.sort();
577    let mut actual_keys = actual.iter().cloned().collect::<Vec<String>>();
578    actual_keys.sort();
579    let missing_keys: Vec<String> = expected.iter().filter(|key| !actual.contains(*key)).cloned().collect();
580    let mut result = vec![];
581
582    if !self.direct_matcher_defined(path, &hashset! { "values", "each-value", "each-key" }) {
583      match self.config {
584        DiffConfig::AllowUnexpectedKeys if !missing_keys.is_empty() => {
585          result.push(CommonMismatch {
586            path: path.to_string(),
587            expected: expected.for_mismatch(),
588            actual: actual.for_mismatch(),
589            description: format!("Actual map is missing the following keys: {}", missing_keys.join(", ")),
590          });
591        }
592        DiffConfig::NoUnexpectedKeys if expected_keys != actual_keys => {
593          result.push(CommonMismatch {
594            path: path.to_string(),
595            expected: expected.for_mismatch(),
596            actual: actual.for_mismatch(),
597            description: format!("Expected a Map with keys [{}] but received one with keys [{}]",
598                              expected_keys.join(", "), actual_keys.join(", ")),
599          });
600        }
601        _ => {}
602      }
603    }
604
605    if self.direct_matcher_defined(path, &Default::default()) {
606      let matchers = self.select_best_matcher(path);
607      for matcher in matchers.rules {
608        match matcher {
609          MatchingRule::EachKey(definition) => {
610            for sub_matcher in definition.rules {
611              match sub_matcher {
612                Either::Left(rule) => {
613                  for key in &actual_keys {
614                    let key_path = path.join(key);
615                    if let Err(err) = String::default().matches_with(key, &rule, false) {
616                      result.push(CommonMismatch {
617                        path: key_path.to_string(),
618                        expected: "".to_string(),
619                        actual: key.clone(),
620                        description: err.to_string(),
621                      });
622                    }
623                  }
624                }
625                Either::Right(name) => {
626                  result.push(CommonMismatch {
627                    path: path.to_string(),
628                    expected: expected.for_mismatch(),
629                    actual: actual.for_mismatch(),
630                    description: format!("Expected a matching rule, found an unresolved reference '{}'",
631                      name.name),
632                  });
633                }
634              }
635            }
636          }
637          _ => {}
638        }
639      }
640    }
641
642    if result.is_empty() {
643      Ok(())
644    } else {
645      Err(result)
646    }
647  }
648
649  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig> {
650    &self.plugin_configuration
651  }
652
653  fn matchers(&self) -> &MatchingRuleCategory {
654    &self.matchers
655  }
656
657  fn config(&self) -> DiffConfig {
658    self.config
659  }
660
661  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync> {
662    Box::new(CoreMatchingContext {
663      matchers: matchers.clone(),
664      config: self.config.clone(),
665      matching_spec: self.matching_spec,
666      plugin_configuration: self.plugin_configuration.clone()
667    })
668  }
669}
670
671#[derive(Debug, Clone, Default)]
672/// Matching context for headers. Keys will be applied in a case-insensitive manor
673pub struct HeaderMatchingContext {
674  inner_context: CoreMatchingContext
675}
676
677impl HeaderMatchingContext {
678  /// Wraps a MatchingContext, downcasing all the matching path keys
679  pub fn new(context: &(dyn MatchingContext + Send + Sync)) -> Self {
680    let matchers = context.matchers();
681    HeaderMatchingContext {
682      inner_context: CoreMatchingContext::new(
683        context.config(),
684        &MatchingRuleCategory {
685          name: matchers.name.clone(),
686          rules: matchers.rules.iter()
687            .map(|(path, rules)| {
688              (path.to_lower_case(), rules.clone())
689            })
690            .collect()
691        },
692        &context.plugin_configuration()
693      )
694    }
695  }
696}
697
698impl MatchingContext for HeaderMatchingContext {
699  fn matcher_is_defined(&self, path: &DocPath) -> bool {
700    self.inner_context.matcher_is_defined(path)
701  }
702
703  fn select_best_matcher(&self, path: &DocPath) -> RuleList {
704    self.inner_context.select_best_matcher(path)
705  }
706
707  fn type_matcher_defined(&self, path: &DocPath) -> bool {
708    self.inner_context.type_matcher_defined(path)
709  }
710
711  fn values_matcher_defined(&self, path: &DocPath) -> bool {
712    self.inner_context.values_matcher_defined(path)
713  }
714
715  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool {
716    self.inner_context.direct_matcher_defined(path, matchers)
717  }
718
719  fn match_keys(&self, path: &DocPath, expected: &BTreeSet<String>, actual: &BTreeSet<String>) -> Result<(), Vec<CommonMismatch>> {
720    self.inner_context.match_keys(path, expected, actual)
721  }
722
723  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig> {
724    self.inner_context.plugin_configuration()
725  }
726
727  fn matchers(&self) -> &MatchingRuleCategory {
728    self.inner_context.matchers()
729  }
730
731  fn config(&self) -> DiffConfig {
732    self.inner_context.config()
733  }
734
735  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync> {
736    Box::new(HeaderMatchingContext::new(
737      &CoreMatchingContext {
738        matchers: matchers.clone(),
739        config: self.inner_context.config.clone(),
740        matching_spec: self.inner_context.matching_spec,
741        plugin_configuration: self.inner_context.plugin_configuration.clone()
742      }
743    ))
744  }
745}
746
747lazy_static! {
748  static ref BODY_MATCHERS: [
749    (fn(content_type: &ContentType) -> bool,
750    fn(expected: &(dyn HttpPart + Send + Sync), actual: &(dyn HttpPart + Send + Sync), context: &(dyn MatchingContext + Send + Sync)) -> Result<(), Vec<Mismatch>>); 5]
751     = [
752      (|content_type| { content_type.is_json() }, json::match_json),
753      (|content_type| { content_type.is_xml() }, match_xml),
754      (|content_type| { content_type.main_type == "multipart" }, binary_utils::match_mime_multipart),
755      (|content_type| { content_type.base_type() == "application/x-www-form-urlencoded" }, form_urlencoded::match_form_urlencoded),
756      (|content_type| { content_type.is_binary() || content_type.base_type() == "application/octet-stream" }, binary_utils::match_octet_stream)
757  ];
758}
759
760fn match_xml(
761  expected: &(dyn HttpPart + Send + Sync),
762  actual: &(dyn HttpPart + Send + Sync),
763  context: &(dyn MatchingContext + Send + Sync)
764) -> Result<(), Vec<Mismatch>> {
765  #[cfg(feature = "xml")]
766  {
767    xml::match_xml(expected, actual, context)
768  }
769  #[cfg(not(feature = "xml"))]
770  {
771    warn!("Matching XML documents requires the xml feature to be enabled");
772    match_text(&expected.body().value(), &actual.body().value(), context)
773  }
774}
775
776/// Store common mismatch information so it can be converted to different type of mismatches
777#[derive(Debug, Clone, PartialOrd, Ord, Eq)]
778pub struct CommonMismatch {
779  /// path expression to where the mismatch occurred
780  pub path: String,
781  /// expected value (as a string)
782  expected: String,
783  /// actual value (as a string)
784  actual: String,
785  /// Description of the mismatch
786  description: String
787}
788
789impl CommonMismatch {
790  /// Convert common mismatch to body mismatch
791  pub fn to_body_mismatch(&self) -> Mismatch {
792    Mismatch::BodyMismatch {
793      path: self.path.clone(),
794      expected: Some(self.expected.clone().into()),
795      actual: Some(self.actual.clone().into()),
796      mismatch: self.description.clone()
797    }
798  }
799
800  /// Convert common mismatch to query mismatch
801  pub fn to_query_mismatch(&self) -> Mismatch {
802    Mismatch::QueryMismatch {
803      parameter: self.path.clone(),
804      expected: self.expected.clone(),
805      actual: self.actual.clone(),
806      mismatch: self.description.clone()
807    }
808  }
809
810  /// Convert common mismatch to header mismatch
811  pub fn to_header_mismatch(&self) -> Mismatch {
812    Mismatch::HeaderMismatch {
813      key: self.path.clone(),
814      expected: self.expected.clone().into(),
815      actual: self.actual.clone().into(),
816      mismatch: self.description.clone()
817    }
818  }
819}
820
821impl Display for CommonMismatch {
822  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
823    write!(f, "{}", self.description)
824  }
825}
826
827impl PartialEq for CommonMismatch {
828  fn eq(&self, other: &CommonMismatch) -> bool {
829    self.path == other.path && self.expected == other.expected && self.actual == other.actual
830  }
831}
832
833impl From<Mismatch> for CommonMismatch {
834  fn from(value: Mismatch) -> Self {
835    match value {
836      Mismatch::MethodMismatch { expected, actual , mismatch} => CommonMismatch {
837        path: "".to_string(),
838        expected: expected.clone(),
839        actual: actual.clone(),
840        description: mismatch.clone()
841      },
842      Mismatch::PathMismatch { expected, actual, mismatch } => CommonMismatch {
843        path: "".to_string(),
844        expected: expected.clone(),
845        actual: actual.clone(),
846        description: mismatch.clone()
847      },
848      Mismatch::StatusMismatch { expected, actual, mismatch } => CommonMismatch {
849        path: "".to_string(),
850        expected: expected.to_string(),
851        actual: actual.to_string(),
852        description: mismatch.clone()
853      },
854      Mismatch::QueryMismatch { parameter, expected, actual, mismatch } => CommonMismatch {
855        path: parameter.clone(),
856        expected: expected.clone(),
857        actual: actual.clone(),
858        description: mismatch.clone()
859      },
860      Mismatch::HeaderMismatch { key, expected, actual, mismatch } => CommonMismatch {
861        path: key.clone(),
862        expected: expected.clone(),
863        actual: actual.clone(),
864        description: mismatch.clone()
865      },
866      Mismatch::BodyTypeMismatch { expected, actual, mismatch, .. } => CommonMismatch {
867        path: "".to_string(),
868        expected: expected.clone(),
869        actual: actual.clone(),
870        description: mismatch.clone()
871      },
872      Mismatch::BodyMismatch { path, expected, actual, mismatch } => CommonMismatch {
873        path: path.clone(),
874        expected: String::from_utf8_lossy(expected.unwrap_or_default().as_ref()).to_string(),
875        actual: String::from_utf8_lossy(actual.unwrap_or_default().as_ref()).to_string(),
876        description: mismatch.clone()
877      },
878      Mismatch::MetadataMismatch { key, expected, actual, mismatch } => CommonMismatch {
879        path: key.clone(),
880        expected: expected.clone(),
881        actual: actual.clone(),
882        description: mismatch.clone()
883      }
884    }
885  }
886}
887
888/// Enum that defines the different types of mismatches that can occur.
889#[derive(Debug, Clone, PartialOrd, Ord, Eq)]
890pub enum Mismatch {
891    /// Request Method mismatch
892    MethodMismatch {
893      /// Expected request method
894      expected: String,
895      /// Actual request method
896      actual: String,
897      /// description of the mismatch
898      mismatch: String
899    },
900    /// Request Path mismatch
901    PathMismatch {
902        /// expected request path
903        expected: String,
904        /// actual request path
905        actual: String,
906        /// description of the mismatch
907        mismatch: String
908    },
909    /// Response status mismatch
910    StatusMismatch {
911        /// expected response status
912      expected: u16,
913      /// actual response status
914      actual: u16,
915      /// description of the mismatch
916      mismatch: String
917    },
918    /// Request query mismatch
919    QueryMismatch {
920        /// query parameter name
921        parameter: String,
922        /// expected value
923        expected: String,
924        /// actual value
925        actual: String,
926        /// description of the mismatch
927        mismatch: String
928    },
929    /// Header mismatch
930    HeaderMismatch {
931        /// header key
932        key: String,
933        /// expected value
934        expected: String,
935        /// actual value
936        actual: String,
937        /// description of the mismatch
938        mismatch: String
939    },
940    /// Mismatch in the content type of the body
941    BodyTypeMismatch {
942      /// expected content type of the body
943      expected: String,
944      /// actual content type of the body
945      actual: String,
946      /// description of the mismatch
947      mismatch: String,
948      /// expected value
949      expected_body: Option<Bytes>,
950      /// actual value
951      actual_body: Option<Bytes>
952    },
953    /// Body element mismatch
954    BodyMismatch {
955      /// path expression to where the mismatch occurred
956      path: String,
957      /// expected value
958      expected: Option<Bytes>,
959      /// actual value
960      actual: Option<Bytes>,
961      /// description of the mismatch
962      mismatch: String
963    },
964    /// Message metadata mismatch
965    MetadataMismatch {
966      /// key
967      key: String,
968      /// expected value
969      expected: String,
970      /// actual value
971      actual: String,
972      /// description of the mismatch
973      mismatch: String
974    }
975}
976
977impl Mismatch {
978  /// Converts the mismatch to a `Value` struct.
979  pub fn to_json(&self) -> serde_json::Value {
980    match self {
981      Mismatch::MethodMismatch { expected: e, actual: a, mismatch: m } => {
982        json!({
983          "type" : "MethodMismatch",
984          "expected" : e,
985          "actual" : a,
986          "mismatch" : m
987        })
988      },
989      Mismatch::PathMismatch { expected: e, actual: a, mismatch: m } => {
990        json!({
991          "type" : "PathMismatch",
992          "expected" : e,
993          "actual" : a,
994          "mismatch" : m
995        })
996      },
997      Mismatch::StatusMismatch { expected: e, actual: a, mismatch: m } => {
998        json!({
999          "type" : "StatusMismatch",
1000          "expected" : e,
1001          "actual" : a,
1002          "mismatch": m
1003        })
1004      },
1005      Mismatch::QueryMismatch { parameter: p, expected: e, actual: a, mismatch: m } => {
1006        json!({
1007          "type" : "QueryMismatch",
1008          "parameter" : p,
1009          "expected" : e,
1010          "actual" : a,
1011          "mismatch" : m
1012        })
1013      },
1014      Mismatch::HeaderMismatch { key: k, expected: e, actual: a, mismatch: m } => {
1015        json!({
1016          "type" : "HeaderMismatch",
1017          "key" : k,
1018          "expected" : e,
1019          "actual" : a,
1020          "mismatch" : m
1021        })
1022      },
1023      Mismatch::BodyTypeMismatch {
1024        expected,
1025        actual,
1026        mismatch,
1027        expected_body,
1028        actual_body
1029      } => {
1030        json!({
1031          "type" : "BodyTypeMismatch",
1032          "expected" : expected,
1033          "actual" : actual,
1034          "mismatch" : mismatch,
1035          "expectedBody": match expected_body {
1036            Some(v) => serde_json::Value::String(str::from_utf8(v)
1037              .unwrap_or("ERROR: could not convert to UTF-8 from bytes").into()),
1038            None => serde_json::Value::Null
1039          },
1040          "actualBody": match actual_body {
1041            Some(v) => serde_json::Value::String(str::from_utf8(v)
1042              .unwrap_or("ERROR: could not convert to UTF-8 from bytes").into()),
1043            None => serde_json::Value::Null
1044          }
1045        })
1046      },
1047      Mismatch::BodyMismatch { path, expected, actual, mismatch } => {
1048        json!({
1049          "type" : "BodyMismatch",
1050          "path" : path,
1051          "expected" : match expected {
1052            Some(v) => serde_json::Value::String(str::from_utf8(v).unwrap_or("ERROR: could not convert from bytes").into()),
1053            None => serde_json::Value::Null
1054          },
1055          "actual" : match actual {
1056            Some(v) => serde_json::Value::String(str::from_utf8(v).unwrap_or("ERROR: could not convert from bytes").into()),
1057            None => serde_json::Value::Null
1058          },
1059          "mismatch" : mismatch
1060        })
1061      }
1062      Mismatch::MetadataMismatch { key, expected, actual, mismatch } => {
1063        json!({
1064          "type" : "MetadataMismatch",
1065          "key" : key,
1066          "expected" : expected,
1067          "actual" : actual,
1068          "mismatch" : mismatch
1069        })
1070      }
1071    }
1072  }
1073
1074    /// Returns the type of the mismatch as a string
1075    pub fn mismatch_type(&self) -> &str {
1076      match *self {
1077        Mismatch::MethodMismatch { .. } => "MethodMismatch",
1078        Mismatch::PathMismatch { .. } => "PathMismatch",
1079        Mismatch::StatusMismatch { .. } => "StatusMismatch",
1080        Mismatch::QueryMismatch { .. } => "QueryMismatch",
1081        Mismatch::HeaderMismatch { .. } => "HeaderMismatch",
1082        Mismatch::BodyTypeMismatch { .. } => "BodyTypeMismatch",
1083        Mismatch::BodyMismatch { .. } => "BodyMismatch",
1084        Mismatch::MetadataMismatch { .. } => "MetadataMismatch"
1085      }
1086    }
1087
1088    /// Returns a summary string for this mismatch
1089    pub fn summary(&self) -> String {
1090      match *self {
1091        Mismatch::MethodMismatch { expected: ref e, .. } => format!("is a {} request", e),
1092        Mismatch::PathMismatch { expected: ref e, .. } => format!("to path '{}'", e),
1093        Mismatch::StatusMismatch { expected: ref e, .. } => format!("has status code {}", e),
1094        Mismatch::QueryMismatch { ref parameter, expected: ref e, .. } => format!("includes parameter '{}' with value '{}'", parameter, e),
1095        Mismatch::HeaderMismatch { ref key, expected: ref e, .. } => format!("includes header '{}' with value '{}'", key, e),
1096        Mismatch::BodyTypeMismatch { .. } => "has a matching body".to_string(),
1097        Mismatch::BodyMismatch { .. } => "has a matching body".to_string(),
1098        Mismatch::MetadataMismatch { .. } => "has matching metadata".to_string()
1099      }
1100    }
1101
1102    /// Returns a formatted string for this mismatch
1103    pub fn description(&self) -> String {
1104      match self {
1105        Mismatch::MethodMismatch { expected: e, actual: a, mismatch: m } => if m.is_empty() {
1106          format!("expected {} but was {}", e, a)
1107        } else {
1108          m.clone()
1109        },
1110        Mismatch::PathMismatch { mismatch, .. } => mismatch.clone(),
1111        Mismatch::StatusMismatch { mismatch, .. } => mismatch.clone(),
1112        Mismatch::QueryMismatch { mismatch, .. } => mismatch.clone(),
1113        Mismatch::HeaderMismatch { mismatch, .. } => mismatch.clone(),
1114        Mismatch::BodyTypeMismatch {  expected: e, actual: a, .. } =>
1115          format!("Expected a body of '{}' but the actual content type was '{}'", e, a),
1116        Mismatch::BodyMismatch { path, mismatch, .. } => format!("{} -> {}", path, mismatch),
1117        Mismatch::MetadataMismatch { mismatch, .. } => mismatch.clone()
1118      }
1119    }
1120
1121    /// Returns a formatted string with ansi escape codes for this mismatch
1122    pub fn ansi_description(&self) -> String {
1123      match self {
1124        Mismatch::MethodMismatch { expected: e, actual: a, .. } => format!("expected {} but was {}", Red.paint(e.clone()), Green.paint(a.clone())),
1125        Mismatch::PathMismatch { expected: e, actual: a, .. } => format!("expected '{}' but was '{}'", Red.paint(e.clone()), Green.paint(a.clone())),
1126        Mismatch::StatusMismatch { expected: e, actual: a, .. } => format!("expected {} but was {}", Red.paint(e.to_string()), Green.paint(a.to_string())),
1127        Mismatch::QueryMismatch { expected: e, actual: a, parameter: p, .. } => format!("Expected '{}' but received '{}' for query parameter '{}'",
1128          Red.paint(e.to_string()), Green.paint(a.to_string()), Style::new().bold().paint(p.clone())),
1129        Mismatch::HeaderMismatch { expected: e, actual: a, key: k, .. } => format!("Expected header '{}' to have value '{}' but was '{}'",
1130          Style::new().bold().paint(k.clone()), Red.paint(e.to_string()), Green.paint(a.to_string())),
1131        Mismatch::BodyTypeMismatch {  expected: e, actual: a, .. } =>
1132          format!("expected a body of '{}' but the actual content type was '{}'", Red.paint(e.clone()), Green.paint(a.clone())),
1133        Mismatch::BodyMismatch { path, mismatch, .. } => format!("{} -> {}", Style::new().bold().paint(path.clone()), mismatch),
1134        Mismatch::MetadataMismatch { expected: e, actual: a, key: k, .. } => format!("Expected message metadata '{}' to have value '{}' but was '{}'",
1135          Style::new().bold().paint(k.clone()), Red.paint(e.to_string()), Green.paint(a.to_string()))
1136      }
1137    }
1138}
1139
1140impl PartialEq for Mismatch {
1141  fn eq(&self, other: &Mismatch) -> bool {
1142    match (self, other) {
1143      (Mismatch::MethodMismatch { expected: e1, actual: a1, .. },
1144        Mismatch::MethodMismatch { expected: e2, actual: a2, .. }) => {
1145        e1 == e2 && a1 == a2
1146      },
1147      (Mismatch::PathMismatch { expected: e1, actual: a1, .. },
1148        Mismatch::PathMismatch { expected: e2, actual: a2, .. }) => {
1149        e1 == e2 && a1 == a2
1150      },
1151      (Mismatch::StatusMismatch { expected: e1, actual: a1, .. },
1152        Mismatch::StatusMismatch { expected: e2, actual: a2, .. }) => {
1153        e1 == e2 && a1 == a2
1154      },
1155      (Mismatch::BodyTypeMismatch { expected: e1, actual: a1, .. },
1156        Mismatch::BodyTypeMismatch { expected: e2, actual: a2, .. }) => {
1157        e1 == e2 && a1 == a2
1158      },
1159      (Mismatch::QueryMismatch { parameter: p1, expected: e1, actual: a1, .. },
1160        Mismatch::QueryMismatch { parameter: p2, expected: e2, actual: a2, .. }) => {
1161        p1 == p2 && e1 == e2 && a1 == a2
1162      },
1163      (Mismatch::HeaderMismatch { key: p1, expected: e1, actual: a1, .. },
1164        Mismatch::HeaderMismatch { key: p2, expected: e2, actual: a2, .. }) => {
1165        p1 == p2 && e1 == e2 && a1 == a2
1166      },
1167      (Mismatch::BodyMismatch { path: p1, expected: e1, actual: a1, .. },
1168        Mismatch::BodyMismatch { path: p2, expected: e2, actual: a2, .. }) => {
1169        p1 == p2 && e1 == e2 && a1 == a2
1170      },
1171      (Mismatch::MetadataMismatch { key: p1, expected: e1, actual: a1, .. },
1172        Mismatch::MetadataMismatch { key: p2, expected: e2, actual: a2, .. }) => {
1173        p1 == p2 && e1 == e2 && a1 == a2
1174      },
1175      (_, _) => false
1176    }
1177  }
1178}
1179
1180impl Display for Mismatch {
1181  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1182    write!(f, "{}", self.description())
1183  }
1184}
1185
1186fn merge_result<T: Clone>(res1: Result<(), Vec<T>>, res2: Result<(), Vec<T>>) -> Result<(), Vec<T>> {
1187  match (&res1, &res2) {
1188    (Ok(_), Ok(_)) => res1.clone(),
1189    (Err(_), Ok(_)) => res1.clone(),
1190    (Ok(_), Err(_)) => res2.clone(),
1191    (Err(m1), Err(m2)) => {
1192      let mut mismatches = m1.clone();
1193      mismatches.extend_from_slice(&*m2);
1194      Err(mismatches)
1195    }
1196  }
1197}
1198
1199/// Result of matching a request body
1200#[derive(Debug, Clone, PartialEq)]
1201pub enum BodyMatchResult {
1202  /// Matched OK
1203  Ok,
1204  /// Mismatch in the content type of the body
1205  BodyTypeMismatch {
1206    /// Expected content type
1207    expected_type: String,
1208    /// Actual content type
1209    actual_type: String,
1210    /// Message
1211    message: String,
1212    /// Expected body
1213    expected: Option<Bytes>,
1214    /// Actual body
1215    actual: Option<Bytes>
1216  },
1217  /// Mismatches with the body contents
1218  BodyMismatches(HashMap<String, Vec<Mismatch>>)
1219}
1220
1221impl BodyMatchResult {
1222  /// Returns all the mismatches
1223  pub fn mismatches(&self) -> Vec<Mismatch> {
1224    match self {
1225      BodyMatchResult::BodyTypeMismatch { expected_type, actual_type, message, expected, actual } => {
1226        vec![Mismatch::BodyTypeMismatch {
1227          expected: expected_type.clone(),
1228          actual: actual_type.clone(),
1229          mismatch: message.clone(),
1230          expected_body: expected.clone(),
1231          actual_body: actual.clone()
1232        }]
1233      },
1234      BodyMatchResult::BodyMismatches(results) =>
1235        results.values().flatten().cloned().collect(),
1236      _ => vec![]
1237    }
1238  }
1239
1240  /// If all the things matched OK
1241  pub fn all_matched(&self) -> bool {
1242    match self {
1243      BodyMatchResult::BodyTypeMismatch { .. } => false,
1244      BodyMatchResult::BodyMismatches(results) =>
1245        results.values().all(|m| m.is_empty()),
1246      _ => true
1247    }
1248  }
1249}
1250
1251/// Result of matching a request
1252#[derive(Debug, Clone, PartialEq)]
1253pub struct RequestMatchResult {
1254  /// Method match result
1255  pub method: Option<Mismatch>,
1256  /// Path match result
1257  pub path: Option<Vec<Mismatch>>,
1258  /// Body match result
1259  pub body: BodyMatchResult,
1260  /// Query parameter result
1261  pub query: HashMap<String, Vec<Mismatch>>,
1262  /// Headers result
1263  pub headers: HashMap<String, Vec<Mismatch>>
1264}
1265
1266impl RequestMatchResult {
1267  /// Returns all the mismatches
1268  pub fn mismatches(&self) -> Vec<Mismatch> {
1269    let mut m = vec![];
1270
1271    if let Some(ref mismatch) = self.method {
1272      m.push(mismatch.clone());
1273    }
1274    if let Some(ref mismatches) = self.path {
1275      m.extend_from_slice(mismatches.as_slice());
1276    }
1277    for mismatches in self.query.values() {
1278      m.extend_from_slice(mismatches.as_slice());
1279    }
1280    for mismatches in self.headers.values() {
1281      m.extend_from_slice(mismatches.as_slice());
1282    }
1283    m.extend_from_slice(self.body.mismatches().as_slice());
1284
1285    m
1286  }
1287
1288  /// Returns a score based on what was matched
1289  pub fn score(&self) -> i8 {
1290    let mut score = 0;
1291    if self.method.is_none() {
1292      score += 1;
1293    } else {
1294      score -= 1;
1295    }
1296    if self.path.is_none() {
1297      score += 1
1298    } else {
1299      score -= 1
1300    }
1301    for mismatches in self.query.values() {
1302      if mismatches.is_empty() {
1303        score += 1;
1304      } else {
1305        score -= 1;
1306      }
1307    }
1308    for mismatches in self.headers.values() {
1309      if mismatches.is_empty() {
1310        score += 1;
1311      } else {
1312        score -= 1;
1313      }
1314    }
1315    match &self.body {
1316      BodyMatchResult::BodyTypeMismatch { .. } => {
1317        score -= 1;
1318      },
1319      BodyMatchResult::BodyMismatches(results) => {
1320        for mismatches in results.values() {
1321          if mismatches.is_empty() {
1322            score += 1;
1323          } else {
1324            score -= 1;
1325          }
1326        }
1327      },
1328      _ => ()
1329    }
1330    score
1331  }
1332
1333  /// If all the things matched OK
1334  pub fn all_matched(&self) -> bool {
1335    self.method.is_none() && self.path.is_none() &&
1336      self.query.values().all(|m| m.is_empty()) &&
1337      self.headers.values().all(|m| m.is_empty()) &&
1338      self.body.all_matched()
1339  }
1340
1341  /// If there was a mismatch with the method or path
1342  pub fn method_or_path_mismatch(&self) -> bool {
1343    self.method.is_some() || self.path.is_some()
1344  }
1345
1346  fn method_mismatch(plan: &ExecutionPlan) -> Option<Mismatch> {
1347    let method_node = plan.fetch_node(&[":request", ":method"]).unwrap_or_default();
1348    let method = method_node.error()
1349      .map(|err| Mismatch::MethodMismatch {
1350        expected: "".to_string(),
1351        actual: "".to_string(),
1352        mismatch: err
1353      });
1354    method
1355  }
1356
1357  fn path_mismatch(plan: &ExecutionPlan) -> Option<Vec<Mismatch>> {
1358    let path_node = plan.fetch_node(&[":request", ":path"]).unwrap_or_default();
1359    let path_errors = path_node.errors().iter()
1360      .map(|err| Mismatch::PathMismatch {
1361        expected: "".to_string(),
1362        actual: "".to_string(),
1363        mismatch: err.clone()
1364      }).collect_vec();
1365    let path = if path_errors.is_empty() {
1366      None
1367    } else {
1368      Some(path_errors)
1369    };
1370    path
1371  }
1372
1373  fn query_mismatches(plan: &ExecutionPlan) -> HashMap<String, Vec<Mismatch>> {
1374    let query_node = plan.fetch_node(&[":request", ":query parameters"]).unwrap_or_default();
1375    let mut query = query_node.children.iter()
1376      .fold(hashmap! {}, |mut acc, child| {
1377        if let PlanNodeType::CONTAINER(label) = &child.node_type {
1378          let mismatches = child.errors().iter().map(|err| QueryMismatch {
1379            parameter: label.clone(),
1380            expected: "".to_string(),
1381            actual: "".to_string(),
1382            mismatch: err.clone(),
1383          }).collect_vec();
1384          acc.insert(label.clone(), mismatches);
1385        } else {
1386          let mismatches = child.errors().iter().map(|err| QueryMismatch {
1387            parameter: "".to_string(),
1388            expected: "".to_string(),
1389            actual: "".to_string(),
1390            mismatch: err.clone(),
1391          }).collect_vec();
1392          if !mismatches.is_empty() {
1393            acc.entry("".to_string())
1394              .and_modify(|entry| entry.extend_from_slice(&mismatches))
1395              .or_insert(vec![]);
1396          }
1397        };
1398        acc
1399      });
1400    let errors = query_node.child_errors(Terminator::CONTAINERS);
1401    if !errors.is_empty() {
1402      let mismatches = errors.iter()
1403        .map(|err| BodyMismatch {
1404          path: "".to_string(),
1405          expected: None,
1406          actual: None,
1407          mismatch: err.clone(),
1408        })
1409        .collect_vec();
1410      query.insert("".to_string(), mismatches);
1411    }
1412    query
1413  }
1414
1415  fn header_mismatches(plan: &ExecutionPlan) -> HashMap<String, Vec<Mismatch>> {
1416    let headers_node = plan.fetch_node(&[":request", ":headers"]).unwrap_or_default();
1417    let mut headers = headers_node.children.iter()
1418      .fold(hashmap! {}, |mut acc, child| {
1419        if let PlanNodeType::CONTAINER(label) = &child.node_type {
1420          let mismatches = child.errors().iter().map(|err| HeaderMismatch {
1421            key: label.clone(),
1422            expected: "".to_string(),
1423            actual: "".to_string(),
1424            mismatch: err.clone(),
1425          }).collect_vec();
1426          acc.insert(label.clone(), mismatches);
1427        } else {
1428          let mismatches = child.errors().iter().map(|err| HeaderMismatch {
1429            key: "".to_string(),
1430            expected: "".to_string(),
1431            actual: "".to_string(),
1432            mismatch: err.clone(),
1433          }).collect_vec();
1434          if !mismatches.is_empty() {
1435            acc.entry("".to_string())
1436              .and_modify(|entry| entry.extend_from_slice(&mismatches))
1437              .or_insert(vec![]);
1438          }
1439        };
1440        acc
1441      });
1442    let errors = headers_node.child_errors(Terminator::CONTAINERS);
1443    if !errors.is_empty() {
1444      let mismatches = errors.iter()
1445        .map(|err| BodyMismatch {
1446          path: "".to_string(),
1447          expected: None,
1448          actual: None,
1449          mismatch: err.clone(),
1450        })
1451        .collect_vec();
1452      headers.insert("".to_string(), mismatches);
1453    }
1454    headers
1455  }
1456
1457  fn body_mismatches(plan: ExecutionPlan) -> BodyMatchResult {
1458    let body_node = plan.fetch_node(&[":request", ":body"]).unwrap_or_default();
1459    let body = if body_node.clone().result.unwrap_or_default().is_truthy() {
1460      BodyMatchResult::Ok
1461    } else if body_node.is_empty() {
1462      match &body_node.clone().result {
1463        Some(NodeResult::ERROR(err)) => {
1464          let mismatch = BodyMismatch {
1465            path: "".to_string(),
1466            expected: None,
1467            actual: None,
1468            mismatch: err.clone()
1469          };
1470          BodyMatchResult::BodyMismatches(hashmap!{"".to_string() => vec![mismatch]})
1471        }
1472        _ => BodyMatchResult::Ok
1473      }
1474    } else {
1475      let first_error = body_node.error().unwrap_or_default();
1476      if first_error.to_lowercase().starts_with("body type error") {
1477        BodyMatchResult::BodyTypeMismatch {
1478          expected_type: "".to_string(),
1479          actual_type: "".to_string(),
1480          message: first_error.clone(),
1481          expected: None,
1482          actual: None,
1483        }
1484      } else {
1485        let mut body_mismatches = body_node.traverse_containers(hashmap! {}, |mut acc, label, node| {
1486          let errors = node.child_errors(Terminator::CONTAINERS);
1487          if !errors.is_empty() {
1488            let mismatches = errors.iter()
1489              .map(|err| BodyMismatch {
1490                path: label.clone(),
1491                expected: None,
1492                actual: None,
1493                mismatch: err.clone(),
1494              })
1495              .collect_vec();
1496            acc.insert(label.clone(), mismatches);
1497          }
1498          acc
1499        });
1500        if !first_error.is_empty() {
1501          let mismatches = body_mismatches.entry("".to_string())
1502            .or_insert_with(|| vec![]);
1503          mismatches.push(BodyMismatch {
1504            path: "".to_string(),
1505            expected: None,
1506            actual: None,
1507            mismatch: first_error.clone()
1508          });
1509        }
1510        BodyMatchResult::BodyMismatches(body_mismatches)
1511      }
1512    };
1513    body
1514  }
1515}
1516
1517impl From<ExecutionPlan> for RequestMatchResult {
1518  fn from(plan: ExecutionPlan) -> Self {
1519    let method = Self::method_mismatch(&plan);
1520    let path = Self::path_mismatch(&plan);
1521    let query = Self::query_mismatches(&plan);
1522    let headers = Self::header_mismatches(&plan);
1523    let body = Self::body_mismatches(plan);
1524    RequestMatchResult {
1525      method,
1526      path,
1527      body,
1528      query,
1529      headers
1530    }
1531  }
1532}
1533
1534/// Enum that defines the configuration options for performing a match.
1535#[derive(Debug, Clone, Copy, PartialEq)]
1536pub enum DiffConfig {
1537    /// If unexpected keys are allowed and ignored during matching.
1538    AllowUnexpectedKeys,
1539    /// If unexpected keys cause a mismatch.
1540    NoUnexpectedKeys
1541}
1542
1543/// Matches the actual text body to the expected one.
1544pub fn match_text(expected: &Option<Bytes>, actual: &Option<Bytes>, context: &dyn MatchingContext) -> Result<(), Vec<Mismatch>> {
1545  let path = DocPath::root();
1546  if context.matcher_is_defined(&path) {
1547    let mut mismatches = vec![];
1548    let empty = Bytes::default();
1549    let expected_str = match from_utf8(expected.as_ref().unwrap_or(&empty)) {
1550      Ok(expected) => expected,
1551      Err(err) => {
1552        mismatches.push(Mismatch::BodyMismatch {
1553          path: "$".to_string(),
1554          expected: expected.clone(),
1555          actual: actual.clone(),
1556          mismatch: format!("Could not parse expected value as UTF-8 text: {}", err)
1557        });
1558        ""
1559      }
1560    };
1561    let actual_str = match from_utf8(actual.as_ref().unwrap_or(&empty)) {
1562      Ok(actual) => actual,
1563      Err(err) => {
1564        mismatches.push(Mismatch::BodyMismatch {
1565          path: "$".to_string(),
1566          expected: expected.clone(),
1567          actual: actual.clone(),
1568          mismatch: format!("Could not parse actual value as UTF-8 text: {}", err)
1569        });
1570        ""
1571      }
1572    };
1573    if let Err(messages) = match_values(&path, &context.select_best_matcher(&path), expected_str, actual_str) {
1574      for message in messages {
1575        mismatches.push(Mismatch::BodyMismatch {
1576          path: "$".to_string(),
1577          expected: expected.clone(),
1578          actual: actual.clone(),
1579          mismatch: message.clone()
1580        })
1581      }
1582    };
1583    if mismatches.is_empty() {
1584      Ok(())
1585    } else {
1586      Err(mismatches)
1587    }
1588  } else if expected != actual {
1589    let expected = expected.clone().unwrap_or_default();
1590    let actual = actual.clone().unwrap_or_default();
1591    let e = String::from_utf8_lossy(&expected);
1592    let a = String::from_utf8_lossy(&actual);
1593    let mismatch = format!("Expected body '{}' to match '{}' using equality but did not match", e, a);
1594    Err(vec![
1595      Mismatch::BodyMismatch {
1596        path: "$".to_string(),
1597        expected: Some(expected.clone()),
1598        actual: Some(actual.clone()),
1599        mismatch
1600      }
1601    ])
1602  } else {
1603    Ok(())
1604  }
1605}
1606
1607/// Matches the actual request method to the expected one.
1608pub fn match_method(expected: &str, actual: &str) -> Result<(), Mismatch> {
1609  if expected.to_lowercase() != actual.to_lowercase() {
1610    Err(Mismatch::MethodMismatch { expected: expected.to_string(), actual: actual.to_string(), mismatch: "".to_string() })
1611  } else {
1612    Ok(())
1613  }
1614}
1615
1616/// Matches the actual request path to the expected one.
1617pub fn match_path(expected: &str, actual: &str, context: &(dyn MatchingContext + Send + Sync)) -> Result<(), Vec<Mismatch>> {
1618  let path = DocPath::empty();
1619  let matcher_result = if context.matcher_is_defined(&path) {
1620    match_values(&path, &context.select_best_matcher(&path), expected.to_string(), actual.to_string())
1621  } else {
1622    expected.matches_with(actual, &MatchingRule::Equality, false).map_err(|err| vec![err])
1623      .map_err(|errors| errors.iter().map(|err| err.to_string()).collect())
1624  };
1625  matcher_result.map_err(|messages| messages.iter().map(|message| {
1626    Mismatch::PathMismatch {
1627      expected: expected.to_string(),
1628      actual: actual.to_string(), mismatch: message.clone()
1629    }
1630  }).collect())
1631}
1632
1633/// Matches the actual query parameters to the expected ones.
1634pub fn match_query(
1635  expected: Option<HashMap<String, Vec<Option<String>>>>,
1636  actual: Option<HashMap<String, Vec<Option<String>>>>,
1637  context: &(dyn MatchingContext + Send + Sync)
1638) -> HashMap<String, Vec<Mismatch>> {
1639  match (actual, expected) {
1640    (Some(aqm), Some(eqm)) => match_query_maps(eqm, aqm, context),
1641    (Some(aqm), None) => aqm.iter().map(|(key, value)| {
1642      let actual_value = value.iter().map(|v| v.clone().unwrap_or_default()).collect_vec();
1643      (key.clone(), vec![Mismatch::QueryMismatch {
1644        parameter: key.clone(),
1645        expected: "".to_string(),
1646        actual: format!("{:?}", actual_value),
1647        mismatch: format!("Unexpected query parameter '{}' received", key)
1648      }])
1649    }).collect(),
1650    (None, Some(eqm)) => eqm.iter().map(|(key, value)| {
1651      let expected_value = value.iter().map(|v| v.clone().unwrap_or_default()).collect_vec();
1652      (key.clone(), vec![Mismatch::QueryMismatch {
1653        parameter: key.clone(),
1654        expected: format!("{:?}", expected_value),
1655        actual: "".to_string(),
1656        mismatch: format!("Expected query parameter '{}' but was missing", key)
1657      }])
1658    }).collect(),
1659    (None, None) => hashmap!{}
1660  }
1661}
1662
1663fn group_by<I, F, K>(items: I, f: F) -> HashMap<K, Vec<I::Item>>
1664  where I: IntoIterator, F: Fn(&I::Item) -> K, K: Eq + Hash {
1665  let mut m = hashmap!{};
1666  for item in items {
1667    let key = f(&item);
1668    let values = m.entry(key).or_insert_with(Vec::new);
1669    values.push(item);
1670  }
1671  m
1672}
1673
1674#[instrument(level = "trace", ret, skip_all)]
1675pub(crate) async fn compare_bodies(
1676  content_type: &ContentType,
1677  expected: &(dyn HttpPart + Send + Sync),
1678  actual: &(dyn HttpPart + Send + Sync),
1679  context: &(dyn MatchingContext + Send + Sync)
1680) -> BodyMatchResult {
1681  let mut mismatches = vec![];
1682
1683  trace!(?content_type, "Comparing bodies");
1684
1685  #[cfg(feature = "plugins")]
1686  {
1687    match find_content_matcher(content_type) {
1688      Some(matcher) => {
1689        debug!("Using content matcher {} for content type '{}'", matcher.catalogue_entry_key(), content_type);
1690        if matcher.is_core() {
1691          if let Err(m) = match matcher.catalogue_entry_key().as_str() {
1692            "core/content-matcher/form-urlencoded" => form_urlencoded::match_form_urlencoded(expected, actual, context),
1693            "core/content-matcher/json" => match_json(expected, actual, context),
1694            "core/content-matcher/multipart-form-data" => binary_utils::match_mime_multipart(expected, actual, context),
1695            "core/content-matcher/text" => match_text(&expected.body().value(), &actual.body().value(), context),
1696            "core/content-matcher/xml" => {
1697              #[cfg(feature = "xml")]
1698              {
1699                xml::match_xml(expected, actual, context)
1700              }
1701              #[cfg(not(feature = "xml"))]
1702              {
1703                warn!("Matching XML bodies requires the xml feature to be enabled");
1704                match_text(&expected.body().value(), &actual.body().value(), context)
1705              }
1706            },
1707            "core/content-matcher/binary" => binary_utils::match_octet_stream(expected, actual, context),
1708            _ => {
1709              warn!("There is no core content matcher for entry {}", matcher.catalogue_entry_key());
1710              match_text(&expected.body().value(), &actual.body().value(), context)
1711            }
1712          } {
1713            mismatches.extend_from_slice(&*m);
1714          }
1715        } else {
1716          trace!(plugin_name = matcher.plugin_name(),"Content matcher is provided via a plugin");
1717          let plugin_config = context.plugin_configuration().get(&matcher.plugin_name()).cloned();
1718          trace!("Plugin config = {:?}", plugin_config);
1719          if let Err(map) = matcher.match_contents(expected.body(), actual.body(), &context.matchers(),
1720                                                   context.config() == DiffConfig::AllowUnexpectedKeys, plugin_config).await {
1721            // TODO: group the mismatches by key
1722            for (_key, list) in map {
1723              for mismatch in list {
1724                mismatches.push(Mismatch::BodyMismatch {
1725                  path: mismatch.path.clone(),
1726                  expected: Some(Bytes::from(mismatch.expected)),
1727                  actual: Some(Bytes::from(mismatch.actual)),
1728                  mismatch: mismatch.mismatch.clone()
1729                });
1730              }
1731            }
1732          }
1733        }
1734      }
1735      None => {
1736        debug!("No content matcher defined for content type '{}', using core matcher implementation", content_type);
1737        mismatches.extend(compare_bodies_core(content_type, expected, actual, context));
1738      }
1739    }
1740  }
1741
1742  #[cfg(not(feature = "plugins"))]
1743  {
1744    mismatches.extend(compare_bodies_core(content_type, expected, actual, context));
1745  }
1746
1747  if mismatches.is_empty() {
1748    BodyMatchResult::Ok
1749  } else {
1750    BodyMatchResult::BodyMismatches(group_by(mismatches, |m| match m {
1751      Mismatch::BodyMismatch { path: m, ..} => m.to_string(),
1752      _ => String::default()
1753    }))
1754  }
1755}
1756
1757fn compare_bodies_core(
1758  content_type: &ContentType,
1759  expected: &(dyn HttpPart + Send + Sync),
1760  actual: &(dyn HttpPart + Send + Sync),
1761  context: &(dyn MatchingContext + Send + Sync)
1762) -> Vec<Mismatch> {
1763  let mut mismatches = vec![];
1764  match BODY_MATCHERS.iter().find(|mt| mt.0(content_type)) {
1765    Some(match_fn) => {
1766      debug!("Using body matcher for content type '{}'", content_type);
1767      if let Err(m) = match_fn.1(expected, actual, context) {
1768        mismatches.extend_from_slice(&*m);
1769      }
1770    },
1771    None => {
1772      debug!("No body matcher defined for content type '{}', checking for a content type matcher", content_type);
1773      let path = DocPath::root();
1774      if context.matcher_is_defined(&path) && context.select_best_matcher(&path).rules
1775        .iter().any(|rule| if let MatchingRule::ContentType(_) = rule { true } else { false }) {
1776        debug!("Found a content type matcher");
1777        if let Err(m) = binary_utils::match_octet_stream(expected, actual, context) {
1778          mismatches.extend_from_slice(&*m);
1779        }
1780      } else {
1781        debug!("No body matcher defined for content type '{}', using plain text matcher", content_type);
1782        if let Err(m) = match_text(&expected.body().value(), &actual.body().value(), context) {
1783          mismatches.extend_from_slice(&*m);
1784        }
1785      }
1786    }
1787  };
1788  mismatches
1789}
1790
1791#[instrument(level = "trace", ret, skip_all, fields(%content_type, ?context))]
1792async fn match_body_content(
1793  content_type: &ContentType,
1794  expected: &(dyn HttpPart + Send + Sync),
1795  actual: &(dyn HttpPart + Send + Sync),
1796  context: &(dyn MatchingContext + Send + Sync)
1797) -> BodyMatchResult {
1798  let expected_body = expected.body();
1799  let actual_body = actual.body();
1800  match (expected_body, actual_body) {
1801    (&OptionalBody::Missing, _) => BodyMatchResult::Ok,
1802    (&OptionalBody::Null, &OptionalBody::Present(ref b, _, _)) => {
1803      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch { expected: None, actual: Some(b.clone()),
1804        mismatch: format!("Expected empty body but received {}", actual_body),
1805        path: s!("/")}]})
1806    },
1807    (&OptionalBody::Empty, &OptionalBody::Present(ref b, _, _)) => {
1808      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch { expected: None, actual: Some(b.clone()),
1809        mismatch: format!("Expected empty body but received {}", actual_body),
1810        path: s!("/")}]})
1811    },
1812    (&OptionalBody::Null, _) => BodyMatchResult::Ok,
1813    (&OptionalBody::Empty, _) => BodyMatchResult::Ok,
1814    (e, &OptionalBody::Missing) => {
1815      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch {
1816        expected: e.value(),
1817        actual: None,
1818        mismatch: format!("Expected body {} but was missing", e),
1819        path: s!("/")}]})
1820    },
1821    (e, &OptionalBody::Empty) => {
1822      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch {
1823        expected: e.value(),
1824        actual: None,
1825        mismatch: format!("Expected body {} but was empty", e),
1826        path: s!("/")}]})
1827    },
1828    (_, _) => compare_bodies(content_type, expected, actual, context).await
1829  }
1830}
1831
1832/// Matches the actual body to the expected one. This takes into account the content type of each.
1833pub async fn match_body(
1834  expected: &(dyn HttpPart + Send + Sync),
1835  actual: &(dyn HttpPart + Send + Sync),
1836  context: &(dyn MatchingContext + Send + Sync),
1837  header_context: &(dyn MatchingContext + Send + Sync)
1838) -> BodyMatchResult {
1839  let expected_content_type = expected.content_type().unwrap_or_default();
1840  let actual_content_type = actual.content_type().unwrap_or_default();
1841  debug!("expected content type = '{}', actual content type = '{}'", expected_content_type,
1842         actual_content_type);
1843  let content_type_matcher = header_context.select_best_matcher(&DocPath::root().join("content-type"));
1844  debug!("content type header matcher = '{:?}'", content_type_matcher);
1845  if expected_content_type.is_unknown() || actual_content_type.is_unknown() ||
1846    expected_content_type.is_equivalent_to(&actual_content_type) ||
1847    expected_content_type.is_equivalent_to(&actual_content_type.base_type()) ||
1848    (!content_type_matcher.is_empty() &&
1849      match_header_value("Content-Type", 0, expected_content_type.to_string().as_str(),
1850                         actual_content_type.to_string().as_str(), header_context, true
1851      ).is_ok()) {
1852    match_body_content(&expected_content_type, expected, actual, context).await
1853  } else if expected.body().is_present() {
1854    BodyMatchResult::BodyTypeMismatch {
1855      expected_type: expected_content_type.to_string(),
1856      actual_type: actual_content_type.to_string(),
1857      message: format!("Expected a body of '{}' but the actual content type was '{}'", expected_content_type,
1858                       actual_content_type),
1859      expected: expected.body().value(),
1860      actual: actual.body().value()
1861    }
1862  } else {
1863    BodyMatchResult::Ok
1864  }
1865}
1866
1867/// Matches the expected and actual requests
1868#[allow(unused_variables)]
1869pub async fn match_request<'a>(
1870  expected: HttpRequest,
1871  actual: HttpRequest,
1872  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>,
1873  interaction: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>
1874) -> anyhow::Result<RequestMatchResult> {
1875  debug!("comparing to expected {}", expected);
1876  debug!("     body: '{}'", expected.body.display_string());
1877  debug!("     matching_rules:\n{}", expected.matching_rules);
1878  debug!("     generators: {:?}", expected.generators);
1879
1880  #[allow(unused_mut, unused_assignments)] let mut plugin_data = hashmap!{};
1881  #[cfg(feature = "plugins")]
1882  {
1883    plugin_data = setup_plugin_config(pact, interaction, InteractionPart::Request);
1884  };
1885  trace!("plugin_data = {:?}", plugin_data);
1886
1887  let use_v2_engine = std::env::var("MATCHING_ENGINE")
1888    .map(|val| val.to_lowercase() == "v2")
1889    .unwrap_or(false);
1890  if use_v2_engine {
1891    let config = MatchingConfiguration {
1892      allow_unexpected_entries: false,
1893      .. MatchingConfiguration::init_from_env()
1894    };
1895    let mut context = PlanMatchingContext {
1896      pact: pact.as_v4_pact().unwrap_or_default(),
1897      interaction: interaction.as_v4().unwrap(),
1898      matching_rules: Default::default(),
1899      config
1900    };
1901
1902    let plan = build_request_plan(&expected, &mut context)?;
1903    let executed_plan = execute_request_plan(&plan, &actual, &mut context)?;
1904
1905    if config.log_executed_plan {
1906      debug!("config = {:?}", config);
1907      debug!("\n{}", executed_plan.pretty_form());
1908    }
1909    if config.log_plan_summary {
1910      info!("\n{}", executed_plan.generate_summary(config.coloured_output));
1911    }
1912    Ok(executed_plan.into())
1913  } else {
1914    let path_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1915      &expected.matching_rules.rules_for_category("path").unwrap_or_default(),
1916      &plugin_data);
1917    let body_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1918      &expected.matching_rules.rules_for_category("body").unwrap_or_default(),
1919      &plugin_data);
1920    let query_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1921      &expected.matching_rules.rules_for_category("query").unwrap_or_default(),
1922      &plugin_data);
1923    let header_context = HeaderMatchingContext::new(
1924      &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1925        &expected.matching_rules.rules_for_category("header").unwrap_or_default(),
1926        &plugin_data
1927      )
1928    );
1929    let result = RequestMatchResult {
1930      method: match_method(&expected.method, &actual.method).err(),
1931      path: match_path(&expected.path, &actual.path, &path_context).err(),
1932      body: match_body(&expected, &actual, &body_context, &header_context).await,
1933      query: match_query(expected.query, actual.query, &query_context),
1934      headers: match_headers(expected.headers, actual.headers, &header_context)
1935    };
1936
1937    debug!("--> Mismatches: {:?}", result.mismatches());
1938    Ok(result)
1939  }
1940}
1941
1942/// Matches the actual response status to the expected one.
1943#[instrument(level = "trace")]
1944pub fn match_status(expected: u16, actual: u16, context: &dyn MatchingContext) -> Result<(), Vec<Mismatch>> {
1945  let path = DocPath::empty();
1946  let result = if context.matcher_is_defined(&path) {
1947    match_values(&path, &context.select_best_matcher(&path), expected, actual)
1948      .map_err(|messages| messages.iter().map(|message| {
1949        Mismatch::StatusMismatch {
1950          expected,
1951          actual,
1952          mismatch: message.clone()
1953        }
1954      }).collect())
1955  } else if expected != actual {
1956    Err(vec![Mismatch::StatusMismatch {
1957      expected,
1958      actual,
1959      mismatch: format!("expected {} but was {}", expected, actual)
1960    }])
1961  } else {
1962    Ok(())
1963  };
1964  trace!(?result, "matching response status");
1965  result
1966}
1967
1968/// Matches the actual and expected responses.
1969#[allow(unused_variables)]
1970pub async fn match_response<'a>(
1971  expected: HttpResponse,
1972  actual: HttpResponse,
1973  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>,
1974  interaction: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>
1975) -> Vec<Mismatch> {
1976  let mut mismatches = vec![];
1977
1978  debug!("comparing to expected response: {}", expected);
1979  #[allow(unused_mut, unused_assignments)] let mut plugin_data = hashmap!{};
1980  #[cfg(feature = "plugins")]
1981  {
1982    plugin_data = setup_plugin_config(pact, interaction, InteractionPart::Response);
1983  };
1984  trace!("plugin_data = {:?}", plugin_data);
1985
1986  let status_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1987    &expected.matching_rules.rules_for_category("status").unwrap_or_default(),
1988    &plugin_data);
1989  let body_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1990    &expected.matching_rules.rules_for_category("body").unwrap_or_default(),
1991    &plugin_data);
1992  let header_context = HeaderMatchingContext::new(
1993    &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1994      &expected.matching_rules.rules_for_category("header").unwrap_or_default(),
1995      &plugin_data
1996    )
1997  );
1998
1999  mismatches.extend_from_slice(match_body(&expected, &actual, &body_context, &header_context).await
2000    .mismatches().as_slice());
2001  if let Err(m) = match_status(expected.status, actual.status, &status_context) {
2002    mismatches.extend_from_slice(&m);
2003  }
2004  let result = match_headers(expected.headers, actual.headers,
2005                             &header_context);
2006  for values in result.values() {
2007    mismatches.extend_from_slice(values.as_slice());
2008  }
2009
2010    trace!(?mismatches, "match response");
2011
2012  mismatches
2013}
2014
2015/// Matches the actual message contents to the expected one. This takes into account the content type of each.
2016#[instrument(level = "trace")]
2017pub async fn match_message_contents(
2018  expected: &MessageContents,
2019  actual: &MessageContents,
2020  context: &(dyn MatchingContext + Send + Sync)
2021) -> Result<(), Vec<Mismatch>> {
2022  let expected_content_type = expected.message_content_type().unwrap_or_default();
2023  let actual_content_type = actual.message_content_type().unwrap_or_default();
2024  debug!("expected content type = '{}', actual content type = '{}'", expected_content_type,
2025         actual_content_type);
2026  if expected_content_type.is_equivalent_to(&actual_content_type) {
2027    let result = match_body_content(&expected_content_type, expected, actual, context).await;
2028    match result {
2029      BodyMatchResult::BodyTypeMismatch { expected_type, actual_type, message, expected, actual } => {
2030        Err(vec![ Mismatch::BodyTypeMismatch {
2031          expected: expected_type,
2032          actual: actual_type,
2033          mismatch: message,
2034          expected_body: expected,
2035          actual_body: actual
2036        } ])
2037      },
2038      BodyMatchResult::BodyMismatches(results) => {
2039        Err(results.values().flat_map(|values| values.iter().cloned()).collect())
2040      },
2041      _ => Ok(())
2042    }
2043  } else if expected.contents.is_present() {
2044    Err(vec![ Mismatch::BodyTypeMismatch {
2045      expected: expected_content_type.to_string(),
2046      actual: actual_content_type.to_string(),
2047      mismatch: format!("Expected message with content type {} but was {}",
2048                        expected_content_type, actual_content_type),
2049      expected_body: expected.contents.value(),
2050      actual_body: actual.contents.value()
2051    } ])
2052  } else {
2053    Ok(())
2054  }
2055}
2056
2057/// Matches the actual message metadata to the expected one.
2058#[instrument(level = "trace")]
2059pub fn match_message_metadata(
2060  expected: &MessageContents,
2061  actual: &MessageContents,
2062  context: &dyn MatchingContext
2063) -> HashMap<String, Vec<Mismatch>> {
2064  debug!("Matching message metadata");
2065  let mut result = hashmap!{};
2066  let expected_metadata = &expected.metadata;
2067  let actual_metadata = &actual.metadata;
2068  debug!("Matching message metadata. Expected '{:?}', Actual '{:?}'", expected_metadata, actual_metadata);
2069
2070  if !expected_metadata.is_empty() || context.config() == DiffConfig::NoUnexpectedKeys {
2071    for (key, value) in expected_metadata {
2072      match actual_metadata.get(key) {
2073        Some(actual_value) => {
2074          result.insert(key.clone(), match_metadata_value(key, value,
2075            actual_value, context).err().unwrap_or_default());
2076        },
2077        None => {
2078          result.insert(key.clone(), vec![Mismatch::MetadataMismatch { key: key.clone(),
2079            expected: json_to_string(&value),
2080            actual: "".to_string(),
2081            mismatch: format!("Expected message metadata '{}' but was missing", key) }]);
2082        }
2083      }
2084    }
2085  }
2086  result
2087}
2088
2089#[instrument(level = "trace")]
2090fn match_metadata_value(
2091  key: &str,
2092  expected: &Value,
2093  actual: &Value,
2094  context: &dyn MatchingContext
2095) -> Result<(), Vec<Mismatch>> {
2096  debug!("Comparing metadata values for key '{}'", key);
2097  let path = DocPath::root().join(key);
2098  let matcher_result = if context.matcher_is_defined(&path) {
2099    match_values(&path, &context.select_best_matcher(&path), expected, actual)
2100  } else if key.to_ascii_lowercase() == "contenttype" || key.to_ascii_lowercase() == "content-type" {
2101    debug!("Comparing message context type '{}' => '{}'", expected, actual);
2102    headers::match_parameter_header(expected.as_str().unwrap_or_default(), actual.as_str().unwrap_or_default(),
2103      key, "metadata", 0, true)
2104  } else {
2105    expected.matches_with(actual, &MatchingRule::Equality, false).map_err(|err| vec![err.to_string()])
2106  };
2107  matcher_result.map_err(|messages| {
2108    messages.iter().map(|message| {
2109      Mismatch::MetadataMismatch {
2110        key: key.to_string(),
2111        expected: expected.to_string(),
2112        actual: actual.to_string(),
2113        mismatch: format!("Expected metadata key '{}' to have value '{}' but was '{}' - {}", key, expected, actual, message)
2114      }
2115    }).collect()
2116  })
2117}
2118
2119/// Matches the actual and expected messages.
2120#[allow(unused_variables)]
2121pub async fn match_message<'a>(
2122  expected: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2123  actual: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2124  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>) -> Vec<Mismatch> {
2125  let mut mismatches = vec![];
2126
2127  if expected.is_message() && actual.is_message() {
2128    debug!("comparing to expected message: {:?}", expected);
2129    let expected_message = expected.as_message().unwrap();
2130    let actual_message = actual.as_message().unwrap();
2131
2132    let matching_rules = &expected_message.matching_rules;
2133    #[allow(unused_mut, unused_assignments)] let mut plugin_data = hashmap!{};
2134    #[cfg(feature = "plugins")]
2135    {
2136      plugin_data = setup_plugin_config(pact, expected, InteractionPart::None);
2137    };
2138
2139    let body_context = if expected.is_v4() {
2140      CoreMatchingContext {
2141        matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2142        config: DiffConfig::AllowUnexpectedKeys,
2143        matching_spec: PactSpecification::V4,
2144        plugin_configuration: plugin_data.clone()
2145      }
2146    } else {
2147      CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2148                           &matching_rules.rules_for_category("body").unwrap_or_default(),
2149                           &plugin_data)
2150    };
2151
2152    let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2153                                                &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2154                                                &plugin_data);
2155    let contents = match_message_contents(&expected_message.as_message_content(), &actual_message.as_message_content(), &body_context).await;
2156
2157    mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2158    for values in match_message_metadata(&expected_message.as_message_content(), &actual_message.as_message_content(), &metadata_context).values() {
2159      mismatches.extend_from_slice(values.as_slice());
2160    }
2161  } else {
2162    mismatches.push(Mismatch::BodyTypeMismatch {
2163      expected: "message".into(),
2164      actual: actual.type_of(),
2165      mismatch: format!("Cannot compare a {} with a {}", expected.type_of(), actual.type_of()),
2166      expected_body: None,
2167      actual_body: None
2168    });
2169  }
2170
2171  mismatches
2172}
2173
2174/// Matches synchronous request/response messages
2175pub async fn match_sync_message<'a>(expected: SynchronousMessage, actual: SynchronousMessage, pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>) -> Vec<Mismatch> {
2176  let mut mismatches = match_sync_message_request(&expected, &actual, pact).await;
2177  let response_result = match_sync_message_response(&expected, &expected.response, &actual.response, pact).await;
2178  mismatches.extend_from_slice(&*response_result);
2179  mismatches
2180}
2181
2182/// Match the request part of a synchronous request/response message
2183#[allow(unused_variables)]
2184pub async fn match_sync_message_request<'a>(
2185  expected: &SynchronousMessage,
2186  actual: &SynchronousMessage,
2187  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>
2188) -> Vec<Mismatch> {
2189  debug!("comparing to expected message request: {:?}", expected);
2190
2191  let matching_rules = &expected.request.matching_rules;
2192  #[allow(unused_mut, unused_assignments)] let mut plugin_data = hashmap!{};
2193  #[cfg(feature = "plugins")]
2194  {
2195    plugin_data = setup_plugin_config(pact, &expected.boxed(), InteractionPart::None);
2196  };
2197
2198  let body_context = CoreMatchingContext {
2199    matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2200    config: DiffConfig::AllowUnexpectedKeys,
2201    matching_spec: PactSpecification::V4,
2202    plugin_configuration: plugin_data.clone()
2203  };
2204
2205  let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2206                                              &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2207                                              &plugin_data);
2208  let contents = match_message_contents(&expected.request, &actual.request, &body_context).await;
2209
2210  let mut mismatches = vec![];
2211  mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2212  for values in match_message_metadata(&expected.request, &actual.request, &metadata_context).values() {
2213    mismatches.extend_from_slice(values.as_slice());
2214  }
2215  mismatches
2216}
2217
2218/// Match the response part of a synchronous request/response message
2219#[allow(unused_variables)]
2220pub async fn match_sync_message_response<'a>(
2221  expected: &SynchronousMessage,
2222  expected_responses: &[MessageContents],
2223  actual_responses: &[MessageContents],
2224  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>
2225) -> Vec<Mismatch> {
2226  debug!("comparing to expected message responses: {:?}", expected_responses);
2227
2228  let mut mismatches = vec![];
2229
2230  if expected_responses.len() != actual_responses.len() {
2231    if !expected_responses.is_empty() && actual_responses.is_empty() {
2232      mismatches.push(Mismatch::BodyTypeMismatch {
2233        expected: "message response".into(),
2234        actual: "".into(),
2235        mismatch: "Expected a message with a response, but the actual response was empty".into(),
2236        expected_body: None,
2237        actual_body: None
2238      });
2239    } else if !expected_responses.is_empty() {
2240      mismatches.push(Mismatch::BodyTypeMismatch {
2241        expected: "message response".into(),
2242        actual: "".into(),
2243        mismatch: format!("Expected a message with {} responses, but the actual response had {}",
2244                          expected_responses.len(), actual_responses.len()),
2245        expected_body: None,
2246        actual_body: None
2247      });
2248    }
2249  } else {
2250    #[allow(unused_mut, unused_assignments)] let mut plugin_data = hashmap!{};
2251    #[cfg(feature = "plugins")]
2252    {
2253      plugin_data = setup_plugin_config(pact, &expected.boxed(), InteractionPart::None);
2254    };
2255    for (expected_response, actual_response) in expected_responses.iter().zip(actual_responses) {
2256      let matching_rules = &expected_response.matching_rules;
2257      let body_context = CoreMatchingContext {
2258        matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2259        config: DiffConfig::AllowUnexpectedKeys,
2260        matching_spec: PactSpecification::V4,
2261        plugin_configuration: plugin_data.clone()
2262      };
2263
2264      let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2265                                                  &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2266                                                  &plugin_data);
2267      let contents = match_message_contents(expected_response, actual_response, &body_context).await;
2268
2269      mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2270      for values in match_message_metadata(expected_response, actual_response, &metadata_context).values() {
2271        mismatches.extend_from_slice(values.as_slice());
2272      }
2273    }
2274  }
2275  mismatches
2276}
2277
2278/// Generates the request by applying any defined generators
2279// TODO: Need to pass in any plugin data
2280#[instrument(level = "trace")]
2281pub async fn generate_request(request: &HttpRequest, mode: &GeneratorTestMode, context: &HashMap<&str, Value>) -> HttpRequest {
2282  trace!(?request, ?mode, ?context, "generate_request");
2283  let mut request = request.clone();
2284
2285  let generators = request.build_generators(&GeneratorCategory::PATH);
2286  if !generators.is_empty() {
2287    debug!("Applying path generator...");
2288    apply_generators(mode, &generators, &mut |_, generator| {
2289      if let Ok(v) = generator.generate_value(&request.path, context, &DefaultVariantMatcher.boxed()) {
2290        request.path = v;
2291      }
2292    });
2293  }
2294
2295  let generators = request.build_generators(&GeneratorCategory::HEADER);
2296  if !generators.is_empty() {
2297    debug!("Applying header generators...");
2298    apply_generators(mode, &generators, &mut |key, generator| {
2299      if let Some(header) = key.first_field() {
2300        if let Some(ref mut headers) = request.headers {
2301          if headers.contains_key(header) {
2302            if let Ok(v) = generator.generate_value(&headers.get(header).unwrap().clone(), context, &DefaultVariantMatcher.boxed()) {
2303              headers.insert(header.to_string(), v);
2304            }
2305          } else {
2306            if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2307              headers.insert(header.to_string(), vec![ v.to_string() ]);
2308            }
2309          }
2310        } else {
2311          if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2312            request.headers = Some(hashmap!{
2313              header.to_string() => vec![ v.to_string() ]
2314            })
2315          }
2316        }
2317      }
2318    });
2319  }
2320
2321  let generators = request.build_generators(&GeneratorCategory::QUERY);
2322  if !generators.is_empty() {
2323    debug!("Applying query generators...");
2324    apply_generators(mode, &generators, &mut |key, generator| {
2325      if let Some(param) = key.first_field() {
2326        if let Some(ref mut parameters) = request.query {
2327          if let Some(parameter) = parameters.get_mut(param) {
2328            let mut generated = parameter.clone();
2329            for (index, val) in parameter.iter().enumerate() {
2330              let value = val.clone().unwrap_or_default();
2331              if let Ok(v) = generator.generate_value(&value, context, &DefaultVariantMatcher.boxed()) {
2332                generated[index] = Some(v);
2333              }
2334            }
2335            *parameter = generated;
2336          } else if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2337            parameters.insert(param.to_string(), vec![ Some(v.to_string()) ]);
2338          }
2339        } else if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2340          request.query = Some(hashmap!{
2341            param.to_string() => vec![ Some(v.to_string()) ]
2342          })
2343        }
2344      }
2345    });
2346  }
2347
2348  let generators = request.build_generators(&GeneratorCategory::BODY);
2349  if !generators.is_empty() && request.body.is_present() {
2350    debug!("Applying body generators...");
2351    match generators_process_body(mode, &request.body, request.content_type(),
2352                                  context, &generators, &DefaultVariantMatcher {}, &vec![], &hashmap!{}).await {
2353      Ok(body) => request.body = body,
2354      Err(err) => error!("Failed to generate the body, will use the original: {}", err)
2355    }
2356  }
2357
2358  request
2359}
2360
2361/// Generates the response by applying any defined generators
2362// TODO: Need to pass in any plugin data
2363pub async fn generate_response(response: &HttpResponse, mode: &GeneratorTestMode, context: &HashMap<&str, Value>) -> HttpResponse {
2364  trace!(?response, ?mode, ?context, "generate_response");
2365  let mut response = response.clone();
2366  let generators = response.build_generators(&GeneratorCategory::STATUS);
2367  if !generators.is_empty() {
2368    debug!("Applying status generator...");
2369    apply_generators(mode, &generators, &mut |_, generator| {
2370      if let Ok(v) = generator.generate_value(&response.status, context, &DefaultVariantMatcher.boxed()) {
2371        debug!("Generated value for status: {}", v);
2372        response.status = v;
2373      }
2374    });
2375  }
2376  let generators = response.build_generators(&GeneratorCategory::HEADER);
2377  if !generators.is_empty() {
2378    debug!("Applying header generators...");
2379    apply_generators(mode, &generators, &mut |key, generator| {
2380      if let Some(header) = key.first_field() {
2381        if let Some(ref mut headers) = response.headers {
2382          if headers.contains_key(header) {
2383            if let Ok(v) = generator.generate_value(&headers.get(header).unwrap().clone(), context, &DefaultVariantMatcher.boxed()) {
2384              headers.insert(header.to_string(), v);
2385            }
2386          } else {
2387            if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2388              headers.insert(header.to_string(), vec![ v.to_string() ]);
2389            }
2390          }
2391        } else {
2392          if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2393            response.headers = Some(hashmap!{
2394              header.to_string() => vec![ v.to_string() ]
2395            })
2396          }
2397        }
2398      }
2399    });
2400  }
2401  let generators = response.build_generators(&GeneratorCategory::BODY);
2402  if !generators.is_empty() && response.body.is_present() {
2403    debug!("Applying body generators...");
2404    match generators_process_body(mode, &response.body, response.content_type(),
2405      context, &generators, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await {
2406      Ok(body) => response.body = body,
2407      Err(err) => error!("Failed to generate the body, will use the original: {}", err)
2408    }
2409  }
2410  response
2411}
2412
2413/// Matches the request part of the interaction
2414pub async fn match_interaction_request(
2415  expected: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2416  actual: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2417  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2418  _spec_version: &PactSpecification
2419) -> anyhow::Result<RequestMatchResult> {
2420  if let Some(http_interaction) = expected.as_v4_http() {
2421    let request = actual.as_v4_http()
2422      .ok_or_else(|| anyhow!("Could not unpack actual request as a V4 Http Request"))?.request;
2423    match_request(http_interaction.request, request, &pact, &expected).await
2424  } else {
2425    Err(anyhow!("match_interaction_request must be called with HTTP request/response interactions, got {}", expected.type_of()))
2426  }
2427}
2428
2429/// Matches the response part of the interaction
2430pub async fn match_interaction_response(
2431  expected: Box<dyn Interaction + Sync + RefUnwindSafe>,
2432  actual: Box<dyn Interaction + Sync + RefUnwindSafe>,
2433  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2434  _spec_version: &PactSpecification
2435) -> anyhow::Result<Vec<Mismatch>> {
2436  if let Some(expected) = expected.as_v4_http() {
2437    let expected_response = expected.response.clone();
2438    let expected = expected.boxed();
2439    let response = actual.as_v4_http()
2440      .ok_or_else(|| anyhow!("Could not unpack actual response as a V4 Http Response"))?.response;
2441    Ok(match_response(expected_response, response, &pact, &expected).await)
2442  } else {
2443    Err(anyhow!("match_interaction_response must be called with HTTP request/response interactions, got {}", expected.type_of()))
2444  }
2445}
2446
2447/// Matches an interaction
2448pub async fn match_interaction(
2449  expected: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2450  actual: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2451  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2452  _spec_version: &PactSpecification
2453) -> anyhow::Result<Vec<Mismatch>> {
2454  if let Some(expected) = expected.as_v4_http() {
2455    let expected_request = expected.request.clone();
2456    let expected_response = expected.response.clone();
2457    let expected = expected.boxed();
2458    let request = actual.as_v4_http()
2459      .ok_or_else(|| anyhow!("Could not unpack actual request as a V4 Http Request"))?.request;
2460    let request_result = match_request(expected_request, request, &pact, &expected).await?;
2461    let response = actual.as_v4_http()
2462      .ok_or_else(|| anyhow!("Could not unpack actual response as a V4 Http Response"))?.response;
2463    let response_result = match_response(expected_response, response, &pact, &expected).await;
2464    let mut mismatches = request_result.mismatches();
2465    mismatches.extend_from_slice(&*response_result);
2466    Ok(mismatches)
2467  } else if expected.is_message() || expected.is_v4() {
2468    Ok(match_message(&expected, &actual, &pact).await)
2469  } else {
2470    Err(anyhow!("match_interaction must be called with either an HTTP request/response interaction or a Message, got {}", expected.type_of()))
2471  }
2472}
2473
2474#[cfg(test)]
2475mod tests;
2476#[cfg(test)]
2477mod generator_tests;