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")] #[cfg(not(target_family = "wasm"))] use pact_plugin_driver::catalogue_manager::find_content_matcher;
371#[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))] 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  body_mismatches,
391  build_request_plan,
392  build_response_plan,
393  execute_request_plan,
394  execute_response_plan,
395  ExecutionPlan,
396  header_mismatches,
397  method_mismatch,
398  path_mismatch,
399  query_mismatches
400};
401use crate::engine::context::{MatchingConfiguration, PlanMatchingContext};
402use crate::generators::bodies::generators_process_body;
403use crate::generators::DefaultVariantMatcher;
404use crate::headers::{match_header_value, match_headers};
405#[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))] use crate::json::match_json;
406use crate::matchingrules::{DisplayForMismatch, DoMatch, match_values, Matches};
407#[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))] use crate::plugin_support::{InteractionPart, setup_plugin_config};
408use crate::query::match_query_maps;
409
410/// Simple macro to convert a string slice to a `String` struct.
411#[macro_export]
412macro_rules! s {
413    ($e:expr) => ($e.to_string())
414}
415
416/// Version of the library
417pub const PACT_RUST_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
418
419pub mod json;
420pub mod matchingrules;
421#[cfg(not(target_family = "wasm"))] pub mod metrics;
422pub mod generators;
423pub mod engine;
424
425#[cfg(feature = "xml")] mod xml;
426pub mod binary_utils;
427pub mod headers;
428pub mod query;
429pub mod form_urlencoded;
430#[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))] mod plugin_support;
431
432#[cfg(not(feature = "plugins"))]
433#[derive(Clone, Debug, PartialEq)]
434/// Stub for when plugins feature is not enabled
435pub struct PluginInteractionConfig {}
436
437/// Context used to apply matching logic
438pub trait MatchingContext: Debug {
439  /// If there is a matcher defined at the path in this context
440  fn matcher_is_defined(&self, path: &DocPath) -> bool;
441
442  /// Selected the best matcher from the context for the given path
443  fn select_best_matcher(&self, path: &DocPath) -> RuleList;
444
445  /// If there is a type matcher defined at the path in this context
446  fn type_matcher_defined(&self, path: &DocPath) -> bool;
447
448  /// If there is a values matcher defined at the path in this context
449  fn values_matcher_defined(&self, path: &DocPath) -> bool;
450
451  /// If a matcher defined at the path (ignoring parents)
452  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool;
453
454  /// Matches the keys of the expected and actual maps
455  fn match_keys(&self, path: &DocPath, expected: &BTreeSet<String>, actual: &BTreeSet<String>) -> Result<(), Vec<CommonMismatch>>;
456
457  /// Returns the plugin configuration associated with the context
458  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
459  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig>;
460
461  /// Returns the matching rules for the matching context
462  fn matchers(&self) -> &MatchingRuleCategory;
463
464  /// Configuration to apply when matching with the context
465  fn config(&self) -> DiffConfig;
466
467  /// Clones the current context with the provided matching rules
468  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync>;
469}
470
471#[derive(Debug, Clone)]
472/// Core implementation of a matching context
473pub struct CoreMatchingContext {
474  /// Matching rules that apply when matching with the context
475  pub matchers: MatchingRuleCategory,
476  /// Configuration to apply when matching with the context
477  pub config: DiffConfig,
478  /// Specification version to apply when matching with the context
479  pub matching_spec: PactSpecification,
480  /// Any plugin configuration available for the interaction
481  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
482  pub plugin_configuration: HashMap<String, PluginInteractionConfig>
483}
484
485impl CoreMatchingContext {
486  /// Creates a new context with the given config and matching rules
487  pub fn new(
488    config: DiffConfig,
489    matchers: &MatchingRuleCategory,
490    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
491    plugin_configuration: &HashMap<String, PluginInteractionConfig>
492  ) -> Self {
493    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
494    {
495      CoreMatchingContext {
496        matchers: matchers.clone(),
497        config,
498        plugin_configuration: plugin_configuration.clone(),
499        ..CoreMatchingContext::default()
500      }
501    }
502
503    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
504    {
505      CoreMatchingContext {
506        matchers: matchers.clone(),
507        config,
508        ..CoreMatchingContext::default()
509      }
510    }
511  }
512
513  /// Creates a new empty context with the given config
514  pub fn with_config(config: DiffConfig) -> Self {
515    CoreMatchingContext {
516      config,
517      .. CoreMatchingContext::default()
518    }
519  }
520
521  fn matchers_for_exact_path(&self, path: &DocPath) -> MatchingRuleCategory {
522    match self.matchers.name {
523      Category::HEADER | Category::QUERY => self.matchers.filter(|&(val, _)| {
524        path.len() == 1 && path.first_field() == val.first_field()
525      }),
526      Category::BODY => self.matchers.filter(|&(val, _)| {
527        let p = path.to_vec();
528        let p_slice = p.iter().map(|p| p.as_str()).collect_vec();
529        val.matches_path_exactly(p_slice.as_slice())
530      }),
531      _ => self.matchers.filter(|_| false)
532    }
533  }
534
535  #[allow(dead_code)]
536  pub(crate) fn clone_from(context: &(dyn MatchingContext + Send + Sync)) -> Self {
537    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
538    {
539      CoreMatchingContext {
540        matchers: context.matchers().clone(),
541        config: context.config().clone(),
542        plugin_configuration: context.plugin_configuration().clone(),
543        .. CoreMatchingContext::default()
544      }
545    }
546
547    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
548    {
549      CoreMatchingContext {
550        matchers: context.matchers().clone(),
551        config: context.config().clone(),
552        .. CoreMatchingContext::default()
553      }
554    }
555  }
556}
557
558impl Default for CoreMatchingContext {
559  fn default() -> Self {
560    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
561    {
562      CoreMatchingContext {
563        matchers: Default::default(),
564        config: DiffConfig::AllowUnexpectedKeys,
565        matching_spec: PactSpecification::V3,
566        plugin_configuration: Default::default()
567      }
568    }
569
570    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
571    {
572      CoreMatchingContext {
573        matchers: Default::default(),
574        config: DiffConfig::AllowUnexpectedKeys,
575        matching_spec: PactSpecification::V3
576      }
577    }
578  }
579}
580
581impl MatchingContext for CoreMatchingContext {
582  #[instrument(level = "trace", ret, skip_all, fields(path, matchers = ?self.matchers))]
583  fn matcher_is_defined(&self, path: &DocPath) -> bool {
584    let path = path.to_vec();
585    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
586    self.matchers.matcher_is_defined(path_slice.as_slice())
587  }
588
589  fn select_best_matcher(&self, path: &DocPath) -> RuleList {
590    let path = path.to_vec();
591    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
592    self.matchers.select_best_matcher(path_slice.as_slice())
593  }
594
595  fn type_matcher_defined(&self, path: &DocPath) -> bool {
596    let path = path.to_vec();
597    let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
598    self.matchers.resolve_matchers_for_path(path_slice.as_slice()).type_matcher_defined()
599  }
600
601  fn values_matcher_defined(&self, path: &DocPath) -> bool {
602    self.matchers_for_exact_path(path).values_matcher_defined()
603  }
604
605  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool {
606    let actual = self.matchers_for_exact_path(path);
607    if matchers.is_empty() {
608      actual.is_not_empty()
609    } else {
610      actual.as_rule_list().rules.iter().any(|r| matchers.contains(r.name().as_str()))
611    }
612  }
613
614  fn match_keys(
615    &self,
616    path: &DocPath,
617    expected: &BTreeSet<String>,
618    actual: &BTreeSet<String>
619  ) -> Result<(), Vec<CommonMismatch>> {
620    let mut expected_keys = expected.iter().cloned().collect::<Vec<String>>();
621    expected_keys.sort();
622    let mut actual_keys = actual.iter().cloned().collect::<Vec<String>>();
623    actual_keys.sort();
624    let missing_keys: Vec<String> = expected.iter().filter(|key| !actual.contains(*key)).cloned().collect();
625    let mut result = vec![];
626
627    if !self.direct_matcher_defined(path, &hashset! { "values", "each-value", "each-key" }) {
628      match self.config {
629        DiffConfig::AllowUnexpectedKeys if !missing_keys.is_empty() => {
630          result.push(CommonMismatch {
631            path: path.to_string(),
632            expected: expected.for_mismatch(),
633            actual: actual.for_mismatch(),
634            description: format!("Actual map is missing the following keys: {}", missing_keys.join(", ")),
635          });
636        }
637        DiffConfig::NoUnexpectedKeys if expected_keys != actual_keys => {
638          result.push(CommonMismatch {
639            path: path.to_string(),
640            expected: expected.for_mismatch(),
641            actual: actual.for_mismatch(),
642            description: format!("Expected a Map with keys [{}] but received one with keys [{}]",
643                              expected_keys.join(", "), actual_keys.join(", ")),
644          });
645        }
646        _ => {}
647      }
648    }
649
650    if self.direct_matcher_defined(path, &Default::default()) {
651      let matchers = self.select_best_matcher(path);
652      for matcher in matchers.rules {
653        match matcher {
654          MatchingRule::EachKey(definition) => {
655            for sub_matcher in definition.rules {
656              match sub_matcher {
657                Either::Left(rule) => {
658                  for key in &actual_keys {
659                    let key_path = path.join(key);
660                    if let Err(err) = rule.match_value("", key.as_str(), false, false) {
661                      result.push(CommonMismatch {
662                        path: key_path.to_string(),
663                        expected: "".to_string(),
664                        actual: key.clone(),
665                        description: err.to_string(),
666                      });
667                    }
668                  }
669                }
670                Either::Right(name) => {
671                  result.push(CommonMismatch {
672                    path: path.to_string(),
673                    expected: expected.for_mismatch(),
674                    actual: actual.for_mismatch(),
675                    description: format!("Expected a matching rule, found an unresolved reference '{}'",
676                      name.name),
677                  });
678                }
679              }
680            }
681          }
682          _ => {}
683        }
684      }
685    }
686
687    if result.is_empty() {
688      Ok(())
689    } else {
690      Err(result)
691    }
692  }
693
694  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
695  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig> {
696    &self.plugin_configuration
697  }
698
699  fn matchers(&self) -> &MatchingRuleCategory {
700    &self.matchers
701  }
702
703  fn config(&self) -> DiffConfig {
704    self.config
705  }
706
707  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync> {
708    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
709    {
710      Box::new(CoreMatchingContext {
711        matchers: matchers.clone(),
712        config: self.config.clone(),
713        matching_spec: self.matching_spec,
714        plugin_configuration: self.plugin_configuration.clone()
715      })
716    }
717
718    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
719    {
720      Box::new(CoreMatchingContext {
721        matchers: matchers.clone(),
722        config: self.config.clone(),
723        matching_spec: self.matching_spec
724      })
725    }
726  }
727}
728
729#[derive(Debug, Clone, Default)]
730/// Matching context for headers. Keys will be applied in a case-insensitive manor
731pub struct HeaderMatchingContext {
732  inner_context: CoreMatchingContext
733}
734
735impl HeaderMatchingContext {
736  /// Wraps a MatchingContext, downcasing all the matching path keys
737  pub fn new(context: &(dyn MatchingContext + Send + Sync)) -> Self {
738    let matchers = context.matchers();
739
740    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
741    {
742      HeaderMatchingContext {
743        inner_context: CoreMatchingContext::new(
744          context.config(),
745          &MatchingRuleCategory {
746            name: matchers.name.clone(),
747            rules: matchers.rules.iter()
748              .map(|(path, rules)| {
749                (path.to_lower_case(), rules.clone())
750              })
751              .collect()
752          },
753          &context.plugin_configuration()
754        )
755      }
756    }
757
758    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
759    {
760      HeaderMatchingContext {
761        inner_context: CoreMatchingContext::new(
762          context.config(),
763          &MatchingRuleCategory {
764            name: matchers.name.clone(),
765            rules: matchers.rules.iter()
766              .map(|(path, rules)| {
767                (path.to_lower_case(), rules.clone())
768              })
769              .collect()
770          }
771        )
772      }
773    }
774  }
775}
776
777impl MatchingContext for HeaderMatchingContext {
778  fn matcher_is_defined(&self, path: &DocPath) -> bool {
779    self.inner_context.matcher_is_defined(path)
780  }
781
782  fn select_best_matcher(&self, path: &DocPath) -> RuleList {
783    self.inner_context.select_best_matcher(path)
784  }
785
786  fn type_matcher_defined(&self, path: &DocPath) -> bool {
787    self.inner_context.type_matcher_defined(path)
788  }
789
790  fn values_matcher_defined(&self, path: &DocPath) -> bool {
791    self.inner_context.values_matcher_defined(path)
792  }
793
794  fn direct_matcher_defined(&self, path: &DocPath, matchers: &HashSet<&str>) -> bool {
795    self.inner_context.direct_matcher_defined(path, matchers)
796  }
797
798  fn match_keys(&self, path: &DocPath, expected: &BTreeSet<String>, actual: &BTreeSet<String>) -> Result<(), Vec<CommonMismatch>> {
799    self.inner_context.match_keys(path, expected, actual)
800  }
801
802  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
803  fn plugin_configuration(&self) -> &HashMap<String, PluginInteractionConfig> {
804    self.inner_context.plugin_configuration()
805  }
806
807  fn matchers(&self) -> &MatchingRuleCategory {
808    self.inner_context.matchers()
809  }
810
811  fn config(&self) -> DiffConfig {
812    self.inner_context.config()
813  }
814
815  fn clone_with(&self, matchers: &MatchingRuleCategory) -> Box<dyn MatchingContext + Send + Sync> {
816    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
817    {
818      Box::new(HeaderMatchingContext::new(
819        &CoreMatchingContext {
820          matchers: matchers.clone(),
821          config: self.inner_context.config.clone(),
822          matching_spec: self.inner_context.matching_spec,
823          plugin_configuration: self.inner_context.plugin_configuration.clone()
824        }
825      ))
826    }
827
828    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
829    {
830      Box::new(HeaderMatchingContext::new(
831        &CoreMatchingContext {
832          matchers: matchers.clone(),
833          config: self.inner_context.config.clone(),
834          matching_spec: self.inner_context.matching_spec
835        }
836      ))
837    }
838  }
839}
840
841lazy_static! {
842  static ref BODY_MATCHERS: [
843    (fn(content_type: &ContentType) -> bool,
844    fn(expected: &(dyn HttpPart + Send + Sync), actual: &(dyn HttpPart + Send + Sync), context: &(dyn MatchingContext + Send + Sync)) -> Result<(), Vec<Mismatch>>); 5]
845     = [
846      (|content_type| { content_type.is_json() }, json::match_json),
847      (|content_type| { content_type.is_xml() }, match_xml),
848      (|content_type| { content_type.main_type == "multipart" }, binary_utils::match_mime_multipart),
849      (|content_type| { content_type.base_type() == "application/x-www-form-urlencoded" }, form_urlencoded::match_form_urlencoded),
850      (|content_type| { content_type.is_binary() || content_type.base_type() == "application/octet-stream" }, binary_utils::match_octet_stream)
851  ];
852}
853
854fn match_xml(
855  expected: &(dyn HttpPart + Send + Sync),
856  actual: &(dyn HttpPart + Send + Sync),
857  context: &(dyn MatchingContext + Send + Sync)
858) -> Result<(), Vec<Mismatch>> {
859  #[cfg(feature = "xml")]
860  {
861    xml::match_xml(expected, actual, context)
862  }
863  #[cfg(not(feature = "xml"))]
864  {
865    warn!("Matching XML documents requires the xml feature to be enabled");
866    match_text(&expected.body().value(), &actual.body().value(), context)
867  }
868}
869
870/// Store common mismatch information so it can be converted to different type of mismatches
871#[derive(Debug, Clone, PartialOrd, Ord, Eq)]
872pub struct CommonMismatch {
873  /// path expression to where the mismatch occurred
874  pub path: String,
875  /// expected value (as a string)
876  expected: String,
877  /// actual value (as a string)
878  actual: String,
879  /// Description of the mismatch
880  description: String
881}
882
883impl CommonMismatch {
884  /// Convert common mismatch to body mismatch
885  pub fn to_body_mismatch(&self) -> Mismatch {
886    Mismatch::BodyMismatch {
887      path: self.path.clone(),
888      expected: Some(self.expected.clone().into()),
889      actual: Some(self.actual.clone().into()),
890      mismatch: self.description.clone()
891    }
892  }
893
894  /// Convert common mismatch to query mismatch
895  pub fn to_query_mismatch(&self) -> Mismatch {
896    Mismatch::QueryMismatch {
897      parameter: self.path.clone(),
898      expected: self.expected.clone(),
899      actual: self.actual.clone(),
900      mismatch: self.description.clone()
901    }
902  }
903
904  /// Convert common mismatch to header mismatch
905  pub fn to_header_mismatch(&self) -> Mismatch {
906    Mismatch::HeaderMismatch {
907      key: self.path.clone(),
908      expected: self.expected.clone().into(),
909      actual: self.actual.clone().into(),
910      mismatch: self.description.clone()
911    }
912  }
913}
914
915impl Display for CommonMismatch {
916  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
917    write!(f, "{}", self.description)
918  }
919}
920
921impl PartialEq for CommonMismatch {
922  fn eq(&self, other: &CommonMismatch) -> bool {
923    self.path == other.path && self.expected == other.expected && self.actual == other.actual
924  }
925}
926
927impl From<Mismatch> for CommonMismatch {
928  fn from(value: Mismatch) -> Self {
929    match value {
930      Mismatch::MethodMismatch { expected, actual , mismatch} => CommonMismatch {
931        path: "".to_string(),
932        expected: expected.clone(),
933        actual: actual.clone(),
934        description: mismatch.clone()
935      },
936      Mismatch::PathMismatch { expected, actual, mismatch } => CommonMismatch {
937        path: "".to_string(),
938        expected: expected.clone(),
939        actual: actual.clone(),
940        description: mismatch.clone()
941      },
942      Mismatch::StatusMismatch { expected, actual, mismatch } => CommonMismatch {
943        path: "".to_string(),
944        expected: expected.to_string(),
945        actual: actual.to_string(),
946        description: mismatch.clone()
947      },
948      Mismatch::QueryMismatch { parameter, expected, actual, mismatch } => CommonMismatch {
949        path: parameter.clone(),
950        expected: expected.clone(),
951        actual: actual.clone(),
952        description: mismatch.clone()
953      },
954      Mismatch::HeaderMismatch { key, expected, actual, mismatch } => CommonMismatch {
955        path: key.clone(),
956        expected: expected.clone(),
957        actual: actual.clone(),
958        description: mismatch.clone()
959      },
960      Mismatch::BodyTypeMismatch { expected, actual, mismatch, .. } => CommonMismatch {
961        path: "".to_string(),
962        expected: expected.clone(),
963        actual: actual.clone(),
964        description: mismatch.clone()
965      },
966      Mismatch::BodyMismatch { path, expected, actual, mismatch } => CommonMismatch {
967        path: path.clone(),
968        expected: String::from_utf8_lossy(expected.unwrap_or_default().as_ref()).to_string(),
969        actual: String::from_utf8_lossy(actual.unwrap_or_default().as_ref()).to_string(),
970        description: mismatch.clone()
971      },
972      Mismatch::MetadataMismatch { key, expected, actual, mismatch } => CommonMismatch {
973        path: key.clone(),
974        expected: expected.clone(),
975        actual: actual.clone(),
976        description: mismatch.clone()
977      }
978    }
979  }
980}
981
982/// Enum that defines the different types of mismatches that can occur.
983#[derive(Debug, Clone, PartialOrd, Ord, Eq)]
984pub enum Mismatch {
985    /// Request Method mismatch
986    MethodMismatch {
987      /// Expected request method
988      expected: String,
989      /// Actual request method
990      actual: String,
991      /// description of the mismatch
992      mismatch: String
993    },
994    /// Request Path mismatch
995    PathMismatch {
996        /// expected request path
997        expected: String,
998        /// actual request path
999        actual: String,
1000        /// description of the mismatch
1001        mismatch: String
1002    },
1003    /// Response status mismatch
1004    StatusMismatch {
1005        /// expected response status
1006      expected: u16,
1007      /// actual response status
1008      actual: u16,
1009      /// description of the mismatch
1010      mismatch: String
1011    },
1012    /// Request query mismatch
1013    QueryMismatch {
1014        /// query parameter name
1015        parameter: String,
1016        /// expected value
1017        expected: String,
1018        /// actual value
1019        actual: String,
1020        /// description of the mismatch
1021        mismatch: String
1022    },
1023    /// Header mismatch
1024    HeaderMismatch {
1025        /// header key
1026        key: String,
1027        /// expected value
1028        expected: String,
1029        /// actual value
1030        actual: String,
1031        /// description of the mismatch
1032        mismatch: String
1033    },
1034    /// Mismatch in the content type of the body
1035    BodyTypeMismatch {
1036      /// expected content type of the body
1037      expected: String,
1038      /// actual content type of the body
1039      actual: String,
1040      /// description of the mismatch
1041      mismatch: String,
1042      /// expected value
1043      expected_body: Option<Bytes>,
1044      /// actual value
1045      actual_body: Option<Bytes>
1046    },
1047    /// Body element mismatch
1048    BodyMismatch {
1049      /// path expression to where the mismatch occurred
1050      path: String,
1051      /// expected value
1052      expected: Option<Bytes>,
1053      /// actual value
1054      actual: Option<Bytes>,
1055      /// description of the mismatch
1056      mismatch: String
1057    },
1058    /// Message metadata mismatch
1059    MetadataMismatch {
1060      /// key
1061      key: String,
1062      /// expected value
1063      expected: String,
1064      /// actual value
1065      actual: String,
1066      /// description of the mismatch
1067      mismatch: String
1068    }
1069}
1070
1071impl Mismatch {
1072  /// Converts the mismatch to a `Value` struct.
1073  pub fn to_json(&self) -> serde_json::Value {
1074    match self {
1075      Mismatch::MethodMismatch { expected: e, actual: a, mismatch: m } => {
1076        json!({
1077          "type" : "MethodMismatch",
1078          "expected" : e,
1079          "actual" : a,
1080          "mismatch" : m
1081        })
1082      },
1083      Mismatch::PathMismatch { expected: e, actual: a, mismatch: m } => {
1084        json!({
1085          "type" : "PathMismatch",
1086          "expected" : e,
1087          "actual" : a,
1088          "mismatch" : m
1089        })
1090      },
1091      Mismatch::StatusMismatch { expected: e, actual: a, mismatch: m } => {
1092        json!({
1093          "type" : "StatusMismatch",
1094          "expected" : e,
1095          "actual" : a,
1096          "mismatch": m
1097        })
1098      },
1099      Mismatch::QueryMismatch { parameter: p, expected: e, actual: a, mismatch: m } => {
1100        json!({
1101          "type" : "QueryMismatch",
1102          "parameter" : p,
1103          "expected" : e,
1104          "actual" : a,
1105          "mismatch" : m
1106        })
1107      },
1108      Mismatch::HeaderMismatch { key: k, expected: e, actual: a, mismatch: m } => {
1109        json!({
1110          "type" : "HeaderMismatch",
1111          "key" : k,
1112          "expected" : e,
1113          "actual" : a,
1114          "mismatch" : m
1115        })
1116      },
1117      Mismatch::BodyTypeMismatch {
1118        expected,
1119        actual,
1120        mismatch,
1121        expected_body,
1122        actual_body
1123      } => {
1124        json!({
1125          "type" : "BodyTypeMismatch",
1126          "expected" : expected,
1127          "actual" : actual,
1128          "mismatch" : mismatch,
1129          "expectedBody": match expected_body {
1130            Some(v) => serde_json::Value::String(str::from_utf8(v)
1131              .unwrap_or("ERROR: could not convert to UTF-8 from bytes").into()),
1132            None => serde_json::Value::Null
1133          },
1134          "actualBody": match actual_body {
1135            Some(v) => serde_json::Value::String(str::from_utf8(v)
1136              .unwrap_or("ERROR: could not convert to UTF-8 from bytes").into()),
1137            None => serde_json::Value::Null
1138          }
1139        })
1140      },
1141      Mismatch::BodyMismatch { path, expected, actual, mismatch } => {
1142        json!({
1143          "type" : "BodyMismatch",
1144          "path" : path,
1145          "expected" : match expected {
1146            Some(v) => serde_json::Value::String(str::from_utf8(v).unwrap_or("ERROR: could not convert from bytes").into()),
1147            None => serde_json::Value::Null
1148          },
1149          "actual" : match actual {
1150            Some(v) => serde_json::Value::String(str::from_utf8(v).unwrap_or("ERROR: could not convert from bytes").into()),
1151            None => serde_json::Value::Null
1152          },
1153          "mismatch" : mismatch
1154        })
1155      }
1156      Mismatch::MetadataMismatch { key, expected, actual, mismatch } => {
1157        json!({
1158          "type" : "MetadataMismatch",
1159          "key" : key,
1160          "expected" : expected,
1161          "actual" : actual,
1162          "mismatch" : mismatch
1163        })
1164      }
1165    }
1166  }
1167
1168    /// Returns the type of the mismatch as a string
1169    pub fn mismatch_type(&self) -> &str {
1170      match *self {
1171        Mismatch::MethodMismatch { .. } => "MethodMismatch",
1172        Mismatch::PathMismatch { .. } => "PathMismatch",
1173        Mismatch::StatusMismatch { .. } => "StatusMismatch",
1174        Mismatch::QueryMismatch { .. } => "QueryMismatch",
1175        Mismatch::HeaderMismatch { .. } => "HeaderMismatch",
1176        Mismatch::BodyTypeMismatch { .. } => "BodyTypeMismatch",
1177        Mismatch::BodyMismatch { .. } => "BodyMismatch",
1178        Mismatch::MetadataMismatch { .. } => "MetadataMismatch"
1179      }
1180    }
1181
1182    /// Returns a summary string for this mismatch
1183    pub fn summary(&self) -> String {
1184      match *self {
1185        Mismatch::MethodMismatch { expected: ref e, .. } => format!("is a {} request", e),
1186        Mismatch::PathMismatch { expected: ref e, .. } => format!("to path '{}'", e),
1187        Mismatch::StatusMismatch { expected: ref e, .. } => format!("has status code {}", e),
1188        Mismatch::QueryMismatch { ref parameter, expected: ref e, .. } => format!("includes parameter '{}' with value '{}'", parameter, e),
1189        Mismatch::HeaderMismatch { ref key, expected: ref e, .. } => format!("includes header '{}' with value '{}'", key, e),
1190        Mismatch::BodyTypeMismatch { .. } => "has a matching body".to_string(),
1191        Mismatch::BodyMismatch { .. } => "has a matching body".to_string(),
1192        Mismatch::MetadataMismatch { .. } => "has matching metadata".to_string()
1193      }
1194    }
1195
1196    /// Returns a formatted string for this mismatch
1197    pub fn description(&self) -> String {
1198      match self {
1199        Mismatch::MethodMismatch { expected: e, actual: a, mismatch: m } => if m.is_empty() {
1200          format!("expected {} but was {}", e, a)
1201        } else {
1202          m.clone()
1203        },
1204        Mismatch::PathMismatch { mismatch, .. } => mismatch.clone(),
1205        Mismatch::StatusMismatch { mismatch, .. } => mismatch.clone(),
1206        Mismatch::QueryMismatch { mismatch, .. } => mismatch.clone(),
1207        Mismatch::HeaderMismatch { mismatch, .. } => mismatch.clone(),
1208        Mismatch::BodyTypeMismatch {  expected: e, actual: a, .. } =>
1209          format!("Expected a body of '{}' but the actual content type was '{}'", e, a),
1210        Mismatch::BodyMismatch { path, mismatch, .. } => format!("{} -> {}", path, mismatch),
1211        Mismatch::MetadataMismatch { mismatch, .. } => mismatch.clone()
1212      }
1213    }
1214
1215    /// Returns a formatted string with ansi escape codes for this mismatch
1216    pub fn ansi_description(&self) -> String {
1217      match self {
1218        Mismatch::MethodMismatch { expected: e, actual: a, .. } => format!("expected {} but was {}", Red.paint(e.clone()), Green.paint(a.clone())),
1219        Mismatch::PathMismatch { expected: e, actual: a, .. } => format!("expected '{}' but was '{}'", Red.paint(e.clone()), Green.paint(a.clone())),
1220        Mismatch::StatusMismatch { expected: e, actual: a, .. } => format!("expected {} but was {}", Red.paint(e.to_string()), Green.paint(a.to_string())),
1221        Mismatch::QueryMismatch { expected: e, actual: a, parameter: p, .. } => format!("Expected '{}' but received '{}' for query parameter '{}'",
1222          Red.paint(e.to_string()), Green.paint(a.to_string()), Style::new().bold().paint(p.clone())),
1223        Mismatch::HeaderMismatch { expected: e, actual: a, key: k, .. } => format!("Expected header '{}' to have value '{}' but was '{}'",
1224          Style::new().bold().paint(k.clone()), Red.paint(e.to_string()), Green.paint(a.to_string())),
1225        Mismatch::BodyTypeMismatch {  expected: e, actual: a, .. } =>
1226          format!("expected a body of '{}' but the actual content type was '{}'", Red.paint(e.clone()), Green.paint(a.clone())),
1227        Mismatch::BodyMismatch { path, mismatch, .. } => format!("{} -> {}", Style::new().bold().paint(path.clone()), mismatch),
1228        Mismatch::MetadataMismatch { expected: e, actual: a, key: k, .. } => format!("Expected message metadata '{}' to have value '{}' but was '{}'",
1229          Style::new().bold().paint(k.clone()), Red.paint(e.to_string()), Green.paint(a.to_string()))
1230      }
1231    }
1232}
1233
1234impl PartialEq for Mismatch {
1235  fn eq(&self, other: &Mismatch) -> bool {
1236    match (self, other) {
1237      (Mismatch::MethodMismatch { expected: e1, actual: a1, .. },
1238        Mismatch::MethodMismatch { expected: e2, actual: a2, .. }) => {
1239        e1 == e2 && a1 == a2
1240      },
1241      (Mismatch::PathMismatch { expected: e1, actual: a1, .. },
1242        Mismatch::PathMismatch { expected: e2, actual: a2, .. }) => {
1243        e1 == e2 && a1 == a2
1244      },
1245      (Mismatch::StatusMismatch { expected: e1, actual: a1, .. },
1246        Mismatch::StatusMismatch { expected: e2, actual: a2, .. }) => {
1247        e1 == e2 && a1 == a2
1248      },
1249      (Mismatch::BodyTypeMismatch { expected: e1, actual: a1, .. },
1250        Mismatch::BodyTypeMismatch { expected: e2, actual: a2, .. }) => {
1251        e1 == e2 && a1 == a2
1252      },
1253      (Mismatch::QueryMismatch { parameter: p1, expected: e1, actual: a1, .. },
1254        Mismatch::QueryMismatch { parameter: p2, expected: e2, actual: a2, .. }) => {
1255        p1 == p2 && e1 == e2 && a1 == a2
1256      },
1257      (Mismatch::HeaderMismatch { key: p1, expected: e1, actual: a1, .. },
1258        Mismatch::HeaderMismatch { key: p2, expected: e2, actual: a2, .. }) => {
1259        p1 == p2 && e1 == e2 && a1 == a2
1260      },
1261      (Mismatch::BodyMismatch { path: p1, expected: e1, actual: a1, .. },
1262        Mismatch::BodyMismatch { path: p2, expected: e2, actual: a2, .. }) => {
1263        p1 == p2 && e1 == e2 && a1 == a2
1264      },
1265      (Mismatch::MetadataMismatch { key: p1, expected: e1, actual: a1, .. },
1266        Mismatch::MetadataMismatch { key: p2, expected: e2, actual: a2, .. }) => {
1267        p1 == p2 && e1 == e2 && a1 == a2
1268      },
1269      (_, _) => false
1270    }
1271  }
1272}
1273
1274impl Display for Mismatch {
1275  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1276    write!(f, "{}", self.description())
1277  }
1278}
1279
1280fn merge_result<T: Clone>(res1: Result<(), Vec<T>>, res2: Result<(), Vec<T>>) -> Result<(), Vec<T>> {
1281  match (&res1, &res2) {
1282    (Ok(_), Ok(_)) => res1.clone(),
1283    (Err(_), Ok(_)) => res1.clone(),
1284    (Ok(_), Err(_)) => res2.clone(),
1285    (Err(m1), Err(m2)) => {
1286      let mut mismatches = m1.clone();
1287      mismatches.extend_from_slice(&*m2);
1288      Err(mismatches)
1289    }
1290  }
1291}
1292
1293/// Result of matching a request body
1294#[derive(Debug, Default, Clone, PartialEq)]
1295pub enum BodyMatchResult {
1296  /// Matched OK
1297  #[default]
1298  Ok,
1299  /// Mismatch in the content type of the body
1300  BodyTypeMismatch {
1301    /// Expected content type
1302    expected_type: String,
1303    /// Actual content type
1304    actual_type: String,
1305    /// Message
1306    message: String,
1307    /// Expected body
1308    expected: Option<Bytes>,
1309    /// Actual body
1310    actual: Option<Bytes>
1311  },
1312  /// Mismatches with the body contents
1313  BodyMismatches(HashMap<String, Vec<Mismatch>>)
1314}
1315
1316impl BodyMatchResult {
1317  /// Returns all the mismatches
1318  pub fn mismatches(&self) -> Vec<Mismatch> {
1319    match self {
1320      BodyMatchResult::BodyTypeMismatch { expected_type, actual_type, message, expected, actual } => {
1321        vec![Mismatch::BodyTypeMismatch {
1322          expected: expected_type.clone(),
1323          actual: actual_type.clone(),
1324          mismatch: message.clone(),
1325          expected_body: expected.clone(),
1326          actual_body: actual.clone()
1327        }]
1328      },
1329      BodyMatchResult::BodyMismatches(results) =>
1330        results.values().flatten().cloned().collect(),
1331      _ => vec![]
1332    }
1333  }
1334
1335  /// If all the things matched OK
1336  pub fn all_matched(&self) -> bool {
1337    match self {
1338      BodyMatchResult::BodyTypeMismatch { .. } => false,
1339      BodyMatchResult::BodyMismatches(results) =>
1340        results.values().all(|m| m.is_empty()),
1341      _ => true
1342    }
1343  }
1344}
1345
1346/// Result of matching a request
1347#[derive(Debug, Default, Clone, PartialEq)]
1348pub struct RequestMatchResult {
1349  /// Method match result
1350  pub method: Option<Mismatch>,
1351  /// Path match result
1352  pub path: Option<Vec<Mismatch>>,
1353  /// Body match result
1354  pub body: BodyMatchResult,
1355  /// Query parameter result
1356  pub query: HashMap<String, Vec<Mismatch>>,
1357  /// Headers result
1358  pub headers: HashMap<String, Vec<Mismatch>>
1359}
1360
1361impl RequestMatchResult {
1362  /// Returns all the mismatches
1363  pub fn mismatches(&self) -> Vec<Mismatch> {
1364    let mut m = vec![];
1365
1366    if let Some(ref mismatch) = self.method {
1367      m.push(mismatch.clone());
1368    }
1369    if let Some(ref mismatches) = self.path {
1370      m.extend_from_slice(mismatches.as_slice());
1371    }
1372    for mismatches in self.query.values() {
1373      m.extend_from_slice(mismatches.as_slice());
1374    }
1375    for mismatches in self.headers.values() {
1376      m.extend_from_slice(mismatches.as_slice());
1377    }
1378    m.extend_from_slice(self.body.mismatches().as_slice());
1379
1380    m
1381  }
1382
1383  /// Returns a score based on what was matched
1384  pub fn score(&self) -> i8 {
1385    let mut score = 0;
1386    if self.method.is_none() {
1387      score += 1;
1388    } else {
1389      score -= 1;
1390    }
1391    if self.path.is_none() {
1392      score += 1
1393    } else {
1394      score -= 1
1395    }
1396    for mismatches in self.query.values() {
1397      if mismatches.is_empty() {
1398        score += 1;
1399      } else {
1400        score -= 1;
1401      }
1402    }
1403    for mismatches in self.headers.values() {
1404      if mismatches.is_empty() {
1405        score += 1;
1406      } else {
1407        score -= 1;
1408      }
1409    }
1410    match &self.body {
1411      BodyMatchResult::BodyTypeMismatch { .. } => {
1412        score -= 1;
1413      },
1414      BodyMatchResult::BodyMismatches(results) => {
1415        for mismatches in results.values() {
1416          if mismatches.is_empty() {
1417            score += 1;
1418          } else {
1419            score -= 1;
1420          }
1421        }
1422      },
1423      _ => ()
1424    }
1425    score
1426  }
1427
1428  /// If all the things matched OK
1429  pub fn all_matched(&self) -> bool {
1430    self.method.is_none() && self.path.is_none() &&
1431      self.query.values().all(|m| m.is_empty()) &&
1432      self.headers.values().all(|m| m.is_empty()) &&
1433      self.body.all_matched()
1434  }
1435
1436  /// If there was a mismatch with the method or path
1437  pub fn method_or_path_mismatch(&self) -> bool {
1438    self.method.is_some() || self.path.is_some()
1439  }
1440}
1441
1442impl From<ExecutionPlan> for RequestMatchResult {
1443  fn from(plan: ExecutionPlan) -> Self {
1444    let request = plan.fetch_node(&[":request"]).unwrap_or_default();
1445    let method = method_mismatch(&request);
1446    let path = path_mismatch(&request);
1447    let query = query_mismatches(&request);
1448    let headers = header_mismatches(&request);
1449    let body = body_mismatches(&request);
1450    RequestMatchResult {
1451      method,
1452      path,
1453      body,
1454      query,
1455      headers
1456    }
1457  }
1458}
1459
1460/// Enum that defines the configuration options for performing a match.
1461#[derive(Debug, Clone, Copy, PartialEq)]
1462pub enum DiffConfig {
1463    /// If unexpected keys are allowed and ignored during matching.
1464    AllowUnexpectedKeys,
1465    /// If unexpected keys cause a mismatch.
1466    NoUnexpectedKeys
1467}
1468
1469/// Matches the actual text body to the expected one.
1470pub fn match_text(expected: &Option<Bytes>, actual: &Option<Bytes>, context: &dyn MatchingContext) -> Result<(), Vec<Mismatch>> {
1471  let path = DocPath::root();
1472  if context.matcher_is_defined(&path) {
1473    let mut mismatches = vec![];
1474    let empty = Bytes::default();
1475    let expected_str = match from_utf8(expected.as_ref().unwrap_or(&empty)) {
1476      Ok(expected) => expected,
1477      Err(err) => {
1478        mismatches.push(Mismatch::BodyMismatch {
1479          path: "$".to_string(),
1480          expected: expected.clone(),
1481          actual: actual.clone(),
1482          mismatch: format!("Could not parse expected value as UTF-8 text: {}", err)
1483        });
1484        ""
1485      }
1486    };
1487    let actual_str = match from_utf8(actual.as_ref().unwrap_or(&empty)) {
1488      Ok(actual) => actual,
1489      Err(err) => {
1490        mismatches.push(Mismatch::BodyMismatch {
1491          path: "$".to_string(),
1492          expected: expected.clone(),
1493          actual: actual.clone(),
1494          mismatch: format!("Could not parse actual value as UTF-8 text: {}", err)
1495        });
1496        ""
1497      }
1498    };
1499    if let Err(messages) = match_values(&path, &context.select_best_matcher(&path), expected_str, actual_str) {
1500      for message in messages {
1501        mismatches.push(Mismatch::BodyMismatch {
1502          path: "$".to_string(),
1503          expected: expected.clone(),
1504          actual: actual.clone(),
1505          mismatch: message.clone()
1506        })
1507      }
1508    };
1509    if mismatches.is_empty() {
1510      Ok(())
1511    } else {
1512      Err(mismatches)
1513    }
1514  } else if expected != actual {
1515    let expected = expected.clone().unwrap_or_default();
1516    let actual = actual.clone().unwrap_or_default();
1517    let e = String::from_utf8_lossy(&expected);
1518    let a = String::from_utf8_lossy(&actual);
1519    let mismatch = format!("Expected body '{}' to match '{}' using equality but did not match", e, a);
1520    Err(vec![
1521      Mismatch::BodyMismatch {
1522        path: "$".to_string(),
1523        expected: Some(expected.clone()),
1524        actual: Some(actual.clone()),
1525        mismatch
1526      }
1527    ])
1528  } else {
1529    Ok(())
1530  }
1531}
1532
1533/// Matches the actual request method to the expected one.
1534pub fn match_method(expected: &str, actual: &str) -> Result<(), Mismatch> {
1535  if expected.to_lowercase() != actual.to_lowercase() {
1536    Err(Mismatch::MethodMismatch { expected: expected.to_string(), actual: actual.to_string(), mismatch: "".to_string() })
1537  } else {
1538    Ok(())
1539  }
1540}
1541
1542/// Matches the actual request path to the expected one.
1543pub fn match_path(expected: &str, actual: &str, context: &(dyn MatchingContext + Send + Sync)) -> Result<(), Vec<Mismatch>> {
1544  let path = DocPath::empty();
1545  let matcher_result = if context.matcher_is_defined(&path) {
1546    match_values(&path, &context.select_best_matcher(&path), expected.to_string(), actual.to_string())
1547  } else {
1548    expected.matches_with(actual, &MatchingRule::Equality, false).map_err(|err| vec![err])
1549      .map_err(|errors| errors.iter().map(|err| err.to_string()).collect())
1550  };
1551  matcher_result.map_err(|messages| messages.iter().map(|message| {
1552    Mismatch::PathMismatch {
1553      expected: expected.to_string(),
1554      actual: actual.to_string(), mismatch: message.clone()
1555    }
1556  }).collect())
1557}
1558
1559/// Matches the actual query parameters to the expected ones.
1560pub fn match_query(
1561  expected: Option<HashMap<String, Vec<Option<String>>>>,
1562  actual: Option<HashMap<String, Vec<Option<String>>>>,
1563  context: &(dyn MatchingContext + Send + Sync)
1564) -> HashMap<String, Vec<Mismatch>> {
1565  match (actual, expected) {
1566    (Some(aqm), Some(eqm)) => match_query_maps(eqm, aqm, context),
1567    (Some(aqm), None) => aqm.iter().map(|(key, value)| {
1568      let actual_value = value.iter().map(|v| v.clone().unwrap_or_default()).collect_vec();
1569      (key.clone(), vec![Mismatch::QueryMismatch {
1570        parameter: key.clone(),
1571        expected: "".to_string(),
1572        actual: format!("{:?}", actual_value),
1573        mismatch: format!("Unexpected query parameter '{}' received", key)
1574      }])
1575    }).collect(),
1576    (None, Some(eqm)) => eqm.iter().map(|(key, value)| {
1577      let expected_value = value.iter().map(|v| v.clone().unwrap_or_default()).collect_vec();
1578      (key.clone(), vec![Mismatch::QueryMismatch {
1579        parameter: key.clone(),
1580        expected: format!("{:?}", expected_value),
1581        actual: "".to_string(),
1582        mismatch: format!("Expected query parameter '{}' but was missing", key)
1583      }])
1584    }).collect(),
1585    (None, None) => hashmap!{}
1586  }
1587}
1588
1589fn group_by<I, F, K>(items: I, f: F) -> HashMap<K, Vec<I::Item>>
1590  where I: IntoIterator, F: Fn(&I::Item) -> K, K: Eq + Hash {
1591  let mut m = hashmap!{};
1592  for item in items {
1593    let key = f(&item);
1594    let values = m.entry(key).or_insert_with(Vec::new);
1595    values.push(item);
1596  }
1597  m
1598}
1599
1600#[instrument(level = "trace", ret, skip_all)]
1601pub(crate) async fn compare_bodies(
1602  content_type: &ContentType,
1603  expected: &(dyn HttpPart + Send + Sync),
1604  actual: &(dyn HttpPart + Send + Sync),
1605  context: &(dyn MatchingContext + Send + Sync)
1606) -> BodyMatchResult {
1607  let mut mismatches = vec![];
1608
1609  trace!(?content_type, "Comparing bodies");
1610
1611  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
1612  {
1613    match find_content_matcher(content_type) {
1614      Some(matcher) => {
1615        debug!("Using content matcher {} for content type '{}'", matcher.catalogue_entry_key(), content_type);
1616        if matcher.is_core() {
1617          if let Err(m) = match matcher.catalogue_entry_key().as_str() {
1618            "core/content-matcher/form-urlencoded" => form_urlencoded::match_form_urlencoded(expected, actual, context),
1619            "core/content-matcher/json" => match_json(expected, actual, context),
1620            "core/content-matcher/multipart-form-data" => binary_utils::match_mime_multipart(expected, actual, context),
1621            "core/content-matcher/text" => match_text(&expected.body().value(), &actual.body().value(), context),
1622            "core/content-matcher/xml" => {
1623              #[cfg(feature = "xml")]
1624              {
1625                xml::match_xml(expected, actual, context)
1626              }
1627              #[cfg(not(feature = "xml"))]
1628              {
1629                warn!("Matching XML bodies requires the xml feature to be enabled");
1630                match_text(&expected.body().value(), &actual.body().value(), context)
1631              }
1632            },
1633            "core/content-matcher/binary" => binary_utils::match_octet_stream(expected, actual, context),
1634            _ => {
1635              warn!("There is no core content matcher for entry {}", matcher.catalogue_entry_key());
1636              match_text(&expected.body().value(), &actual.body().value(), context)
1637            }
1638          } {
1639            mismatches.extend_from_slice(&*m);
1640          }
1641        } else {
1642          trace!(plugin_name = matcher.plugin_name(),"Content matcher is provided via a plugin");
1643          let plugin_config = context.plugin_configuration().get(&matcher.plugin_name()).cloned();
1644          trace!("Plugin config = {:?}", plugin_config);
1645          if let Err(map) = matcher.match_contents(expected.body(), actual.body(), &context.matchers(),
1646                                                   context.config() == DiffConfig::AllowUnexpectedKeys, plugin_config).await {
1647            // TODO: group the mismatches by key
1648            for (_key, list) in map {
1649              for mismatch in list {
1650                mismatches.push(Mismatch::BodyMismatch {
1651                  path: mismatch.path.clone(),
1652                  expected: Some(Bytes::from(mismatch.expected)),
1653                  actual: Some(Bytes::from(mismatch.actual)),
1654                  mismatch: mismatch.mismatch.clone()
1655                });
1656              }
1657            }
1658          }
1659        }
1660      }
1661      None => {
1662        debug!("No content matcher defined for content type '{}', using core matcher implementation", content_type);
1663        mismatches.extend(compare_bodies_core(content_type, expected, actual, context));
1664      }
1665    }
1666  }
1667
1668  #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
1669  {
1670    mismatches.extend(compare_bodies_core(content_type, expected, actual, context));
1671  }
1672
1673  if mismatches.is_empty() {
1674    BodyMatchResult::Ok
1675  } else {
1676    BodyMatchResult::BodyMismatches(group_by(mismatches, |m| match m {
1677      Mismatch::BodyMismatch { path: m, ..} => m.to_string(),
1678      _ => String::default()
1679    }))
1680  }
1681}
1682
1683fn compare_bodies_core(
1684  content_type: &ContentType,
1685  expected: &(dyn HttpPart + Send + Sync),
1686  actual: &(dyn HttpPart + Send + Sync),
1687  context: &(dyn MatchingContext + Send + Sync)
1688) -> Vec<Mismatch> {
1689  let mut mismatches = vec![];
1690  match BODY_MATCHERS.iter().find(|mt| mt.0(content_type)) {
1691    Some(match_fn) => {
1692      debug!("Using body matcher for content type '{}'", content_type);
1693      if let Err(m) = match_fn.1(expected, actual, context) {
1694        mismatches.extend_from_slice(&*m);
1695      }
1696    },
1697    None => {
1698      debug!("No body matcher defined for content type '{}', checking for a content type matcher", content_type);
1699      let path = DocPath::root();
1700      if context.matcher_is_defined(&path) && context.select_best_matcher(&path).rules
1701        .iter().any(|rule| if let MatchingRule::ContentType(_) = rule { true } else { false }) {
1702        debug!("Found a content type matcher");
1703        if let Err(m) = binary_utils::match_octet_stream(expected, actual, context) {
1704          mismatches.extend_from_slice(&*m);
1705        }
1706      } else {
1707        debug!("No body matcher defined for content type '{}', using plain text matcher", content_type);
1708        if let Err(m) = match_text(&expected.body().value(), &actual.body().value(), context) {
1709          mismatches.extend_from_slice(&*m);
1710        }
1711      }
1712    }
1713  };
1714  mismatches
1715}
1716
1717#[instrument(level = "trace", ret, skip_all, fields(%content_type, ?context))]
1718async fn match_body_content(
1719  content_type: &ContentType,
1720  expected: &(dyn HttpPart + Send + Sync),
1721  actual: &(dyn HttpPart + Send + Sync),
1722  context: &(dyn MatchingContext + Send + Sync)
1723) -> BodyMatchResult {
1724  let expected_body = expected.body();
1725  let actual_body = actual.body();
1726  match (expected_body, actual_body) {
1727    (&OptionalBody::Missing, _) => BodyMatchResult::Ok,
1728    (&OptionalBody::Null, &OptionalBody::Present(ref b, _, _)) => {
1729      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch { expected: None, actual: Some(b.clone()),
1730        mismatch: format!("Expected empty body but received {}", actual_body),
1731        path: s!("/")}]})
1732    },
1733    (&OptionalBody::Empty, &OptionalBody::Present(ref b, _, _)) => {
1734      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch { expected: None, actual: Some(b.clone()),
1735        mismatch: format!("Expected empty body but received {}", actual_body),
1736        path: s!("/")}]})
1737    },
1738    (&OptionalBody::Null, _) => BodyMatchResult::Ok,
1739    (&OptionalBody::Empty, _) => BodyMatchResult::Ok,
1740    (e, &OptionalBody::Missing) => {
1741      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch {
1742        expected: e.value(),
1743        actual: None,
1744        mismatch: format!("Expected body {} but was missing", e),
1745        path: s!("/")}]})
1746    },
1747    (e, &OptionalBody::Empty) => {
1748      BodyMatchResult::BodyMismatches(hashmap!{ "$".into() => vec![Mismatch::BodyMismatch {
1749        expected: e.value(),
1750        actual: None,
1751        mismatch: format!("Expected body {} but was empty", e),
1752        path: s!("/")}]})
1753    },
1754    (_, _) => compare_bodies(content_type, expected, actual, context).await
1755  }
1756}
1757
1758/// Matches the actual body to the expected one. This takes into account the content type of each.
1759pub async fn match_body(
1760  expected: &(dyn HttpPart + Send + Sync),
1761  actual: &(dyn HttpPart + Send + Sync),
1762  context: &(dyn MatchingContext + Send + Sync),
1763  header_context: &(dyn MatchingContext + Send + Sync)
1764) -> BodyMatchResult {
1765  let expected_content_type = expected.content_type().unwrap_or_default();
1766  let actual_content_type = actual.content_type().unwrap_or_default();
1767  debug!("expected content type = '{}', actual content type = '{}'", expected_content_type,
1768         actual_content_type);
1769  let content_type_matcher = header_context.select_best_matcher(&DocPath::root().join("content-type"));
1770  debug!("content type header matcher = '{:?}'", content_type_matcher);
1771  if expected_content_type.is_unknown() || actual_content_type.is_unknown() ||
1772    expected_content_type.is_equivalent_to(&actual_content_type) ||
1773    expected_content_type.is_equivalent_to(&actual_content_type.base_type()) ||
1774    (!content_type_matcher.is_empty() &&
1775      match_header_value("Content-Type", 0, expected_content_type.to_string().as_str(),
1776                         actual_content_type.to_string().as_str(), header_context, true
1777      ).is_ok()) {
1778    match_body_content(&expected_content_type, expected, actual, context).await
1779  } else if expected.body().is_present() {
1780    BodyMatchResult::BodyTypeMismatch {
1781      expected_type: expected_content_type.to_string(),
1782      actual_type: actual_content_type.to_string(),
1783      message: format!("Expected a body of '{}' but the actual content type was '{}'", expected_content_type,
1784                       actual_content_type),
1785      expected: expected.body().value(),
1786      actual: actual.body().value()
1787    }
1788  } else {
1789    BodyMatchResult::Ok
1790  }
1791}
1792
1793/// Matches the expected and actual requests
1794#[allow(unused_variables)]
1795pub async fn match_request<'a>(
1796  expected: HttpRequest,
1797  actual: HttpRequest,
1798  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>,
1799  interaction: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>
1800) -> anyhow::Result<RequestMatchResult> {
1801  debug!("comparing to expected {}", expected);
1802  debug!("     body: '{}'", expected.body.display_string());
1803  debug!("     matching_rules:\n{}", expected.matching_rules);
1804  debug!("     generators: {:?}", expected.generators);
1805
1806  let use_v2_engine = std::env::var("PACT_MATCHING_ENGINE")
1807    .map(|val| val.to_lowercase() == "v2")
1808    .unwrap_or(false);
1809  if use_v2_engine {
1810    let config = MatchingConfiguration {
1811      allow_unexpected_entries: false,
1812      .. MatchingConfiguration::init_from_env()
1813    };
1814    let mut context = PlanMatchingContext {
1815      pact: pact.as_v4_pact().unwrap_or_default(),
1816      interaction: interaction.as_v4().unwrap(),
1817      matching_rules: Default::default(),
1818      config
1819    };
1820
1821    let plan = build_request_plan(&expected, &mut context)?;
1822    let executed_plan = execute_request_plan(&plan, &actual, &mut context)?;
1823
1824    if config.log_executed_plan {
1825      debug!("config = {:?}", config);
1826      debug!("\n{}", executed_plan.pretty_form());
1827    }
1828    if config.log_plan_summary {
1829      info!("\n{}", executed_plan.generate_summary(config.coloured_output));
1830    }
1831    Ok(executed_plan.into())
1832  } else {
1833    let mut result = RequestMatchResult::default();
1834
1835    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
1836    {
1837      let plugin_data = setup_plugin_config(pact, interaction, InteractionPart::Request);
1838      trace!("plugin_data = {:?}", plugin_data);
1839
1840      let path_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1841        &expected.matching_rules.rules_for_category("path").unwrap_or_default(),
1842        &plugin_data
1843      );
1844      let body_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1845        &expected.matching_rules.rules_for_category("body").unwrap_or_default(),
1846        &plugin_data
1847      );
1848      let query_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1849        &expected.matching_rules.rules_for_category("query").unwrap_or_default(),
1850        &plugin_data
1851      );
1852      let header_context = HeaderMatchingContext::new(
1853        &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1854          &expected.matching_rules.rules_for_category("header").unwrap_or_default(),
1855          &plugin_data
1856        )
1857      );
1858      result = RequestMatchResult {
1859        method: match_method(&expected.method, &actual.method).err(),
1860        path: match_path(&expected.path, &actual.path, &path_context).err(),
1861        body: match_body(&expected, &actual, &body_context, &header_context).await,
1862        query: match_query(expected.query, actual.query, &query_context),
1863        headers: match_headers(expected.headers, actual.headers, &header_context)
1864      };
1865    }
1866
1867    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
1868    {
1869      let path_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1870        &expected.matching_rules.rules_for_category("path").unwrap_or_default()
1871      );
1872      let body_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1873        &expected.matching_rules.rules_for_category("body").unwrap_or_default()
1874      );
1875      let query_context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1876        &expected.matching_rules.rules_for_category("query").unwrap_or_default()
1877      );
1878      let header_context = HeaderMatchingContext::new(
1879        &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1880          &expected.matching_rules.rules_for_category("header").unwrap_or_default()
1881        )
1882      );
1883      result = RequestMatchResult {
1884        method: match_method(&expected.method, &actual.method).err(),
1885        path: match_path(&expected.path, &actual.path, &path_context).err(),
1886        body: match_body(&expected, &actual, &body_context, &header_context).await,
1887        query: match_query(expected.query, actual.query, &query_context),
1888        headers: match_headers(expected.headers, actual.headers, &header_context)
1889      };
1890    }
1891
1892    debug!("--> Mismatches: {:?}", result.mismatches());
1893    Ok(result)
1894  }
1895}
1896
1897/// Matches the actual response status to the expected one.
1898#[instrument(level = "trace")]
1899pub fn match_status(expected: u16, actual: u16, context: &dyn MatchingContext) -> Result<(), Vec<Mismatch>> {
1900  let path = DocPath::empty();
1901  let result = if context.matcher_is_defined(&path) {
1902    match_values(&path, &context.select_best_matcher(&path), expected, actual)
1903      .map_err(|messages| messages.iter().map(|message| {
1904        Mismatch::StatusMismatch {
1905          expected,
1906          actual,
1907          mismatch: message.clone()
1908        }
1909      }).collect())
1910  } else if expected != actual {
1911    Err(vec![Mismatch::StatusMismatch {
1912      expected,
1913      actual,
1914      mismatch: format!("expected {} but was {}", expected, actual)
1915    }])
1916  } else {
1917    Ok(())
1918  };
1919  trace!(?result, "matching response status");
1920  result
1921}
1922
1923/// Matches the actual and expected responses.
1924#[allow(unused_variables)]
1925pub async fn match_response<'a>(
1926  expected: HttpResponse,
1927  actual: HttpResponse,
1928  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>,
1929  interaction: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>
1930) -> anyhow::Result<Vec<Mismatch>> {
1931  let mut mismatches = vec![];
1932
1933  debug!("comparing to expected response: {}", expected);
1934
1935  let use_v2_engine = std::env::var("PACT_MATCHING_ENGINE")
1936    .map(|val| val.to_lowercase() == "v2")
1937    .unwrap_or(false);
1938  if use_v2_engine {
1939    let config = MatchingConfiguration {
1940      allow_unexpected_entries: true,
1941      .. MatchingConfiguration::init_from_env()
1942    };
1943    let mut context = PlanMatchingContext {
1944      pact: pact.as_v4_pact().unwrap_or_default(),
1945      interaction: interaction.as_v4().unwrap(),
1946      matching_rules: Default::default(),
1947      config
1948    };
1949
1950    let plan = build_response_plan(&expected, &mut context)?;
1951    let executed_plan = execute_response_plan(&plan, &actual, &mut context)?;
1952
1953    if config.log_executed_plan {
1954      debug!("config = {:?}", config);
1955      debug!("\n{}", executed_plan.pretty_form());
1956    }
1957    if config.log_plan_summary {
1958      info!("\n{}", executed_plan.generate_summary(config.coloured_output));
1959    }
1960    Ok(executed_plan.into())
1961  } else {
1962
1963    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
1964    {
1965      let plugin_data = setup_plugin_config(pact, interaction, InteractionPart::Response);
1966      trace!("plugin_data = {:?}", plugin_data);
1967
1968      let status_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1969        &expected.matching_rules.rules_for_category("status").unwrap_or_default(),
1970        &plugin_data);
1971      let body_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1972        &expected.matching_rules.rules_for_category("body").unwrap_or_default(),
1973        &plugin_data);
1974      let header_context = HeaderMatchingContext::new(
1975        &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
1976          &expected.matching_rules.rules_for_category("header").unwrap_or_default(),
1977          &plugin_data
1978        )
1979      );
1980
1981      mismatches.extend_from_slice(match_body(&expected, &actual, &body_context, &header_context).await
1982        .mismatches().as_slice());
1983      if let Err(m) = match_status(expected.status, actual.status, &status_context) {
1984        mismatches.extend_from_slice(&m);
1985      }
1986      let result = match_headers(expected.headers, actual.headers,
1987        &header_context);
1988      for values in result.values() {
1989        mismatches.extend_from_slice(values.as_slice());
1990      }
1991    }
1992
1993    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
1994    {
1995      let status_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1996        &expected.matching_rules.rules_for_category("status").unwrap_or_default());
1997      let body_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
1998        &expected.matching_rules.rules_for_category("body").unwrap_or_default());
1999      let header_context = HeaderMatchingContext::new(
2000        &CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
2001          &expected.matching_rules.rules_for_category("header").unwrap_or_default()
2002        )
2003      );
2004
2005      mismatches.extend_from_slice(match_body(&expected, &actual, &body_context, &header_context).await
2006        .mismatches().as_slice());
2007      if let Err(m) = match_status(expected.status, actual.status, &status_context) {
2008        mismatches.extend_from_slice(&m);
2009      }
2010      let result = match_headers(expected.headers, actual.headers,
2011        &header_context);
2012      for values in result.values() {
2013        mismatches.extend_from_slice(values.as_slice());
2014      }
2015    }
2016
2017    trace!(?mismatches, "match response");
2018
2019    Ok(mismatches)
2020  }
2021}
2022
2023/// Matches the actual message contents to the expected one. This takes into account the content type of each.
2024#[instrument(level = "trace")]
2025pub async fn match_message_contents(
2026  expected: &MessageContents,
2027  actual: &MessageContents,
2028  context: &(dyn MatchingContext + Send + Sync)
2029) -> Result<(), Vec<Mismatch>> {
2030  let expected_content_type = expected.message_content_type().unwrap_or_default();
2031  let actual_content_type = actual.message_content_type().unwrap_or_default();
2032  debug!("expected content type = '{}', actual content type = '{}'", expected_content_type,
2033         actual_content_type);
2034  if expected_content_type.is_equivalent_to(&actual_content_type) {
2035    let result = match_body_content(&expected_content_type, expected, actual, context).await;
2036    match result {
2037      BodyMatchResult::BodyTypeMismatch { expected_type, actual_type, message, expected, actual } => {
2038        Err(vec![ Mismatch::BodyTypeMismatch {
2039          expected: expected_type,
2040          actual: actual_type,
2041          mismatch: message,
2042          expected_body: expected,
2043          actual_body: actual
2044        } ])
2045      },
2046      BodyMatchResult::BodyMismatches(results) => {
2047        Err(results.values().flat_map(|values| values.iter().cloned()).collect())
2048      },
2049      _ => Ok(())
2050    }
2051  } else if expected.contents.is_present() {
2052    Err(vec![ Mismatch::BodyTypeMismatch {
2053      expected: expected_content_type.to_string(),
2054      actual: actual_content_type.to_string(),
2055      mismatch: format!("Expected message with content type {} but was {}",
2056                        expected_content_type, actual_content_type),
2057      expected_body: expected.contents.value(),
2058      actual_body: actual.contents.value()
2059    } ])
2060  } else {
2061    Ok(())
2062  }
2063}
2064
2065/// Matches the actual message metadata to the expected one.
2066#[instrument(level = "trace")]
2067pub fn match_message_metadata(
2068  expected: &MessageContents,
2069  actual: &MessageContents,
2070  context: &dyn MatchingContext
2071) -> HashMap<String, Vec<Mismatch>> {
2072  debug!("Matching message metadata");
2073  let mut result = hashmap!{};
2074  let expected_metadata = &expected.metadata;
2075  let actual_metadata = &actual.metadata;
2076  debug!("Matching message metadata. Expected '{:?}', Actual '{:?}'", expected_metadata, actual_metadata);
2077
2078  if !expected_metadata.is_empty() || context.config() == DiffConfig::NoUnexpectedKeys {
2079    for (key, value) in expected_metadata {
2080      match actual_metadata.get(key) {
2081        Some(actual_value) => {
2082          result.insert(key.clone(), match_metadata_value(key, value,
2083            actual_value, context).err().unwrap_or_default());
2084        },
2085        None => {
2086          result.insert(key.clone(), vec![Mismatch::MetadataMismatch { key: key.clone(),
2087            expected: json_to_string(&value),
2088            actual: "".to_string(),
2089            mismatch: format!("Expected message metadata '{}' but was missing", key) }]);
2090        }
2091      }
2092    }
2093  }
2094  result
2095}
2096
2097#[instrument(level = "trace")]
2098fn match_metadata_value(
2099  key: &str,
2100  expected: &Value,
2101  actual: &Value,
2102  context: &dyn MatchingContext
2103) -> Result<(), Vec<Mismatch>> {
2104  debug!("Comparing metadata values for key '{}'", key);
2105  let path = DocPath::root().join(key);
2106  let matcher_result = if context.matcher_is_defined(&path) {
2107    match_values(&path, &context.select_best_matcher(&path), expected, actual)
2108  } else if key.to_ascii_lowercase() == "contenttype" || key.to_ascii_lowercase() == "content-type" {
2109    debug!("Comparing message context type '{}' => '{}'", expected, actual);
2110    headers::match_parameter_header(expected.as_str().unwrap_or_default(), actual.as_str().unwrap_or_default(),
2111      key, "metadata", 0, true)
2112  } else {
2113    expected.matches_with(actual, &MatchingRule::Equality, false).map_err(|err| vec![err.to_string()])
2114  };
2115  matcher_result.map_err(|messages| {
2116    messages.iter().map(|message| {
2117      Mismatch::MetadataMismatch {
2118        key: key.to_string(),
2119        expected: expected.to_string(),
2120        actual: actual.to_string(),
2121        mismatch: format!("Expected metadata key '{}' to have value '{}' but was '{}' - {}", key, expected, actual, message)
2122      }
2123    }).collect()
2124  })
2125}
2126
2127/// Matches the actual and expected messages.
2128#[allow(unused_variables)]
2129pub async fn match_message<'a>(
2130  expected: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2131  actual: &Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2132  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>) -> Vec<Mismatch> {
2133  let mut mismatches = vec![];
2134
2135  if expected.is_message() && actual.is_message() {
2136    debug!("comparing to expected message: {:?}", expected);
2137    let expected_message = expected.as_message().unwrap();
2138    let actual_message = actual.as_message().unwrap();
2139
2140    let matching_rules = &expected_message.matching_rules;
2141
2142    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
2143    {
2144      let plugin_data  = setup_plugin_config(pact, expected, InteractionPart::None);
2145
2146      let body_context = if expected.is_v4() {
2147        CoreMatchingContext {
2148          matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2149          config: DiffConfig::AllowUnexpectedKeys,
2150          matching_spec: PactSpecification::V4,
2151          plugin_configuration: plugin_data.clone()
2152        }
2153      } else {
2154        CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2155          &matching_rules.rules_for_category("body").unwrap_or_default(),
2156          &plugin_data)
2157      };
2158
2159      let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2160        &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2161        &plugin_data);
2162      let result = match_message_contents(&expected_message.as_message_content(), &actual_message.as_message_content(), &body_context).await;
2163      mismatches.extend_from_slice(result.err().unwrap_or_default().as_slice());
2164      for values in match_message_metadata(&expected_message.as_message_content(), &actual_message.as_message_content(), &metadata_context).values() {
2165        mismatches.extend_from_slice(values.as_slice());
2166      }
2167    }
2168
2169    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
2170    {
2171      let body_context = if expected.is_v4() {
2172        CoreMatchingContext {
2173          matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2174          config: DiffConfig::AllowUnexpectedKeys,
2175          matching_spec: PactSpecification::V4
2176        }
2177      } else {
2178        CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2179          &matching_rules.rules_for_category("body").unwrap_or_default())
2180      };
2181
2182      let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2183        &matching_rules.rules_for_category("metadata").unwrap_or_default());
2184      let result = crate::match_message_contents(&expected_message.as_message_content(), &actual_message.as_message_content(), &body_context).await;
2185      mismatches.extend_from_slice(result.err().unwrap_or_default().as_slice());
2186      for values in crate::match_message_metadata(&expected_message.as_message_content(), &actual_message.as_message_content(), &metadata_context).values() {
2187        mismatches.extend_from_slice(values.as_slice());
2188      }
2189    }
2190  } else {
2191    mismatches.push(Mismatch::BodyTypeMismatch {
2192      expected: "message".into(),
2193      actual: actual.type_of(),
2194      mismatch: format!("Cannot compare a {} with a {}", expected.type_of(), actual.type_of()),
2195      expected_body: None,
2196      actual_body: None
2197    });
2198  }
2199
2200  mismatches
2201}
2202
2203/// Matches synchronous request/response messages
2204pub async fn match_sync_message<'a>(expected: SynchronousMessage, actual: SynchronousMessage, pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>) -> Vec<Mismatch> {
2205  let mut mismatches = match_sync_message_request(&expected, &actual, pact).await;
2206  let response_result = match_sync_message_response(&expected, &expected.response, &actual.response, pact).await;
2207  mismatches.extend_from_slice(&*response_result);
2208  mismatches
2209}
2210
2211/// Match the request part of a synchronous request/response message
2212#[allow(unused_variables)]
2213pub async fn match_sync_message_request<'a>(
2214  expected: &SynchronousMessage,
2215  actual: &SynchronousMessage,
2216  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>
2217) -> Vec<Mismatch> {
2218  debug!("comparing to expected message request: {:?}", expected);
2219
2220  let mut mismatches = vec![];
2221  let matching_rules = &expected.request.matching_rules;
2222
2223  #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
2224  {
2225    let plugin_data = setup_plugin_config(pact, &expected.boxed(), InteractionPart::None);
2226
2227    let body_context = CoreMatchingContext {
2228      matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2229      config: DiffConfig::AllowUnexpectedKeys,
2230      matching_spec: PactSpecification::V4,
2231      plugin_configuration: plugin_data.clone()
2232    };
2233
2234    let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2235      &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2236      &plugin_data);
2237    let contents = match_message_contents(&expected.request, &actual.request, &body_context).await;
2238
2239    mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2240    for values in match_message_metadata(&expected.request, &actual.request, &metadata_context).values() {
2241      mismatches.extend_from_slice(values.as_slice());
2242    }
2243  }
2244
2245  #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
2246  {
2247    let body_context = CoreMatchingContext {
2248      matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2249      config: DiffConfig::AllowUnexpectedKeys,
2250      matching_spec: PactSpecification::V4
2251    };
2252
2253    let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2254      &matching_rules.rules_for_category("metadata").unwrap_or_default());
2255    let contents = match_message_contents(&expected.request, &actual.request, &body_context).await;
2256
2257    mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2258    for values in match_message_metadata(&expected.request, &actual.request, &metadata_context).values() {
2259      mismatches.extend_from_slice(values.as_slice());
2260    }
2261  }
2262
2263  mismatches
2264}
2265
2266/// Match the response part of a synchronous request/response message
2267#[allow(unused_variables)]
2268pub async fn match_sync_message_response<'a>(
2269  expected: &SynchronousMessage,
2270  expected_responses: &[MessageContents],
2271  actual_responses: &[MessageContents],
2272  pact: &Box<dyn Pact + Send + Sync + RefUnwindSafe + 'a>
2273) -> Vec<Mismatch> {
2274  debug!("comparing to expected message responses: {:?}", expected_responses);
2275
2276  let mut mismatches = vec![];
2277
2278  if expected_responses.len() != actual_responses.len() {
2279    if !expected_responses.is_empty() && actual_responses.is_empty() {
2280      mismatches.push(Mismatch::BodyTypeMismatch {
2281        expected: "message response".into(),
2282        actual: "".into(),
2283        mismatch: "Expected a message with a response, but the actual response was empty".into(),
2284        expected_body: None,
2285        actual_body: None
2286      });
2287    } else if !expected_responses.is_empty() {
2288      mismatches.push(Mismatch::BodyTypeMismatch {
2289        expected: "message response".into(),
2290        actual: "".into(),
2291        mismatch: format!("Expected a message with {} responses, but the actual response had {}",
2292                          expected_responses.len(), actual_responses.len()),
2293        expected_body: None,
2294        actual_body: None
2295      });
2296    }
2297  } else {
2298
2299    #[cfg(feature = "plugins")] #[cfg(not(target_family = "wasm"))]
2300    {
2301      let plugin_data = setup_plugin_config(pact, &expected.boxed(), InteractionPart::None);
2302      for (expected_response, actual_response) in expected_responses.iter().zip(actual_responses) {
2303        let matching_rules = &expected_response.matching_rules;
2304        let body_context = CoreMatchingContext {
2305          matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2306          config: DiffConfig::AllowUnexpectedKeys,
2307          matching_spec: PactSpecification::V4,
2308          plugin_configuration: plugin_data.clone()
2309        };
2310
2311        let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2312          &matching_rules.rules_for_category("metadata").unwrap_or_default(),
2313          &plugin_data);
2314        let contents = match_message_contents(expected_response, actual_response, &body_context).await;
2315
2316        mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2317        for values in match_message_metadata(expected_response, actual_response, &metadata_context).values() {
2318          mismatches.extend_from_slice(values.as_slice());
2319        }
2320      }
2321    }
2322
2323    #[cfg(any(not(feature = "plugins"), target_family = "wasm"))]
2324    {
2325      for (expected_response, actual_response) in expected_responses.iter().zip(actual_responses) {
2326        let matching_rules = &expected_response.matching_rules;
2327        let body_context = CoreMatchingContext {
2328          matchers: matching_rules.rules_for_category("content").unwrap_or_default(),
2329          config: DiffConfig::AllowUnexpectedKeys,
2330          matching_spec: PactSpecification::V4
2331        };
2332
2333        let metadata_context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys,
2334          &matching_rules.rules_for_category("metadata").unwrap_or_default());
2335        let contents = match_message_contents(expected_response, actual_response, &body_context).await;
2336
2337        mismatches.extend_from_slice(contents.err().unwrap_or_default().as_slice());
2338        for values in match_message_metadata(expected_response, actual_response, &metadata_context).values() {
2339          mismatches.extend_from_slice(values.as_slice());
2340        }
2341      }
2342    }
2343  }
2344  mismatches
2345}
2346
2347/// Generates the request by applying any defined generators
2348// TODO: Need to pass in any plugin data
2349#[instrument(level = "trace")]
2350pub async fn generate_request(request: &HttpRequest, mode: &GeneratorTestMode, context: &HashMap<&str, Value>) -> HttpRequest {
2351  trace!(?request, ?mode, ?context, "generate_request");
2352  let mut request = request.clone();
2353
2354  let generators = request.build_generators(&GeneratorCategory::PATH);
2355  if !generators.is_empty() {
2356    debug!("Applying path generator...");
2357    apply_generators(mode, &generators, &mut |_, generator| {
2358      if let Ok(v) = generator.generate_value(&request.path, context, &DefaultVariantMatcher.boxed()) {
2359        request.path = v;
2360      }
2361    });
2362  }
2363
2364  let generators = request.build_generators(&GeneratorCategory::HEADER);
2365  if !generators.is_empty() {
2366    debug!("Applying header generators...");
2367    apply_generators(mode, &generators, &mut |key, generator| {
2368      if let Some(header) = key.first_field() {
2369        if let Some(ref mut headers) = request.headers {
2370          if headers.contains_key(header) {
2371            if let Ok(v) = generator.generate_value(&headers.get(header).unwrap().clone(), context, &DefaultVariantMatcher.boxed()) {
2372              headers.insert(header.to_string(), v);
2373            }
2374          } else {
2375            if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2376              headers.insert(header.to_string(), vec![ v.to_string() ]);
2377            }
2378          }
2379        } else {
2380          if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2381            request.headers = Some(hashmap!{
2382              header.to_string() => vec![ v.to_string() ]
2383            })
2384          }
2385        }
2386      }
2387    });
2388  }
2389
2390  let generators = request.build_generators(&GeneratorCategory::QUERY);
2391  if !generators.is_empty() {
2392    debug!("Applying query generators...");
2393    apply_generators(mode, &generators, &mut |key, generator| {
2394      if let Some(param) = key.first_field() {
2395        if let Some(ref mut parameters) = request.query {
2396          if let Some(parameter) = parameters.get_mut(param) {
2397            let mut generated = parameter.clone();
2398            for (index, val) in parameter.iter().enumerate() {
2399              let value = val.clone().unwrap_or_default();
2400              if let Ok(v) = generator.generate_value(&value, context, &DefaultVariantMatcher.boxed()) {
2401                generated[index] = Some(v);
2402              }
2403            }
2404            *parameter = generated;
2405          } else if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2406            parameters.insert(param.to_string(), vec![ Some(v.to_string()) ]);
2407          }
2408        } else if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2409          request.query = Some(hashmap!{
2410            param.to_string() => vec![ Some(v.to_string()) ]
2411          })
2412        }
2413      }
2414    });
2415  }
2416
2417  let generators = request.build_generators(&GeneratorCategory::BODY);
2418  if !generators.is_empty() && request.body.is_present() {
2419    debug!("Applying body generators...");
2420    match generators_process_body(mode, &request.body, request.content_type(),
2421                                  context, &generators, &DefaultVariantMatcher {}, &vec![], &hashmap!{}).await {
2422      Ok(body) => request.body = body,
2423      Err(err) => error!("Failed to generate the body, will use the original: {}", err)
2424    }
2425  }
2426
2427  request
2428}
2429
2430/// Generates the response by applying any defined generators
2431// TODO: Need to pass in any plugin data
2432pub async fn generate_response(response: &HttpResponse, mode: &GeneratorTestMode, context: &HashMap<&str, Value>) -> HttpResponse {
2433  trace!(?response, ?mode, ?context, "generate_response");
2434  let mut response = response.clone();
2435  let generators = response.build_generators(&GeneratorCategory::STATUS);
2436  if !generators.is_empty() {
2437    debug!("Applying status generator...");
2438    apply_generators(mode, &generators, &mut |_, generator| {
2439      if let Ok(v) = generator.generate_value(&response.status, context, &DefaultVariantMatcher.boxed()) {
2440        debug!("Generated value for status: {}", v);
2441        response.status = v;
2442      }
2443    });
2444  }
2445  let generators = response.build_generators(&GeneratorCategory::HEADER);
2446  if !generators.is_empty() {
2447    debug!("Applying header generators...");
2448    apply_generators(mode, &generators, &mut |key, generator| {
2449      if let Some(header) = key.first_field() {
2450        if let Some(ref mut headers) = response.headers {
2451          if headers.contains_key(header) {
2452            if let Ok(v) = generator.generate_value(&headers.get(header).unwrap().clone(), context, &DefaultVariantMatcher.boxed()) {
2453              headers.insert(header.to_string(), v);
2454            }
2455          } else {
2456            if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2457              headers.insert(header.to_string(), vec![ v.to_string() ]);
2458            }
2459          }
2460        } else {
2461          if let Ok(v) = generator.generate_value(&"".to_string(), context, &DefaultVariantMatcher.boxed()) {
2462            response.headers = Some(hashmap!{
2463              header.to_string() => vec![ v.to_string() ]
2464            })
2465          }
2466        }
2467      }
2468    });
2469  }
2470  let generators = response.build_generators(&GeneratorCategory::BODY);
2471  if !generators.is_empty() && response.body.is_present() {
2472    debug!("Applying body generators...");
2473    match generators_process_body(mode, &response.body, response.content_type(),
2474      context, &generators, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await {
2475      Ok(body) => response.body = body,
2476      Err(err) => error!("Failed to generate the body, will use the original: {}", err)
2477    }
2478  }
2479  response
2480}
2481
2482/// Matches the request part of the interaction
2483pub async fn match_interaction_request(
2484  expected: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2485  actual: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2486  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2487  _spec_version: &PactSpecification
2488) -> anyhow::Result<RequestMatchResult> {
2489  if let Some(http_interaction) = expected.as_v4_http() {
2490    let request = actual.as_v4_http()
2491      .ok_or_else(|| anyhow!("Could not unpack actual request as a V4 Http Request"))?.request;
2492    match_request(http_interaction.request, request, &pact, &expected).await
2493  } else {
2494    Err(anyhow!("match_interaction_request must be called with HTTP request/response interactions, got {}", expected.type_of()))
2495  }
2496}
2497
2498/// Matches the response part of the interaction
2499pub async fn match_interaction_response(
2500  expected: Box<dyn Interaction + Sync + RefUnwindSafe>,
2501  actual: Box<dyn Interaction + Sync + RefUnwindSafe>,
2502  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2503  _spec_version: &PactSpecification
2504) -> anyhow::Result<Vec<Mismatch>> {
2505  if let Some(expected) = expected.as_v4_http() {
2506    let expected_response = expected.response.clone();
2507    let expected = expected.boxed();
2508    let response = actual.as_v4_http()
2509      .ok_or_else(|| anyhow!("Could not unpack actual response as a V4 Http Response"))?.response;
2510    match_response(expected_response, response, &pact, &expected).await
2511  } else {
2512    Err(anyhow!("match_interaction_response must be called with HTTP request/response interactions, got {}", expected.type_of()))
2513  }
2514}
2515
2516/// Matches an interaction
2517pub async fn match_interaction(
2518  expected: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2519  actual: Box<dyn Interaction + Send + Sync + RefUnwindSafe>,
2520  pact: Box<dyn Pact + Send + Sync + RefUnwindSafe>,
2521  _spec_version: &PactSpecification
2522) -> anyhow::Result<Vec<Mismatch>> {
2523  if let Some(expected) = expected.as_v4_http() {
2524    let expected_request = expected.request.clone();
2525    let expected_response = expected.response.clone();
2526    let expected = expected.boxed();
2527    let request = actual.as_v4_http()
2528      .ok_or_else(|| anyhow!("Could not unpack actual request as a V4 Http Request"))?.request;
2529    let request_result = match_request(expected_request, request, &pact, &expected).await?;
2530    let response = actual.as_v4_http()
2531      .ok_or_else(|| anyhow!("Could not unpack actual response as a V4 Http Response"))?.response;
2532    let response_result = match_response(expected_response, response, &pact, &expected).await?;
2533    let mut mismatches = request_result.mismatches();
2534    mismatches.extend_from_slice(&*response_result);
2535    Ok(mismatches)
2536  } else if expected.is_message() || expected.is_v4() {
2537    Ok(match_message(&expected, &actual, &pact).await)
2538  } else {
2539    Err(anyhow!("match_interaction must be called with either an HTTP request/response interaction or a Message, got {}", expected.type_of()))
2540  }
2541}
2542
2543#[cfg(test)]
2544mod tests;
2545#[cfg(test)]
2546mod generator_tests;