pact_models/matchingrules/
mod.rs

1//! `matchingrules` module includes all the classes to deal with V3/V4 spec matchers
2
3use std::{fmt, mem};
4use std::cmp::Ordering;
5use std::collections::{HashMap, HashSet};
6use std::fmt::{Display, Formatter};
7use std::hash::{Hash, Hasher};
8use std::str::FromStr;
9
10use anyhow::{anyhow, Context as _};
11use itertools::{Either, Itertools};
12use maplit::hashmap;
13use serde_json::{json, Map, Value};
14use tracing::{error, trace};
15
16use crate::{HttpStatus, PactSpecification};
17use crate::content_types::ContentType;
18use crate::generators::{Generator, GeneratorCategory, Generators};
19use crate::json_utils::{json_to_num, json_to_string};
20use crate::matchingrules::expressions::{MatchingReference, MatchingRuleDefinition, ValueType};
21use crate::path_exp::{DocPath, PathToken};
22
23pub mod expressions;
24
25fn generator_from_json(json: &Map<String, Value>) -> Option<Generator> {
26  if let Some(generator_json) = json.get("generator") {
27    match generator_json {
28      Value::Object(attributes) => if let Some(generator_type) = attributes.get("type") {
29        match generator_type {
30          Value::String(generator_type) => Generator::from_map(generator_type.as_str(), attributes),
31          _ => None
32        }
33      } else {
34        None
35      }
36      _ => None
37    }
38  } else {
39    None
40  }
41}
42
43fn rules_from_json(attributes: &Map<String, Value>) -> anyhow::Result<Vec<Either<MatchingRule, MatchingReference>>> {
44  match attributes.get("rules") {
45    Some(rules) => match rules {
46      Value::Array(rules) => {
47        let rules = rules.iter()
48          .map(|rule| MatchingRule::from_json(rule));
49        if let Some(err) = rules.clone().find(|rule| rule.is_err()) {
50          Err(anyhow!("Matching rule configuration is not correct - {}", err.unwrap_err()))
51        } else {
52          Ok(rules.map(|rule| Either::Left(rule.unwrap())).collect())
53        }
54      }
55      _ => Err(anyhow!("EachKey matcher config is not valid. Was expected an array but got {}", rules))
56    }
57    None => Ok(vec![])
58  }
59}
60
61/// Set of all matching rules
62#[derive(Debug, Clone, Eq)]
63pub enum MatchingRule {
64  /// Matcher using equals
65  Equality,
66  /// Match using a regular expression
67  Regex(String),
68  /// Match using the type of the value
69  Type,
70  /// Match using the type of the value and a minimum length for collections
71  MinType(usize),
72  /// Match using the type of the value and a maximum length for collections
73  MaxType(usize),
74  /// Match using the type of the value and a minimum and maximum length for collections
75  MinMaxType(usize, usize),
76  /// Match the value using a timestamp pattern
77  Timestamp(String),
78  /// Match the value using a time pattern
79  Time(String),
80  /// Match the value using a date pattern
81  Date(String),
82  /// Match if the value includes the given value
83  Include(String),
84  /// Match if the value is a number
85  Number,
86  /// Match if the value is an integer number
87  Integer,
88  /// Match if the value is a decimal number
89  Decimal,
90  /// Match if the value is a null value (this is content specific, for JSON will match a JSON null)
91  Null,
92  /// Match binary data by its content type (magic file check)
93  ContentType(String),
94  /// Match array items in any order against a list of variants
95  ArrayContains(Vec<(usize, MatchingRuleCategory, HashMap<DocPath, Generator>)>),
96  /// Matcher for values in a map, ignoring the keys
97  Values,
98  /// Matches boolean values (booleans and the string values `true` and `false`)
99  Boolean,
100  /// Request status code matcher
101  StatusCode(HttpStatus),
102  /// Value must be the same type and not empty
103  NotEmpty,
104  /// Value must a semantic version
105  Semver,
106  /// Matcher for keys in a map
107  EachKey(MatchingRuleDefinition),
108  /// Matcher for values in a collection. This delegates to the Values matcher for maps.
109  EachValue(MatchingRuleDefinition)
110}
111
112impl MatchingRule {
113
114  /// Builds a `MatchingRule` from a `Value` struct
115  pub fn from_json(value: &Value) -> anyhow::Result<MatchingRule> {
116    match value {
117      Value::Object(m) => match m.get("match").or_else(|| m.get("pact:matcher:type")) {
118        Some(match_val) => {
119          let val = json_to_string(match_val);
120          MatchingRule::create(val.as_str(), value)
121        }
122        None => if let Some(val) = m.get("regex") {
123          Ok(MatchingRule::Regex(json_to_string(val)))
124        } else if let Some(val) = json_to_num(m.get("min").cloned()) {
125          Ok(MatchingRule::MinType(val))
126        } else if let Some(val) = json_to_num(m.get("max").cloned()) {
127          Ok(MatchingRule::MaxType(val))
128        } else if let Some(val) = m.get("timestamp") {
129          Ok(MatchingRule::Timestamp(json_to_string(val)))
130        } else if let Some(val) = m.get("time") {
131          Ok(MatchingRule::Time(json_to_string(val)))
132        } else if let Some(val) = m.get("date") {
133          Ok(MatchingRule::Date(json_to_string(val)))
134        } else {
135          Err(anyhow!("Matching rule missing 'match' field and unable to guess its type"))
136        }
137      },
138      _ => Err(anyhow!("Matching rule JSON is not an Object")),
139    }
140  }
141
142  /// Converts this `MatchingRule` to a `Value` struct
143  pub fn to_json(&self) -> Value {
144    match self {
145      MatchingRule::Equality => json!({ "match": "equality" }),
146      MatchingRule::Regex(r) => json!({ "match": "regex",
147        "regex": r.clone() }),
148      MatchingRule::Type => json!({ "match": "type" }),
149      MatchingRule::MinType(min) => json!({ "match": "type",
150        "min": json!(*min as u64) }),
151      MatchingRule::MaxType(max) => json!({ "match": "type",
152        "max": json!(*max as u64) }),
153      MatchingRule::MinMaxType(min, max) => json!({ "match": "type",
154        "min": json!(*min as u64), "max": json!(*max as u64) }),
155      MatchingRule::Timestamp(t) => json!({ "match": "datetime",
156        "format": Value::String(t.clone()) }),
157      MatchingRule::Time(t) => json!({ "match": "time",
158        "format": Value::String(t.clone()) }),
159      MatchingRule::Date(d) => json!({ "match": "date",
160        "format": Value::String(d.clone()) }),
161      MatchingRule::Include(s) => json!({ "match": "include",
162        "value": Value::String(s.clone()) }),
163      MatchingRule::Number => json!({ "match": "number" }),
164      MatchingRule::Integer => json!({ "match": "integer" }),
165      MatchingRule::Decimal => json!({ "match": "decimal" }),
166      MatchingRule::Boolean => json!({ "match": "boolean" }),
167      MatchingRule::Null => json!({ "match": "null" }),
168      MatchingRule::ContentType(r) => json!({ "match": "contentType",
169        "value": Value::String(r.clone()) }),
170      MatchingRule::ArrayContains(variants) => json!({
171        "match": "arrayContains",
172        "variants": variants.iter().map(|(index, rules, generators)| {
173          let mut json = json!({
174            "index": index,
175            "rules": rules.to_v3_json()
176          });
177          if !generators.is_empty() {
178            json["generators"] = Value::Object(generators.iter()
179              .map(|(k, g)| {
180                if let Some(json) = g.to_json() {
181                  Some((String::from(k), json))
182                } else {
183                  None
184                }
185              })
186              .filter(|item| item.is_some())
187              .map(|item| item.unwrap())
188              .collect())
189          }
190          json
191        }).collect::<Vec<Value>>()
192      }),
193      MatchingRule::Values => json!({ "match": "values" }),
194      MatchingRule::StatusCode(status) => json!({ "match": "statusCode", "status": status.to_json() }),
195      MatchingRule::NotEmpty => json!({ "match": "notEmpty" }),
196      MatchingRule::Semver => json!({ "match": "semver" }),
197      MatchingRule::EachKey(definition) => {
198        let mut json = json!({
199          "match": "eachKey",
200          "rules": definition.rules.iter()
201            .map(|rule| rule.as_ref().expect_left("Expected a matching rule, found an unresolved reference").to_json())
202          .collect::<Vec<Value>>()
203        });
204        let map = json.as_object_mut().unwrap();
205
206        if !definition.value.is_empty() {
207          map.insert("value".to_string(), Value::String(definition.value.clone()));
208        }
209
210        if let Some(generator) = &definition.generator {
211          map.insert("generator".to_string(), generator.to_json().unwrap_or_default());
212        }
213
214        Value::Object(map.clone())
215      }
216      MatchingRule::EachValue(definition) => {
217        let mut json = json!({
218          "match": "eachValue",
219          "rules": definition.rules.iter()
220            .map(|rule| rule.as_ref().expect_left("Expected a matching rule, found an unresolved reference").to_json())
221          .collect::<Vec<Value>>()
222        });
223        let map = json.as_object_mut().unwrap();
224
225        if !definition.value.is_empty() {
226          map.insert("value".to_string(), Value::String(definition.value.clone()));
227        }
228
229        if let Some(generator) = &definition.generator {
230          map.insert("generator".to_string(), generator.to_json().unwrap_or_default());
231        }
232
233        Value::Object(map.clone())
234      }
235    }
236  }
237
238  /// If there are any generators associated with this matching rule
239  pub fn has_generators(&self) -> bool {
240    match self {
241      MatchingRule::ArrayContains(variants) => variants.iter()
242        .any(|(_, _, generators)| !generators.is_empty()),
243      _ => false
244    }
245  }
246
247  /// Return the generators for this rule
248  pub fn generators(&self) -> Vec<Generator> {
249    match self {
250      MatchingRule::ArrayContains(variants) => vec![Generator::ArrayContains(variants.clone())],
251      _ => vec![]
252    }
253  }
254
255  /// Returns the type name of this matching rule
256  pub fn name(&self) -> String {
257    match self {
258      MatchingRule::Equality => "equality",
259      MatchingRule::Regex(_) => "regex",
260      MatchingRule::Type => "type",
261      MatchingRule::MinType(_) => "min-type",
262      MatchingRule::MaxType(_) => "max-type",
263      MatchingRule::MinMaxType(_, _) => "min-max-type",
264      MatchingRule::Timestamp(_) => "datetime",
265      MatchingRule::Time(_) => "time",
266      MatchingRule::Date(_) => "date",
267      MatchingRule::Include(_) => "include",
268      MatchingRule::Number => "number",
269      MatchingRule::Integer => "integer",
270      MatchingRule::Decimal => "decimal",
271      MatchingRule::Null => "null",
272      MatchingRule::ContentType(_) => "content-type",
273      MatchingRule::ArrayContains(_) => "array-contains",
274      MatchingRule::Values => "values",
275      MatchingRule::Boolean => "boolean",
276      MatchingRule::StatusCode(_) => "status-code",
277      MatchingRule::NotEmpty => "not-empty",
278      MatchingRule::Semver => "semver",
279      MatchingRule::EachKey(_) => "each-key",
280      MatchingRule::EachValue(_) => "each-value"
281    }.to_string()
282  }
283
284  /// Returns the type name of this matching rule
285  pub fn values(&self) -> HashMap<&'static str, Value> {
286    let empty = hashmap!{};
287    match self {
288      MatchingRule::Equality => empty,
289      MatchingRule::Regex(r) => hashmap!{ "regex" => Value::String(r.clone()) },
290      MatchingRule::Type => empty,
291      MatchingRule::MinType(min) => hashmap!{ "min" => json!(min) },
292      MatchingRule::MaxType(max) => hashmap!{ "max" => json!(max) },
293      MatchingRule::MinMaxType(min, max) => hashmap!{ "min" => json!(min), "max" => json!(max) },
294      MatchingRule::Timestamp(f) => hashmap!{ "format" => Value::String(f.clone()) },
295      MatchingRule::Time(f) => hashmap!{ "format" => Value::String(f.clone()) },
296      MatchingRule::Date(f) => hashmap!{ "format" => Value::String(f.clone()) },
297      MatchingRule::Include(s) => hashmap!{ "value" => Value::String(s.clone()) },
298      MatchingRule::Number => empty,
299      MatchingRule::Integer => empty,
300      MatchingRule::Decimal => empty,
301      MatchingRule::Null => empty,
302      MatchingRule::ContentType(ct) => hashmap!{ "value" => Value::String(ct.clone()) },
303      MatchingRule::ArrayContains(variants) => hashmap! { "variants" =>
304        variants.iter().map(|(variant, rules, gens)| {
305          Value::Array(vec![json!(variant), rules.to_v3_json(), Value::Object(gens.iter().map(|(key, g)| {
306            (key.to_string(), g.to_json().unwrap())
307          }).collect())])
308        }).collect()
309      },
310      MatchingRule::Values => empty,
311      MatchingRule::Boolean => empty,
312      MatchingRule::StatusCode(sc) => hashmap!{ "status" => sc.to_json() },
313      MatchingRule::NotEmpty => empty,
314      MatchingRule::Semver => empty,
315      MatchingRule::EachKey(definition) | MatchingRule::EachValue(definition) => {
316        let mut map = hashmap! {
317          "rules" => Value::Array(definition.rules.iter()
318            .map(|rule| rule.as_ref().expect_left("Expected a matching rule, found an unresolved reference").to_json())
319            .collect())
320        };
321
322        if !definition.value.is_empty() {
323          map.insert("value", Value::String(definition.value.clone()));
324        }
325
326        if let Some(generator) = &definition.generator {
327          map.insert("generator", generator.to_json().unwrap_or_default());
328        }
329
330        map
331      }
332    }
333  }
334
335  /// Creates a `MatchingRule` from a type and a map of attributes
336  pub fn create(rule_type: &str, attributes: &Value) -> anyhow::Result<MatchingRule> {
337    trace!("rule_type: {}, attributes: {}", rule_type, attributes);
338    let attributes = match attributes {
339      Value::Object(values) => values.clone(),
340      Value::Null => Map::default(),
341      _ => {
342        error!("Matching rule attributes {} are not valid", attributes);
343        return Err(anyhow!("Matching rule attributes {} are not valid", attributes));
344      }
345    };
346    match rule_type {
347      "regex" => match attributes.get(rule_type) {
348        Some(s) => Ok(MatchingRule::Regex(json_to_string(s))),
349        None => Err(anyhow!("Regex matcher missing 'regex' field")),
350      },
351      "equality" => Ok(MatchingRule::Equality),
352      "include" => match attributes.get("value") {
353        Some(s) => Ok(MatchingRule::Include(json_to_string(s))),
354        None => Err(anyhow!("Include matcher missing 'value' field")),
355      },
356      "type" => match (json_to_num(attributes.get("min").cloned()), json_to_num(attributes.get("max").cloned())) {
357        (Some(min), Some(max)) => Ok(MatchingRule::MinMaxType(min, max)),
358        (Some(min), None) => Ok(MatchingRule::MinType(min)),
359        (None, Some(max)) => Ok(MatchingRule::MaxType(max)),
360        _ => Ok(MatchingRule::Type)
361      },
362      "number" => Ok(MatchingRule::Number),
363      "integer" => Ok(MatchingRule::Integer),
364      "decimal" => Ok(MatchingRule::Decimal),
365      "real" => Ok(MatchingRule::Decimal),
366      "boolean" => Ok(MatchingRule::Boolean),
367      "min" => match json_to_num(attributes.get(rule_type).cloned()) {
368        Some(min) => Ok(MatchingRule::MinType(min)),
369        None => Err(anyhow!("Min matcher missing 'min' field")),
370      },
371      "max" => match json_to_num(attributes.get(rule_type).cloned()) {
372        Some(max) => Ok(MatchingRule::MaxType(max)),
373        None => Err(anyhow!("Max matcher missing 'max' field")),
374      },
375      "min-type" => match json_to_num(attributes.get("min").cloned()) {
376        Some(min) => Ok(MatchingRule::MinType(min)),
377        None => Err(anyhow!("Min matcher missing 'min' field")),
378      },
379      "max-type" => match json_to_num(attributes.get("max").cloned()) {
380        Some(max) => Ok(MatchingRule::MaxType(max)),
381        None => Err(anyhow!("Max matcher missing 'max' field")),
382      },
383      "timestamp" | "datetime" => match attributes.get("format").or_else(|| attributes.get(rule_type)) {
384        Some(s) => Ok(MatchingRule::Timestamp(json_to_string(s))),
385        None => Ok(MatchingRule::Timestamp(String::default())),
386      },
387      "date" => match attributes.get("format").or_else(|| attributes.get(rule_type)) {
388        Some(s) => Ok(MatchingRule::Date(json_to_string(s))),
389        None => Ok(MatchingRule::Date(String::default())),
390      },
391      "time" => match attributes.get("format").or_else(|| attributes.get(rule_type)) {
392        Some(s) => Ok(MatchingRule::Time(json_to_string(s))),
393        None => Ok(MatchingRule::Time(String::default()))
394      },
395      "null" => Ok(MatchingRule::Null),
396      "contentType" | "content-type" => match attributes.get("value") {
397        Some(s) => Ok(MatchingRule::ContentType(json_to_string(s))),
398        None => Err(anyhow!("ContentType matcher missing 'value' field")),
399      },
400      "arrayContains" | "array-contains" => match attributes.get("variants") {
401        Some(variants) => match variants {
402          Value::Array(variants) => {
403            let mut values = Vec::new();
404            for variant in variants {
405              let index = json_to_num(variant.get("index").cloned()).unwrap_or_default();
406              let mut category = MatchingRuleCategory::empty("body");
407              if let Some(rules) = variant.get("rules") {
408                category.add_rules_from_json(rules)
409                  .with_context(||
410                    format!("Unable to parse matching rules: {:?}", rules))?;
411              } else {
412                category.add_rule(
413                  DocPath::empty(), MatchingRule::Equality, RuleLogic::And);
414              }
415              let generators = if let Some(generators_json) = variant.get("generators") {
416                let mut g = Generators::default();
417                let cat = GeneratorCategory::BODY;
418                if let Value::Object(map) = generators_json {
419                  for (k, v) in map {
420                    if let Value::Object(map) = v {
421                      let path = DocPath::new(k)?;
422                      g.parse_generator_from_map(&cat, map, Some(path));
423                    }
424                  }
425                }
426                g.categories.get(&cat).cloned().unwrap_or_default()
427              } else {
428                HashMap::default()
429              };
430              values.push((index, category, generators));
431            }
432            Ok(MatchingRule::ArrayContains(values))
433          }
434          _ => Err(anyhow!("ArrayContains matcher 'variants' field is not an Array")),
435        }
436        None => Err(anyhow!("ArrayContains matcher missing 'variants' field")),
437      }
438      "values" => Ok(MatchingRule::Values),
439      "statusCode" | "status-code" => match attributes.get("status") {
440        Some(s) => {
441          let status = HttpStatus::from_json(s)
442            .context("Unable to parse status code for StatusCode matcher")?;
443          Ok(MatchingRule::StatusCode(status))
444        },
445        None => Ok(MatchingRule::StatusCode(HttpStatus::Success))
446      },
447      "notEmpty" | "not-empty" => Ok(MatchingRule::NotEmpty),
448      "semver" => Ok(MatchingRule::Semver),
449      "eachKey" | "each-key" => {
450        let generator = generator_from_json(&attributes);
451        let value = attributes.get("value").cloned().unwrap_or_default();
452        let rules = rules_from_json(&attributes)?;
453        let definition = MatchingRuleDefinition {
454          value: json_to_string(&value),
455          value_type: ValueType::Unknown,
456          rules,
457          generator,
458          expression: "".to_string()
459        };
460        Ok(MatchingRule::EachKey(definition))
461      }
462      "eachValue" | "each-value" => {
463        let generator = generator_from_json(&attributes);
464        let value = attributes.get("value").cloned().unwrap_or_default();
465        let rules = rules_from_json(&attributes)?;
466        let definition = MatchingRuleDefinition {
467          value: json_to_string(&value),
468          value_type: ValueType::Unknown,
469          rules,
470          generator,
471          expression: "".to_string()
472        };
473        Ok(MatchingRule::EachValue(definition))
474      }
475      _ => Err(anyhow!("{} is not a valid matching rule type", rule_type)),
476    }
477  }
478
479  /// If this matching rule is a values matcher (ignores keys in maps)
480  pub fn is_values_matcher(&self) -> bool {
481    match self {
482      MatchingRule::Values => true,
483      MatchingRule::EachValue(_) => true,
484      _ => false
485    }
486  }
487
488  /// If this matching rule is a type matcher
489  pub fn is_type_matcher(&self) -> bool {
490    match self {
491      MatchingRule::Type => true,
492      MatchingRule::MinType(_) => true,
493      MatchingRule::MaxType(_) => true,
494      MatchingRule::MinMaxType(_, _) => true,
495      _ => false
496    }
497  }
498
499  /// If this matching rule is a type matcher that includes a length restriction
500  pub fn is_length_type_matcher(&self) -> bool {
501    match self {
502      MatchingRule::MinType(_) => true,
503      MatchingRule::MaxType(_) => true,
504      MatchingRule::MinMaxType(_, _) => true,
505      _ => false
506    }
507  }
508
509  /// If this matcher should cascade to children
510  pub fn can_cascade(&self) -> bool {
511    match self {
512      MatchingRule::Values => false,
513      MatchingRule::EachValue(_) => false,
514      MatchingRule::EachKey(_) => false,
515      _ => true
516    }
517  }
518
519  /// Generates a description of the matching rule that can be used in a test report.
520  /// `for_collection` will be true if the description is for a collection, otherwise it is for
521  /// a single item.
522  pub fn generate_description(&self, for_collection: bool) -> String {
523    match self {
524      MatchingRule::Equality => "must be equal to the expected value".to_string(),
525      MatchingRule::Regex(r) => format!("must match the regular expression /{}/", r),
526      MatchingRule::Type => "must match by type".to_string(),
527      MatchingRule::MinType(min) => if for_collection {
528        format!("must match by type and have at least {} item{}", min, if *min == 1 { "" } else { "s" })
529      } else {
530        "must match by type".to_string()
531      },
532      MatchingRule::MaxType(max) => if for_collection {
533        format!("must match by type and have at most {} item{}", max, if *max == 1 { "" } else { "s" })
534      } else {
535        "must match by type".to_string()
536      },
537      MatchingRule::MinMaxType(min, max) => if for_collection {
538        format!("must match by type and have at {}..{} items", min, max)
539      } else {
540        "must match by type".to_string()
541      },
542      MatchingRule::Timestamp(f) => format!("must match the date-time format '{}'", f),
543      MatchingRule::Time(f) => format!("must match the time format '{}'", f),
544      MatchingRule::Date(f) => format!("must match the date format '{}'", f),
545      MatchingRule::Include(s) => format!("must include the string '{}'", s),
546      MatchingRule::Number => "must be a number".to_string(),
547      MatchingRule::Integer => "must be an integer".to_string(),
548      MatchingRule::Decimal => "must be a number with at least one digit after the decimal point".to_string(),
549      MatchingRule::Null => "must be null".to_string(),
550      MatchingRule::ContentType(ct) => format!("the content type must be '{}'", ct),
551      MatchingRule::ArrayContains(_) => "must be a list/array that has at least one matching item".to_string(),
552      MatchingRule::Values => "have values that match".to_string(),
553      MatchingRule::Boolean => "must be a boolean".to_string(),
554      MatchingRule::StatusCode(s) => match s {
555        HttpStatus::Information => "must be an Information (10x) status".to_string(),
556        HttpStatus::Success => "must be a Success (20x) status".to_string(),
557        HttpStatus::Redirect => "must be a redirect (30x) status".to_string(),
558        HttpStatus::ClientError => "must be a Client Error (40x) status".to_string(),
559        HttpStatus::ServerError => "must be an Server Error (50x) status".to_string(),
560        HttpStatus::StatusCodes(c) => format!("must be a status code of {}", c.iter().join(", ")),
561        HttpStatus::NonError => "must not be an Error (40x/50x) status".to_string(),
562        HttpStatus::Error => "must be an Error (40x/50x) status".to_string(),
563      }
564      MatchingRule::NotEmpty => "must not be empty".to_string(),
565      MatchingRule::Semver => "must match a semver version".to_string(),
566      MatchingRule::EachKey(m) => format!("each key must match '{}'", m.expression()),
567      MatchingRule::EachValue(m) => format!("each value must match '{}'", m.expression())
568    }
569  }
570
571  /// Clones this matching rule, downgrading it if it doesn't correspond to a matching rule that
572  /// can be applied to single values. Examples are the Min/Max type matchers, which just apply
573  /// basic type matchers to single values.
574  pub fn for_single_item(&self) -> MatchingRule {
575    match self {
576      MatchingRule::MinType(_) => MatchingRule::Type,
577      MatchingRule::MaxType(_) => MatchingRule::Type,
578      MatchingRule::MinMaxType(_, _) => MatchingRule::Type,
579      _ => self.clone()
580    }
581  }
582
583  /// If the matching rule can be applied data in the form of the content type
584  pub fn can_match(&self, content_type: &ContentType) -> bool {
585    match self {
586      MatchingRule::NotEmpty => true,
587      MatchingRule::ArrayContains(_) => true, // why? This is set in Pact-JVM
588      MatchingRule::ContentType(_) => true,
589      MatchingRule::Equality => true,
590      MatchingRule::Include(_) => true,
591      MatchingRule::Regex(_) => content_type.is_text(),
592      _ => false
593    }
594  }
595}
596
597impl Hash for MatchingRule {
598  fn hash<H: Hasher>(&self, state: &mut H) {
599    mem::discriminant(self).hash(state);
600    match self {
601      MatchingRule::Regex(s) => s.hash(state),
602      MatchingRule::MinType(min) => min.hash(state),
603      MatchingRule::MaxType(max) => max.hash(state),
604      MatchingRule::MinMaxType(min, max) => {
605        min.hash(state);
606        max.hash(state);
607      }
608      MatchingRule::Timestamp(format) => format.hash(state),
609      MatchingRule::Time(format) => format.hash(state),
610      MatchingRule::Date(format) => format.hash(state),
611      MatchingRule::Include(str) => str.hash(state),
612      MatchingRule::ContentType(str) => str.hash(state),
613      MatchingRule::ArrayContains(variants) => {
614        for (index, rules, generators) in variants {
615          index.hash(state);
616          rules.hash(state);
617          for (s, g) in generators {
618            s.hash(state);
619            g.hash(state);
620          }
621        }
622      }
623      _ => ()
624    }
625  }
626}
627
628impl PartialEq for MatchingRule {
629  fn eq(&self, other: &Self) -> bool {
630    match (self, other) {
631      (MatchingRule::Regex(s1), MatchingRule::Regex(s2)) => s1 == s2,
632      (MatchingRule::MinType(min1), MatchingRule::MinType(min2)) => min1 == min2,
633      (MatchingRule::MaxType(max1), MatchingRule::MaxType(max2)) => max1 == max2,
634      (MatchingRule::MinMaxType(min1, max1), MatchingRule::MinMaxType(min2, max2)) => min1 == min2 && max1 == max2,
635      (MatchingRule::Timestamp(format1), MatchingRule::Timestamp(format2)) => format1 == format2,
636      (MatchingRule::Time(format1), MatchingRule::Time(format2)) => format1 == format2,
637      (MatchingRule::Date(format1), MatchingRule::Date(format2)) => format1 == format2,
638      (MatchingRule::Include(str1), MatchingRule::Include(str2)) => str1 == str2,
639      (MatchingRule::ContentType(str1), MatchingRule::ContentType(str2)) => str1 == str2,
640      (MatchingRule::ArrayContains(variants1), MatchingRule::ArrayContains(variants2)) => variants1 == variants2,
641      (MatchingRule::EachKey(definition1), MatchingRule::EachKey(definition2)) => definition1 == definition2,
642      (MatchingRule::EachValue(definition1), MatchingRule::EachValue(definition2)) => definition1 == definition2,
643      _ => mem::discriminant(self) == mem::discriminant(other)
644    }
645  }
646}
647
648/// Enumeration to define how to combine rules
649#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, PartialOrd, Ord)]
650pub enum RuleLogic {
651  /// All rules must match
652  And,
653  /// At least one rule must match
654  Or
655}
656
657impl RuleLogic {
658  fn to_json(&self) -> Value {
659    Value::String(match self {
660      RuleLogic::And => "AND",
661      RuleLogic::Or => "OR"
662    }.into())
663  }
664}
665
666/// Data structure for representing a list of rules and the logic needed to combine them
667#[derive(Debug, Clone, Eq)]
668pub struct RuleList {
669  /// List of rules to apply
670  pub rules: Vec<MatchingRule>,
671  /// Rule logic to use to evaluate multiple rules
672  pub rule_logic: RuleLogic,
673  /// If this rule list has matched the exact path or if it has cascaded (i.e. is a parent)
674  pub cascaded: bool
675}
676
677impl RuleList {
678
679  /// Creates a new empty rule list
680  pub fn empty(rule_logic: RuleLogic) -> RuleList {
681    RuleList {
682      rules: Vec::new(),
683      rule_logic,
684      cascaded: false
685    }
686  }
687
688  /// Creates a default rule list with an equality matcher
689  pub fn equality() -> RuleList {
690    RuleList {
691      rules: vec![ MatchingRule::Equality ],
692      rule_logic: RuleLogic::And,
693      cascaded: false
694    }
695  }
696
697  /// Creates a new rule list with the single matching rule
698  pub fn new(rule: MatchingRule) -> RuleList {
699    RuleList {
700      rules: vec![ rule ],
701      rule_logic: RuleLogic::And,
702      cascaded: false
703    }
704  }
705
706  /// If the rule list is empty (has no matchers)
707  pub fn is_empty(&self) -> bool {
708    self.rules.is_empty()
709  }
710
711  fn to_v3_json(&self) -> Value {
712    json!({
713      "combine": self.rule_logic.to_json(),
714      "matchers": Value::Array(self.rules.iter().map(|matcher| matcher.to_json()).collect())
715    })
716  }
717
718  fn to_v2_json(&self) -> Value {
719    match self.rules.get(0) {
720      Some(rule) => rule.to_json(),
721      None => json!({})
722    }
723  }
724
725  /// If there is a type matcher defined for the rule list
726  pub fn type_matcher_defined(&self) -> bool {
727    self.rules.iter().any(|rule| match rule {
728      MatchingRule::Type => true,
729      MatchingRule::MinType(_) => true,
730      MatchingRule::MaxType(_) => true,
731      MatchingRule::MinMaxType(_, _) => true,
732      _ => false
733    })
734  }
735
736  /// If the values matcher is defined for the rule list
737  pub fn values_matcher_defined(&self) -> bool {
738    self.rules.iter().any(MatchingRule::is_values_matcher)
739  }
740
741  /// If there are any matchers that are not type matchers
742  pub fn has_non_type_matchers(&self) -> bool {
743    self.rules.iter().any(|rule| match rule {
744      MatchingRule::Type => false,
745      MatchingRule::MinType(_) => false,
746      MatchingRule::MaxType(_) => false,
747      MatchingRule::MinMaxType(_, _) => false,
748      _ => true
749    })
750  }
751
752  /// Add a matching rule to the rule list
753  pub fn add_rule(&mut self, rule: &MatchingRule) {
754    self.rules.push(rule.clone())
755  }
756
757  /// If this rule list has matched the exact path or if it has cascaded (i.e. is a parent)
758  pub fn as_cascaded(&self, b: bool) -> RuleList {
759    RuleList {
760      cascaded: b,
761      .. self.clone()
762    }
763  }
764
765  /// Add all the rules from the list to this list
766  pub fn add_rules(&mut self, rules: &RuleList) {
767    for rule in &rules.rules {
768      self.add_rule(rule);
769    }
770  }
771
772  /// Create a new rule list with all the rules from both
773  pub fn and_rules(&self, rules: &RuleList) -> RuleList {
774    let mut base_list = self.clone();
775    base_list.add_rules(rules);
776    base_list
777  }
778
779  /// Creates a new list with no duplicated rules
780  pub fn remove_duplicates(&self) -> RuleList {
781    RuleList {
782      rules: self.rules.iter().unique().cloned().collect(),
783      rule_logic: self.rule_logic,
784      cascaded: self.cascaded
785    }
786  }
787
788  /// Generates a description of the matching rules that can be displayed in a test report.
789  /// `for_collection` changes the description for collections or single items.
790  pub fn generate_description(&self, for_collection: bool) -> String {
791    if self.rules.len() == 0 {
792      "no-op".to_string()
793    } else if self.rules.len() == 1 {
794      self.rules[0].generate_description(for_collection)
795    } else {
796      self.rules.iter()
797        .map(|rule| rule.generate_description(for_collection))
798        .join(", ")
799    }
800  }
801
802  /// Filters this list with the given predicate, returning a new list
803  pub fn filter<F>(&self, predicate: F) -> RuleList where F: FnMut(&&MatchingRule) -> bool {
804    RuleList {
805      rules: self.rules.iter().filter(predicate).cloned().collect(),
806      rule_logic: self.rule_logic,
807      cascaded: self.cascaded
808    }
809  }
810
811  /// If all the matching rules in this list can match data on the form of the content type
812  pub fn can_match(&self, content_type: &ContentType) -> bool {
813    self.rules.iter().all(|rule| rule.can_match(content_type))
814  }
815}
816
817impl Hash for RuleList {
818  fn hash<H: Hasher>(&self, state: &mut H) {
819    self.rule_logic.hash(state);
820    for rule in &self.rules {
821      rule.hash(state);
822    }
823  }
824}
825
826impl PartialEq for RuleList {
827  fn eq(&self, other: &Self) -> bool {
828    self.rule_logic == other.rule_logic &&
829      self.rules == other.rules
830  }
831}
832
833impl Default for RuleList {
834  fn default() -> Self {
835    RuleList::empty(RuleLogic::And)
836  }
837}
838
839/// Category that the matching rule is applied to
840#[derive(PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord)]
841pub enum Category {
842  /// Request Method
843  METHOD,
844  /// Request Path
845  PATH,
846  /// Request/Response Header
847  HEADER,
848  /// Request Query Parameter
849  QUERY,
850  /// Body
851  BODY,
852  /// Response Status
853  STATUS,
854  /// Message contents (body)
855  CONTENTS,
856  /// Message metadata
857  METADATA
858}
859
860impl Category {
861  pub(crate) fn v2_form(&self) -> String {
862    match self {
863      Category::HEADER => "headers".to_string(),
864      _ => self.to_string()
865    }
866  }
867}
868
869impl FromStr for Category {
870  type Err = String;
871
872  fn from_str(s: &str) -> Result<Self, Self::Err> {
873    match s.to_lowercase().as_str() {
874      "method" => Ok(Category::METHOD),
875      "path" => Ok(Category::PATH),
876      "header" => Ok(Category::HEADER),
877      "headers" => Ok(Category::HEADER),
878      "query" => Ok(Category::QUERY),
879      "body" => Ok(Category::BODY),
880      "status" => Ok(Category::STATUS),
881      "contents" => Ok(Category::CONTENTS),
882      "metadata" => Ok(Category::METADATA),
883      _ => Err(format!("'{}' is not a valid Category", s))
884    }
885  }
886}
887
888impl <'a> Into<&'a str> for Category {
889  fn into(self) -> &'a str {
890    match self {
891      Category::METHOD => "method",
892      Category::PATH => "path",
893      Category::HEADER => "header",
894      Category::QUERY => "query",
895      Category::BODY => "body",
896      Category::STATUS => "status",
897      Category::CONTENTS => "contents",
898      Category::METADATA => "metadata"
899    }
900  }
901}
902
903impl Into<String> for Category {
904  fn into(self) -> String {
905    self.to_string()
906  }
907}
908
909impl <'a> From<&'a str> for Category {
910  fn from(s: &'a str) -> Self {
911    Category::from_str(s).unwrap_or_default()
912  }
913}
914
915impl From<String> for Category {
916  fn from(s: String) -> Self {
917    Category::from_str(&s).unwrap_or_default()
918  }
919}
920
921impl Default for Category {
922  fn default() -> Self {
923    Category::BODY
924  }
925}
926
927impl Display for Category {
928  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
929    let s: &str = self.clone().into();
930    write!(f, "{}", s)
931  }
932}
933
934/// Data structure for representing a category of matching rules
935#[derive(Debug, Clone, Eq, Default)]
936pub struct MatchingRuleCategory {
937  /// Name of the category
938  pub name: Category,
939  /// Matching rules for this category
940  pub rules: HashMap<DocPath, RuleList>
941}
942
943impl MatchingRuleCategory {
944  /// Creates an empty category
945  pub fn empty<S>(name: S) -> MatchingRuleCategory
946    where S: Into<Category>
947  {
948    MatchingRuleCategory {
949      name: name.into(),
950      rules: hashmap! {},
951    }
952  }
953
954  /// Creates a default category
955  pub fn equality<S>(name: S) -> MatchingRuleCategory
956    where S: Into<Category>
957  {
958    MatchingRuleCategory {
959      name: name.into(),
960      rules: hashmap! {
961        DocPath::empty() => RuleList::equality()
962      }
963    }
964  }
965
966  /// If the matching rules in the category are empty
967  pub fn is_empty(&self) -> bool {
968    self.rules.is_empty()
969  }
970
971  /// If the matching rules in the category are not empty
972  pub fn is_not_empty(&self) -> bool {
973    !self.rules.is_empty()
974  }
975
976  /// Adds a rule from the Value representation
977  pub fn rule_from_json(
978    &mut self,
979    key: DocPath,
980    matcher_json: &Value,
981    rule_logic: RuleLogic,
982  ) -> anyhow::Result<()> {
983    let matching_rule = MatchingRule::from_json(matcher_json)
984      .with_context(|| format!("Could not parse matcher JSON {:?}", matcher_json))?;
985
986    let rules = self.rules.entry(key)
987      .or_insert_with(|| RuleList::empty(rule_logic));
988    rules.rules.push(matching_rule);
989    Ok(())
990  }
991
992  /// Adds a rule to this category
993  pub fn add_rule(
994    &mut self,
995    key: DocPath,
996    matcher: MatchingRule,
997    rule_logic: RuleLogic,
998  ) {
999    let rules = self.rules.entry(key).or_insert_with(|| RuleList::empty(rule_logic));
1000    rules.rules.push(matcher);
1001  }
1002
1003  /// Filters the matchers in the category by the predicate, and returns a new category
1004  pub fn filter<F>(&self, predicate: F) -> MatchingRuleCategory
1005    where F : Fn(&(&DocPath, &RuleList)) -> bool {
1006    MatchingRuleCategory {
1007      name: self.name.clone(),
1008      rules: self.rules.iter().filter(predicate)
1009        .map(|(path, rules)| (path.clone(), rules.clone())).collect()
1010    }
1011  }
1012
1013  fn max_by_path(&self, path: &[&str]) -> RuleList {
1014    self.rules.iter().map(|(k, v)| (k, v, k.path_weight(path)))
1015      .filter(|&(_, _, (w, _))| w > 0)
1016      .max_by_key(|&(_, _, (w, t))| w * t)
1017      .map(|(_, v, (_, t))| v.as_cascaded(t != path.len()))
1018      .unwrap_or_default()
1019  }
1020
1021  /// Returns a JSON Value representation in V3 format
1022  pub fn to_v3_json(&self) -> Value {
1023    Value::Object(self.rules.iter().fold(serde_json::Map::new(), |mut map, (category, rulelist)| {
1024      match self.name {
1025        Category::HEADER | Category::QUERY => {
1026          let name = category.first_field().map(|v| v.to_string())
1027            .unwrap_or_else(|| category.to_string());
1028          map.insert(name, rulelist.to_v3_json());
1029        }
1030        _ => {
1031          map.insert(String::from(category), rulelist.to_v3_json());
1032        }
1033      }
1034      map
1035    }))
1036  }
1037
1038  /// Returns a JSON Value representation in V2 format
1039  pub fn to_v2_json(&self) -> HashMap<String, Value> {
1040    let mut map = hashmap!{};
1041
1042    match &self.name {
1043      Category::PATH => for (_, v) in self.rules.clone() {
1044        map.insert("$.path".to_string(), v.to_v2_json());
1045      }
1046      Category::BODY => for (k, v) in self.rules.clone() {
1047        map.insert(String::from(k).replace("$", "$.body"), v.to_v2_json());
1048      }
1049      Category::HEADER | Category::QUERY => for (k, v) in &self.rules {
1050        let mut path = DocPath::root();
1051        path.push_field(self.name.v2_form());
1052        path.push_path(k);
1053        map.insert(path.to_string(), v.to_v2_json());
1054      }
1055      _ => for (k, v) in &self.rules {
1056        map.insert(format!("$.{}.{}", self.name.v2_form(), k), v.to_v2_json());
1057      }
1058    };
1059
1060    map
1061  }
1062
1063  /// If there is a type matcher defined for the category
1064  pub fn type_matcher_defined(&self) -> bool {
1065    self.rules.values().any(|rule_list| rule_list.type_matcher_defined())
1066  }
1067
1068  /// If there is a values matcher defined in the rules
1069  pub fn values_matcher_defined(&self) -> bool {
1070    self.rules.values().any(|rule_list| rule_list.values_matcher_defined())
1071  }
1072
1073  /// If there is a matcher defined for the path
1074  pub fn matcher_is_defined(&self, path: &[&str]) -> bool {
1075    let result = !self.resolve_matchers_for_path(path).is_empty();
1076    trace!("matcher_is_defined: for category {} and path {:?} -> {}", self.name.to_string(), path, result);
1077    result
1078  }
1079
1080  /// filters this category with all rules that match the given path for categories that contain
1081  /// collections (eg. bodies, headers, query parameters). Returns self otherwise.
1082  pub fn resolve_matchers_for_path(&self, path: &[&str]) -> MatchingRuleCategory {
1083    match self.name {
1084      Category::HEADER| Category::QUERY | Category::BODY |
1085      Category::CONTENTS | Category::METADATA => self.filter(|(val, _)| {
1086        val.matches_path(path)
1087      }),
1088      _ => self.clone()
1089    }
1090  }
1091
1092  /// Selects the best matcher for the given path by calculating a weighting for each one
1093  pub fn select_best_matcher(&self, path: &[&str]) -> RuleList {
1094    match self.name {
1095      Category::BODY | Category::METADATA => self.max_by_path(path),
1096      _ => self.resolve_matchers_for_path(path).as_rule_list()
1097    }
1098  }
1099
1100  /// Returns this category as a matching rule list. Returns a None if there are no rules
1101  pub fn as_rule_list(&self) -> RuleList {
1102    self.rules.values().next().cloned().unwrap_or_default()
1103  }
1104
1105  /// Adds the rules to the category from the provided JSON
1106  pub fn add_rules_from_json(&mut self, rules: &Value) -> anyhow::Result<()> {
1107    if self.name == Category::PATH && rules.get("matchers").is_some() {
1108      let rule_logic = match rules.get("combine") {
1109        Some(val) => if json_to_string(val).to_uppercase() == "OR" {
1110          RuleLogic::Or
1111        } else {
1112          RuleLogic::And
1113        },
1114        None => RuleLogic::And
1115      };
1116      if let Some(matchers) = rules.get("matchers") {
1117        if let Value::Array(array) = matchers {
1118          for matcher in array {
1119            self.rule_from_json(DocPath::empty(), &matcher, rule_logic)?;
1120          }
1121        }
1122      }
1123    } else if let Value::Object(m) = rules {
1124      if m.contains_key("matchers") {
1125        self.add_rule_list(DocPath::empty(), rules)?;
1126      } else {
1127        for (k, v) in m {
1128          self.add_rule_list(DocPath::new(k)?, v)?;
1129        }
1130      }
1131    }
1132    Ok(())
1133  }
1134
1135  /// Adds the rules to the category from the provided JSON in V3+ format
1136  pub fn add_v3_rules_from_json(&mut self, rules: &Value) -> anyhow::Result<()> {
1137    if self.name == Category::PATH && rules.get("matchers").is_some() {
1138      let rule_logic = match rules.get("combine") {
1139        Some(val) => if json_to_string(val).to_uppercase() == "OR" {
1140          RuleLogic::Or
1141        } else {
1142          RuleLogic::And
1143        },
1144        None => RuleLogic::And
1145      };
1146      if let Some(matchers) = rules.get("matchers") {
1147        if let Value::Array(array) = matchers {
1148          for matcher in array {
1149            self.rule_from_json(DocPath::empty(), &matcher, rule_logic)?;
1150          }
1151        }
1152      }
1153    } else if let Value::Object(m) = rules {
1154      if m.contains_key("matchers") {
1155        self.add_rule_list(DocPath::empty(), rules)?;
1156      } else if self.name == Category::QUERY || self.name == Category::HEADER {
1157        for (k, v) in m {
1158          let mut path = DocPath::root();
1159          path.push_field(k);
1160          self.add_rule_list(path, v)?;
1161        }
1162      } else {
1163        for (k, v) in m {
1164          self.add_rule_list(DocPath::new(k)?, v)?;
1165        }
1166      }
1167    } else {
1168      return Err(anyhow!("Matching rule JSON {} is not correctly formed", rules));
1169    }
1170    Ok(())
1171  }
1172
1173  fn add_rule_list(&mut self, k: DocPath, v: &Value) -> anyhow::Result<()> {
1174    let rule_logic = match v.get("combine") {
1175      Some(val) => if json_to_string(val).to_uppercase() == "OR" {
1176        RuleLogic::Or
1177      } else {
1178        RuleLogic::And
1179      },
1180      None => RuleLogic::And
1181    };
1182    if let Some(&Value::Array(ref array)) = v.get("matchers") {
1183      for matcher in array {
1184        self.rule_from_json(k.clone(), &matcher, rule_logic)?;
1185      }
1186    }
1187    Ok(())
1188  }
1189
1190  /// Returns any generators associated with these matching rules
1191  pub fn generators(&self) -> HashMap<DocPath, Generator> {
1192    let mut generators = hashmap!{};
1193    for (base_path, rules) in &self.rules {
1194      for rule in &rules.rules {
1195        if rule.has_generators() {
1196          for generator in rule.generators() {
1197            generators.insert(base_path.clone(), generator);
1198          }
1199        }
1200      }
1201    }
1202    generators
1203  }
1204
1205  /// Clones this category with the new name
1206  pub fn rename<S>(&self, name: S) -> Self
1207    where S: Into<Category> {
1208    MatchingRuleCategory {
1209      name: name.into(),
1210      .. self.clone()
1211    }
1212  }
1213
1214  /// Add all the rules from the provided rules
1215  pub fn add_rules(&mut self, category: MatchingRuleCategory) {
1216    for (path, rules) in &category.rules {
1217      if self.rules.contains_key(path) {
1218        self.rules.get_mut(path).unwrap().add_rules(rules)
1219      } else {
1220        self.rules.insert(path.clone(), rules.clone());
1221      }
1222    }
1223  }
1224}
1225
1226impl Hash for MatchingRuleCategory {
1227  fn hash<H: Hasher>(&self, state: &mut H) {
1228    self.name.hash(state);
1229    for (k, v) in self.rules.iter()
1230      .sorted_by(|(a, _), (b, _)| Ord::cmp(a, b)) {
1231      k.hash(state);
1232      v.hash(state);
1233    }
1234  }
1235}
1236
1237impl PartialEq for MatchingRuleCategory {
1238  fn eq(&self, other: &Self) -> bool {
1239    self.name == other.name && self.rules == other.rules
1240  }
1241
1242  fn ne(&self, other: &Self) -> bool {
1243    self.name != other.name || self.rules != other.rules
1244  }
1245}
1246
1247impl PartialOrd for MatchingRuleCategory {
1248  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1249    Some(self.cmp(other))
1250  }
1251}
1252
1253impl Ord for MatchingRuleCategory {
1254  fn cmp(&self, other: &Self) -> Ordering {
1255    self.name.cmp(&other.name)
1256  }
1257}
1258
1259/// Data structure for representing a collection of matchers
1260#[derive(Debug, Clone, Eq)]
1261pub struct MatchingRules {
1262  /// Categories of matching rules
1263  pub rules: HashMap<Category, MatchingRuleCategory>
1264}
1265
1266impl MatchingRules {
1267
1268  /// If the matching rules are empty (that is there are no rules assigned to any categories)
1269  pub fn is_empty(&self) -> bool {
1270    self.rules.values().all(|category| category.is_empty())
1271  }
1272
1273  /// If the matching rules are not empty (that is there is at least one rule assigned to a category)
1274  pub fn is_not_empty(&self) -> bool {
1275    self.rules.values().any(|category| category.is_not_empty())
1276  }
1277
1278  /// Adds the category to the map of rules
1279  pub fn add_category<S>(&mut self, category: S) -> &mut MatchingRuleCategory
1280    where S: Into<Category> + Clone
1281  {
1282    let category = category.into();
1283    if !self.rules.contains_key(&category) {
1284      self.rules.insert(category.clone(), MatchingRuleCategory::empty(category.clone()));
1285    }
1286    self.rules.get_mut(&category).unwrap()
1287  }
1288
1289  /// Returns all the category names in this rule set
1290  pub fn categories(&self) -> HashSet<Category> {
1291    self.rules.keys().cloned().collect()
1292  }
1293
1294  /// Returns the category of rules for a given category name
1295  pub fn rules_for_category<S>(&self, category: S) -> Option<MatchingRuleCategory>
1296    where S: Into<Category> {
1297    self.rules.get(&category.into()).cloned()
1298  }
1299
1300  /// If there is a matcher defined for the category and path
1301  pub fn matcher_is_defined<S>(&self, category: S, path: &Vec<&str>) -> bool
1302    where S: Into<Category> + Clone {
1303    let result = match self.resolve_matchers(category.clone().into(), path) {
1304      Some(ref rules) => !rules.is_empty(),
1305      None => false
1306    };
1307    trace!("matcher_is_defined for category {} and path {:?} -> {}", category.into(), path, result);
1308    result
1309  }
1310
1311  /// If there is a wildcard matcher defined for the category and path
1312  pub fn wildcard_matcher_is_defined<S>(&self, category: S, path: &Vec<&str>) -> bool
1313    where S: Into<Category> + Clone {
1314    match self.resolve_wildcard_matchers(category, path) {
1315      Some(ref rules) => !rules.filter(|&(val, _)| val.is_wildcard()).is_empty(),
1316      None => false
1317    }
1318  }
1319
1320  /// If there is a type matcher defined for the category and path
1321  pub fn type_matcher_defined<S>(&self, category: S, path: &Vec<&str>) -> bool
1322    where S: Into<Category> + Display + Clone {
1323    let result = match self.resolve_matchers(category.clone(), path) {
1324      Some(ref rules) => rules.type_matcher_defined(),
1325      None => false
1326    };
1327    trace!("type_matcher_defined for category {} and path {:?} -> {}", category.into(), path, result);
1328    result
1329  }
1330
1331  /// Returns a `Category` filtered with all rules that match the given path.
1332  pub fn resolve_matchers<S>(&self, category: S, path: &Vec<&str>) -> Option<MatchingRuleCategory>
1333    where S: Into<Category> {
1334    self.rules_for_category(category)
1335      .map(|rules| rules.resolve_matchers_for_path(path))
1336  }
1337
1338  /// Returns a list of rules from the body category that match the given path
1339  pub fn resolve_body_matchers_by_path(&self, path: &Vec<&str>) -> RuleList {
1340    match self.rules_for_category("body") {
1341      Some(category) => category.max_by_path(path),
1342      None => RuleList::default()
1343    }
1344  }
1345
1346  fn resolve_wildcard_matchers<S>(&self, category: S, path: &Vec<&str>) -> Option<MatchingRuleCategory>
1347    where S: Into<Category> + Clone {
1348    let category = category.into();
1349    match category {
1350      Category::BODY => self.rules_for_category(Category::BODY).map(|category| category.filter(|&(val, _)| {
1351        val.matches_path_exactly(path)
1352      })),
1353      Category::HEADER | Category::QUERY => self.rules_for_category(category.clone()).map(|category| category.filter(|&(val, _)| {
1354        path.len() == 1 && Some(path[0]) == val.first_field()
1355      })),
1356      _ => self.rules_for_category(category)
1357    }
1358  }
1359
1360  fn load_from_v2_map(&mut self, map: &serde_json::Map<String, Value>
1361  ) -> anyhow::Result<()> {
1362    for (key, v) in map {
1363      if key.starts_with("$.body") {
1364        if key == "$.body" {
1365          self.add_v2_rule("body", DocPath::root(), v)?;
1366        } else {
1367          self.add_v2_rule("body", DocPath::new(format!("${}", &key[6..]))?, v)?;
1368        }
1369      } else if key.starts_with("$.headers") {
1370        let path = DocPath::new(key)?;
1371        let header_name = path.tokens().iter().filter_map(|t| match t {
1372          PathToken::Field(n) => Some(n.clone()),
1373          _ => None
1374        }).skip(1).next();
1375        let header = header_name.ok_or_else(|| anyhow!("'{}' is not a valid path value for a matching rule", key))?;
1376        self.add_v2_rule("header", DocPath::new(header)?, v)?;
1377      } else {
1378        let path = DocPath::new(key)?;
1379        let mut names = path.tokens().iter().filter_map(|t| match t {
1380          PathToken::Field(n) => Some(n.clone()),
1381          _ => None
1382        });
1383        let category = names.next().ok_or_else(|| anyhow!("'{}' is not a valid path value for a matching rule", key))?;
1384        let name = names.next();
1385        self.add_v2_rule(
1386          category,
1387          if let Some(name) = name { DocPath::new(name)? } else { DocPath::empty() },
1388          v,
1389        )?;
1390      }
1391    }
1392    Ok(())
1393  }
1394
1395  fn load_from_v3_map(&mut self, map: &serde_json::Map<String, Value>
1396  ) -> anyhow::Result<()> {
1397    for (k, v) in map {
1398      let category = self.add_category(k.as_str());
1399      category.add_v3_rules_from_json(v)?;
1400    }
1401    Ok(())
1402  }
1403
1404  fn add_v2_rule<S: Into<String>>(
1405    &mut self,
1406    category_name: S,
1407    sub_category: DocPath,
1408    rule: &Value,
1409  ) -> anyhow::Result<()> {
1410    let category = self.add_category(category_name.into());
1411    category.rule_from_json(sub_category, rule, RuleLogic::And)
1412  }
1413
1414  fn to_v3_json(&self) -> Value {
1415    Value::Object(self.rules.iter()
1416      .fold(serde_json::Map::new(), |mut map, (name, sub_category)| {
1417      match name {
1418        Category::PATH => if let Some(rules) = sub_category.rules.get(&DocPath::empty()).or_else(|| sub_category.rules.get(&DocPath::root())) {
1419          map.insert(name.to_string(), rules.to_v3_json());
1420        }
1421        _ => {
1422          let value = sub_category.to_v3_json();
1423          if let Some(values) = value.as_object() {
1424            if !values.is_empty() {
1425              map.insert(name.to_string(), value);
1426            }
1427          }
1428        }
1429      }
1430      map
1431    }))
1432  }
1433
1434  fn to_v2_json(&self) -> Value {
1435    Value::Object(self.rules.iter().fold(serde_json::Map::new(), |mut map, (_, category)| {
1436      for (key, value) in category.to_v2_json() {
1437        map.insert(key.clone(), value);
1438      }
1439      map
1440    }))
1441  }
1442
1443  /// Clones the matching rules, renaming the category
1444  pub fn rename<S>(&self, old_name: S, new_name: S) -> Self
1445    where S: Into<Category> {
1446    let old = old_name.into();
1447    let new = new_name.into();
1448    MatchingRules {
1449      rules: self.rules.iter().map(|(key, value)| {
1450        if key == &old {
1451          (new.clone(), value.rename(new.clone()))
1452        } else {
1453          (key.clone(), value.clone())
1454        }
1455      }).collect()
1456    }
1457  }
1458
1459  /// Add the rules to the category
1460  pub fn add_rules<S>(&mut self, category: S, rules: MatchingRuleCategory) where S: Into<Category> {
1461    let category = category.into();
1462    let entry = self.rules.entry(category.clone())
1463      .or_insert_with(|| MatchingRuleCategory::empty(category.clone()));
1464    entry.add_rules(rules);
1465  }
1466
1467  /// Merge the rules from the other matching rules into this one.
1468  pub fn merge(&mut self, other: &MatchingRules) {
1469    for (category, rules) in &other.rules {
1470      self.add_rules(category.clone(), rules.clone());
1471    }
1472  }
1473}
1474
1475impl Hash for MatchingRules {
1476  fn hash<H: Hasher>(&self, state: &mut H) {
1477    for (k, v) in self.rules.iter()
1478      .filter(|(_, cat)| cat.is_not_empty()) {
1479      k.hash(state);
1480      v.hash(state);
1481    }
1482  }
1483}
1484
1485impl PartialEq for MatchingRules {
1486  fn eq(&self, other: &Self) -> bool {
1487    let self_rules = self.rules.iter()
1488      .filter(|(_, cat)| cat.is_not_empty())
1489      .sorted_by(|(a, _), (b, _)| Ord::cmp(a, b))
1490      .collect_vec();
1491    let other_rules = other.rules.iter()
1492      .filter(|(_, cat)| cat.is_not_empty())
1493      .sorted_by(|(a, _), (b, _)| Ord::cmp(a, b))
1494      .collect_vec();
1495    self_rules == other_rules
1496  }
1497}
1498
1499impl Default for MatchingRules {
1500  fn default() -> Self {
1501    MatchingRules {
1502      rules: hashmap!{}
1503    }
1504  }
1505}
1506
1507impl Display for MatchingRules {
1508  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1509    for (category, rules) in &self.rules {
1510      write!(f, "{}:\n", category)?;
1511      for (path, rule_list) in &rules.rules {
1512        if rule_list.is_empty() {
1513          write!(f, "    {} => []\n", path)?;
1514        } else if rule_list.rules.len() == 1 {
1515          write!(f, "    {} => {:?}\n", path, rule_list.rules[0])?;
1516        } else {
1517          write!(f, "    {} => [\n", path)?;
1518          for rule in &rule_list.rules {
1519            write!(f, "        {:?}\n", rule)?;
1520          }
1521          write!(f, "    ]\n")?;
1522        }
1523      }
1524    }
1525
1526    Ok(())
1527  }
1528}
1529
1530/// Parses the matching rules from the Value structure
1531pub fn matchers_from_json(value: &Value, deprecated_name: &Option<String>
1532) -> anyhow::Result<MatchingRules> {
1533  let matchers_json = match (value.get("matchingRules"), deprecated_name.clone().and_then(|name| value.get(&name))) {
1534    (Some(v), _) => Some(v),
1535    (None, Some(v)) => Some(v),
1536    (None, None) => None
1537  };
1538
1539  let mut matching_rules = MatchingRules::default();
1540  match matchers_json {
1541    Some(value) => match value {
1542      &Value::Object(ref m) => {
1543        if m.keys().next().unwrap_or(&String::default()).starts_with("$") {
1544          matching_rules.load_from_v2_map(m)?
1545        } else {
1546          matching_rules.load_from_v3_map(m)?
1547        }
1548      },
1549      _ => ()
1550    },
1551    None => ()
1552  }
1553  Ok(matching_rules)
1554}
1555
1556/// Generates a Value structure for the provided matching rules
1557pub fn matchers_to_json(matchers: &MatchingRules, spec_version: &PactSpecification) -> Value {
1558  match spec_version {
1559    PactSpecification::V3 | PactSpecification::V4 => matchers.to_v3_json(),
1560    _ => matchers.to_v2_json()
1561  }
1562}
1563
1564/// Macro to ease constructing matching rules
1565/// Example usage:
1566/// ```
1567/// # use pact_models::matchingrules;
1568/// # use pact_models::matchingrules::MatchingRule;
1569/// matchingrules! {
1570///   "query" => { "user_id" => [ MatchingRule::Regex("^[0-9]+$".to_string()) ] }
1571/// };
1572/// ```
1573#[macro_export]
1574macro_rules! matchingrules {
1575    ( $( $name:expr => {
1576        $( $subname:expr => [ $( $matcher:expr ), * ] ),*
1577    }), * ) => {{
1578        let mut _rules = $crate::matchingrules::MatchingRules::default();
1579        $({
1580            let mut _category = _rules.add_category($name);
1581            $({
1582              $({
1583                _category.add_rule(
1584                  $crate::path_exp::DocPath::new_unwrap($subname),
1585                  $matcher,
1586                  $crate::matchingrules::RuleLogic::And,
1587                );
1588              })*
1589            })*
1590        })*
1591        _rules
1592    }};
1593}
1594
1595/// Macro to ease constructing matching rules
1596/// Example usage:
1597/// ```
1598/// # use pact_models::matchingrules_list;
1599/// # use pact_models::matchingrules::MatchingRule;
1600/// matchingrules_list! {
1601///   "body"; "user_id" => [ MatchingRule::Regex("^[0-9]+$".to_string()) ]
1602/// };
1603/// ```
1604#[macro_export]
1605macro_rules! matchingrules_list {
1606  ( $name:expr ; $( $subname:expr => [ $( $matcher:expr ), * ] ),* ) => {{
1607    let mut _category = $crate::matchingrules::MatchingRuleCategory::empty($name);
1608    $(
1609      $(
1610        _category.add_rule(
1611          $crate::path_exp::DocPath::new_unwrap($subname),
1612          $matcher,
1613          $crate::matchingrules::RuleLogic::And,
1614        );
1615      )*
1616    )*
1617    _category
1618  }};
1619
1620  ( $name:expr ; [ $( $matcher:expr ), * ] ) => {{
1621    let mut _category = $crate::matchingrules::MatchingRuleCategory::empty($name);
1622    $(
1623      _category.add_rule(
1624        $crate::path_exp::DocPath::empty(),
1625        $matcher,
1626        $crate::matchingrules::RuleLogic::And,
1627      );
1628    )*
1629    _category
1630  }};
1631}
1632
1633#[cfg(test)]
1634mod tests {
1635  use std::collections::hash_map::DefaultHasher;
1636
1637  use expectest::prelude::*;
1638  use maplit::hashset;
1639  use pretty_assertions::assert_eq;
1640  use serde_json::Value;
1641  use speculate::speculate;
1642
1643  use crate::generators::*;
1644
1645  use super::*;
1646  use super::super::*;
1647
1648  fn h<H: Hash>(rule: &H) -> u64 {
1649    let mut hasher = DefaultHasher::new();
1650    rule.hash(&mut hasher);
1651    hasher.finish()
1652  }
1653
1654  #[test]
1655  fn hash_and_partial_eq_for_matching_rule() {
1656    expect!(h(&MatchingRule::Equality)).to(be_equal_to(h(&MatchingRule::Equality)));
1657    expect!(MatchingRule::Equality).to(be_equal_to(MatchingRule::Equality));
1658    expect!(MatchingRule::Equality).to_not(be_equal_to(MatchingRule::Type));
1659
1660    expect!(h(&MatchingRule::Type)).to(be_equal_to(h(&MatchingRule::Type)));
1661    expect!(MatchingRule::Type).to(be_equal_to(MatchingRule::Type));
1662
1663    expect!(h(&MatchingRule::Number)).to(be_equal_to(h(&MatchingRule::Number)));
1664    expect!(MatchingRule::Number).to(be_equal_to(MatchingRule::Number));
1665
1666    expect!(h(&MatchingRule::Integer)).to(be_equal_to(h(&MatchingRule::Integer)));
1667    expect!(MatchingRule::Integer).to(be_equal_to(MatchingRule::Integer));
1668
1669    expect!(h(&MatchingRule::Decimal)).to(be_equal_to(h(&MatchingRule::Decimal)));
1670    expect!(MatchingRule::Decimal).to(be_equal_to(MatchingRule::Decimal));
1671
1672    expect!(h(&MatchingRule::Null)).to(be_equal_to(h(&MatchingRule::Null)));
1673    expect!(MatchingRule::Null).to(be_equal_to(MatchingRule::Null));
1674
1675    let regex1 = MatchingRule::Regex("\\d+".into());
1676    let regex2 = MatchingRule::Regex("\\w+".into());
1677
1678    expect!(h(&regex1)).to(be_equal_to(h(&regex1)));
1679    expect!(&regex1).to(be_equal_to(&regex1));
1680    expect!(h(&regex1)).to_not(be_equal_to(h(&regex2)));
1681    expect!(&regex1).to_not(be_equal_to(&regex2));
1682
1683    let min1 = MatchingRule::MinType(100);
1684    let min2 = MatchingRule::MinType(200);
1685
1686    expect!(h(&min1)).to(be_equal_to(h(&min1)));
1687    expect!(&min1).to(be_equal_to(&min1));
1688    expect!(h(&min1)).to_not(be_equal_to(h(&min2)));
1689    expect!(&min1).to_not(be_equal_to(&min2));
1690
1691    let max1 = MatchingRule::MaxType(100);
1692    let max2 = MatchingRule::MaxType(200);
1693
1694    expect!(h(&max1)).to(be_equal_to(h(&max1)));
1695    expect!(&max1).to(be_equal_to(&max1));
1696    expect!(h(&max1)).to_not(be_equal_to(h(&max2)));
1697    expect!(&max1).to_not(be_equal_to(&max2));
1698
1699    let minmax1 = MatchingRule::MinMaxType(100, 200);
1700    let minmax2 = MatchingRule::MinMaxType(200, 200);
1701
1702    expect!(h(&minmax1)).to(be_equal_to(h(&minmax1)));
1703    expect!(&minmax1).to(be_equal_to(&minmax1));
1704    expect!(h(&minmax1)).to_not(be_equal_to(h(&minmax2)));
1705    expect!(&minmax1).to_not(be_equal_to(&minmax2));
1706
1707    let datetime1 = MatchingRule::Timestamp("yyyy-MM-dd HH:mm:ss".into());
1708    let datetime2 = MatchingRule::Timestamp("yyyy-MM-ddTHH:mm:ss".into());
1709
1710    expect!(h(&datetime1)).to(be_equal_to(h(&datetime1)));
1711    expect!(&datetime1).to(be_equal_to(&datetime1));
1712    expect!(h(&datetime1)).to_not(be_equal_to(h(&datetime2)));
1713    expect!(&datetime1).to_not(be_equal_to(&datetime2));
1714
1715    let date1 = MatchingRule::Date("yyyy-MM-dd".into());
1716    let date2 = MatchingRule::Date("yy-MM-dd".into());
1717
1718    expect!(h(&date1)).to(be_equal_to(h(&date1)));
1719    expect!(&date1).to(be_equal_to(&date1));
1720    expect!(h(&date1)).to_not(be_equal_to(h(&date2)));
1721    expect!(&date1).to_not(be_equal_to(&date2));
1722
1723    let time1 = MatchingRule::Time("HH:mm:ss".into());
1724    let time2 = MatchingRule::Time("hh:mm:ss".into());
1725
1726    expect!(h(&time1)).to(be_equal_to(h(&time1)));
1727    expect!(&time1).to(be_equal_to(&time1));
1728    expect!(h(&time1)).to_not(be_equal_to(h(&time2)));
1729    expect!(&time1).to_not(be_equal_to(&time2));
1730
1731    let inc1 = MatchingRule::Include("string one".into());
1732    let inc2 = MatchingRule::Include("string two".into());
1733
1734    expect!(h(&inc1)).to(be_equal_to(h(&inc1)));
1735    expect!(&inc1).to(be_equal_to(&inc1));
1736    expect!(h(&inc1)).to_not(be_equal_to(h(&inc2)));
1737    expect!(&inc1).to_not(be_equal_to(&inc2));
1738
1739    let content1 = MatchingRule::ContentType("one".into());
1740    let content2 = MatchingRule::ContentType("two".into());
1741
1742    expect!(h(&content1)).to(be_equal_to(h(&content1)));
1743    expect!(&content1).to(be_equal_to(&content1));
1744    expect!(h(&content1)).to_not(be_equal_to(h(&content2)));
1745    expect!(&content1).to_not(be_equal_to(&content2));
1746
1747    let ac1 = MatchingRule::ArrayContains(vec![]);
1748    let ac2 = MatchingRule::ArrayContains(vec![(0, MatchingRuleCategory::empty("body"), hashmap!{})]);
1749    let ac3 = MatchingRule::ArrayContains(vec![(1, MatchingRuleCategory::empty("body"), hashmap!{})]);
1750    let ac4 = MatchingRule::ArrayContains(vec![(0, MatchingRuleCategory::equality("body"), hashmap!{})]);
1751    let ac5 = MatchingRule::ArrayContains(vec![(0, MatchingRuleCategory::empty("body"), hashmap!{ DocPath::new_unwrap("A") => Generator::RandomBoolean })]);
1752    let ac6 = MatchingRule::ArrayContains(vec![
1753      (0, MatchingRuleCategory::empty("body"), hashmap!{ DocPath::new_unwrap("A") => Generator::RandomBoolean }),
1754      (1, MatchingRuleCategory::empty("body"), hashmap!{ DocPath::new_unwrap("A") => Generator::RandomDecimal(10) })
1755    ]);
1756    let ac7 = MatchingRule::ArrayContains(vec![
1757      (0, MatchingRuleCategory::empty("body"), hashmap!{ DocPath::new_unwrap("A") => Generator::RandomBoolean }),
1758      (1, MatchingRuleCategory::equality("body"), hashmap!{ DocPath::new_unwrap("A") => Generator::RandomDecimal(10) })
1759    ]);
1760
1761    expect!(h(&ac1)).to(be_equal_to(h(&ac1)));
1762    expect!(h(&ac1)).to_not(be_equal_to(h(&ac2)));
1763    expect!(h(&ac1)).to_not(be_equal_to(h(&ac3)));
1764    expect!(h(&ac1)).to_not(be_equal_to(h(&ac4)));
1765    expect!(h(&ac1)).to_not(be_equal_to(h(&ac5)));
1766    expect!(h(&ac1)).to_not(be_equal_to(h(&ac6)));
1767    expect!(h(&ac1)).to_not(be_equal_to(h(&ac7)));
1768    expect!(h(&ac2)).to(be_equal_to(h(&ac2)));
1769    expect!(h(&ac2)).to_not(be_equal_to(h(&ac1)));
1770    expect!(h(&ac2)).to_not(be_equal_to(h(&ac3)));
1771    expect!(h(&ac2)).to_not(be_equal_to(h(&ac4)));
1772    expect!(h(&ac2)).to_not(be_equal_to(h(&ac5)));
1773    expect!(h(&ac2)).to_not(be_equal_to(h(&ac6)));
1774    expect!(h(&ac2)).to_not(be_equal_to(h(&ac7)));
1775    expect!(h(&ac3)).to(be_equal_to(h(&ac3)));
1776    expect!(h(&ac3)).to_not(be_equal_to(h(&ac2)));
1777    expect!(h(&ac3)).to_not(be_equal_to(h(&ac1)));
1778    expect!(h(&ac3)).to_not(be_equal_to(h(&ac4)));
1779    expect!(h(&ac3)).to_not(be_equal_to(h(&ac5)));
1780    expect!(h(&ac3)).to_not(be_equal_to(h(&ac6)));
1781    expect!(h(&ac3)).to_not(be_equal_to(h(&ac7)));
1782    expect!(h(&ac4)).to(be_equal_to(h(&ac4)));
1783    expect!(h(&ac4)).to_not(be_equal_to(h(&ac2)));
1784    expect!(h(&ac4)).to_not(be_equal_to(h(&ac3)));
1785    expect!(h(&ac4)).to_not(be_equal_to(h(&ac1)));
1786    expect!(h(&ac4)).to_not(be_equal_to(h(&ac5)));
1787    expect!(h(&ac4)).to_not(be_equal_to(h(&ac6)));
1788    expect!(h(&ac4)).to_not(be_equal_to(h(&ac7)));
1789    expect!(h(&ac5)).to(be_equal_to(h(&ac5)));
1790    expect!(h(&ac5)).to_not(be_equal_to(h(&ac2)));
1791    expect!(h(&ac5)).to_not(be_equal_to(h(&ac3)));
1792    expect!(h(&ac5)).to_not(be_equal_to(h(&ac4)));
1793    expect!(h(&ac5)).to_not(be_equal_to(h(&ac1)));
1794    expect!(h(&ac5)).to_not(be_equal_to(h(&ac6)));
1795    expect!(h(&ac5)).to_not(be_equal_to(h(&ac7)));
1796    expect!(h(&ac6)).to(be_equal_to(h(&ac6)));
1797    expect!(h(&ac6)).to_not(be_equal_to(h(&ac2)));
1798    expect!(h(&ac6)).to_not(be_equal_to(h(&ac3)));
1799    expect!(h(&ac6)).to_not(be_equal_to(h(&ac4)));
1800    expect!(h(&ac6)).to_not(be_equal_to(h(&ac5)));
1801    expect!(h(&ac6)).to_not(be_equal_to(h(&ac1)));
1802    expect!(h(&ac6)).to_not(be_equal_to(h(&ac7)));
1803    expect!(h(&ac7)).to(be_equal_to(h(&ac7)));
1804    expect!(h(&ac7)).to_not(be_equal_to(h(&ac2)));
1805    expect!(h(&ac7)).to_not(be_equal_to(h(&ac3)));
1806    expect!(h(&ac7)).to_not(be_equal_to(h(&ac4)));
1807    expect!(h(&ac7)).to_not(be_equal_to(h(&ac5)));
1808    expect!(h(&ac7)).to_not(be_equal_to(h(&ac6)));
1809    expect!(h(&ac7)).to_not(be_equal_to(h(&ac1)));
1810
1811    expect!(&ac1).to(be_equal_to(&ac1));
1812    expect!(&ac1).to_not(be_equal_to(&ac2));
1813    expect!(&ac1).to_not(be_equal_to(&ac3));
1814    expect!(&ac1).to_not(be_equal_to(&ac4));
1815    expect!(&ac1).to_not(be_equal_to(&ac5));
1816    expect!(&ac1).to_not(be_equal_to(&ac6));
1817    expect!(&ac1).to_not(be_equal_to(&ac7));
1818    expect!(&ac2).to(be_equal_to(&ac2));
1819    expect!(&ac2).to_not(be_equal_to(&ac1));
1820    expect!(&ac2).to_not(be_equal_to(&ac3));
1821    expect!(&ac2).to_not(be_equal_to(&ac4));
1822    expect!(&ac2).to_not(be_equal_to(&ac5));
1823    expect!(&ac2).to_not(be_equal_to(&ac6));
1824    expect!(&ac2).to_not(be_equal_to(&ac7));
1825    expect!(&ac3).to(be_equal_to(&ac3));
1826    expect!(&ac3).to_not(be_equal_to(&ac2));
1827    expect!(&ac3).to_not(be_equal_to(&ac1));
1828    expect!(&ac3).to_not(be_equal_to(&ac4));
1829    expect!(&ac3).to_not(be_equal_to(&ac5));
1830    expect!(&ac3).to_not(be_equal_to(&ac6));
1831    expect!(&ac3).to_not(be_equal_to(&ac7));
1832    expect!(&ac4).to(be_equal_to(&ac4));
1833    expect!(&ac4).to_not(be_equal_to(&ac2));
1834    expect!(&ac4).to_not(be_equal_to(&ac3));
1835    expect!(&ac4).to_not(be_equal_to(&ac1));
1836    expect!(&ac4).to_not(be_equal_to(&ac5));
1837    expect!(&ac4).to_not(be_equal_to(&ac6));
1838    expect!(&ac4).to_not(be_equal_to(&ac7));
1839    expect!(&ac5).to(be_equal_to(&ac5));
1840    expect!(&ac5).to_not(be_equal_to(&ac2));
1841    expect!(&ac5).to_not(be_equal_to(&ac3));
1842    expect!(&ac5).to_not(be_equal_to(&ac4));
1843    expect!(&ac5).to_not(be_equal_to(&ac1));
1844    expect!(&ac5).to_not(be_equal_to(&ac6));
1845    expect!(&ac5).to_not(be_equal_to(&ac7));
1846    expect!(&ac6).to(be_equal_to(&ac6));
1847    expect!(&ac6).to_not(be_equal_to(&ac2));
1848    expect!(&ac6).to_not(be_equal_to(&ac3));
1849    expect!(&ac6).to_not(be_equal_to(&ac4));
1850    expect!(&ac6).to_not(be_equal_to(&ac5));
1851    expect!(&ac6).to_not(be_equal_to(&ac1));
1852    expect!(&ac6).to_not(be_equal_to(&ac7));
1853    expect!(&ac7).to(be_equal_to(&ac7));
1854    expect!(&ac7).to_not(be_equal_to(&ac2));
1855    expect!(&ac7).to_not(be_equal_to(&ac3));
1856    expect!(&ac7).to_not(be_equal_to(&ac4));
1857    expect!(&ac7).to_not(be_equal_to(&ac5));
1858    expect!(&ac7).to_not(be_equal_to(&ac6));
1859    expect!(&ac7).to_not(be_equal_to(&ac1));
1860  }
1861
1862  #[test]
1863  fn rules_are_empty_when_there_are_no_categories() {
1864    expect!(MatchingRules::default().is_empty()).to(be_true());
1865  }
1866
1867  #[test]
1868  fn rules_are_empty_when_there_are_only_empty_categories() {
1869    expect!(MatchingRules {
1870      rules: hashmap!{
1871        "body".into() => MatchingRuleCategory::empty("body"),
1872        "header".into() => MatchingRuleCategory::empty("header"),
1873        "query".into() => MatchingRuleCategory::empty("query")
1874      }
1875    }.is_empty()).to(be_true());
1876  }
1877
1878  #[test]
1879  fn rules_are_not_empty_when_there_is_a_nonempty_category() {
1880    expect!(MatchingRules {
1881      rules: hashmap!{
1882        "body".into() => MatchingRuleCategory::empty("body"),
1883        "header".into() => MatchingRuleCategory::empty("headers"),
1884        "query".into() => MatchingRuleCategory {
1885            name: "query".into(),
1886            rules: hashmap!{
1887              DocPath::empty() => RuleList {
1888                rules: vec![ MatchingRule::Equality ],
1889                rule_logic: RuleLogic::And,
1890                cascaded: false
1891              }
1892            }
1893        },
1894      }
1895    }.is_empty()).to(be_false());
1896  }
1897
1898  #[test]
1899  fn matchers_from_json_test() {
1900    let matching_rules = matchers_from_json(&Value::Null, &None);
1901    let matching_rules = matching_rules.unwrap();
1902    expect!(matching_rules.rules.iter()).to(be_empty());
1903  }
1904
1905  #[test]
1906  fn loads_v2_matching_rules() {
1907    let matching_rules_json = Value::from_str(r#"{"matchingRules": {
1908      "$.path": { "match": "regex", "regex": "\\w+" },
1909      "$.query.Q1": { "match": "regex", "regex": "\\d+" },
1910      "$.header.HEADERY": {"match": "include", "value": "ValueA"},
1911      "$.body.animals": {"min": 1, "match": "type"},
1912      "$.body.animals[*].*": {"match": "type"},
1913      "$.body.animals[*].children": {"min": 1},
1914      "$.body.animals[*].children[*].*": {"match": "type"}
1915    }}"#).unwrap();
1916
1917    let matching_rules = matchers_from_json(&matching_rules_json, &None);
1918    let matching_rules = matching_rules.unwrap();
1919
1920    expect!(matching_rules.rules.iter()).to_not(be_empty());
1921    expect!(matching_rules.categories()).to(be_equal_to(hashset!{
1922      Category::PATH, Category::QUERY, Category::HEADER, Category::BODY
1923    }));
1924    expect!(matching_rules.rules_for_category("path")).to(be_some().value(MatchingRuleCategory {
1925      name: "path".into(),
1926      rules: hashmap! { DocPath::empty() => RuleList { rules: vec![ MatchingRule::Regex("\\w+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
1927    }));
1928    expect!(matching_rules.rules_for_category("query")).to(be_some().value(MatchingRuleCategory {
1929      name: "query".into(),
1930      rules: hashmap!{ DocPath::new_unwrap("Q1") => RuleList { rules: vec![ MatchingRule::Regex("\\d+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
1931    }));
1932    expect!(matching_rules.rules_for_category("header")).to(be_some().value(MatchingRuleCategory {
1933      name: "header".into(),
1934      rules: hashmap!{ DocPath::new_unwrap("HEADERY") => RuleList { rules: vec![
1935        MatchingRule::Include("ValueA".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
1936    }));
1937    expect!(matching_rules.rules_for_category("body")).to(be_some().value(MatchingRuleCategory {
1938      name: "body".into(),
1939      rules: hashmap!{
1940        DocPath::new_unwrap("$.animals") => RuleList { rules: vec![ MatchingRule::MinType(1) ], rule_logic: RuleLogic::And, cascaded: false },
1941        DocPath::new_unwrap("$.animals[*].*") => RuleList { rules: vec![ MatchingRule::Type ], rule_logic: RuleLogic::And, cascaded: false },
1942        DocPath::new_unwrap("$.animals[*].children") => RuleList { rules: vec![ MatchingRule::MinType(1) ], rule_logic: RuleLogic::And, cascaded: false },
1943        DocPath::new_unwrap("$.animals[*].children[*].*") => RuleList { rules: vec![ MatchingRule::Type ], rule_logic: RuleLogic::And, cascaded: false }
1944      }
1945    }));
1946  }
1947
1948  #[test]
1949  fn loads_v2_header_matching_rules_with_correct_pluralisation() {
1950    let matching_rules_json = Value::from_str(r#"{"matchingRules": {
1951      "$.headers.HEADERY": {"match": "include", "value": "ValueA"}
1952    }}"#).unwrap();
1953
1954    let matching_rules = matchers_from_json(&matching_rules_json, &None).unwrap();
1955
1956    expect!(matching_rules.rules.iter()).to_not(be_empty());
1957    expect!(matching_rules.categories()).to(be_equal_to(hashset!{ Category::HEADER }));
1958    expect!(matching_rules.rules_for_category("header")).to(be_some().value(MatchingRuleCategory {
1959      name: "header".into(),
1960      rules: hashmap!{ DocPath::new_unwrap("HEADERY") => RuleList { rules: vec![
1961        MatchingRule::Include("ValueA".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
1962    }));
1963  }
1964
1965  #[test]
1966  fn load_from_v2_map_supports_headers_and_query_parameters_in_encoded_format() {
1967    let matching_rules_json = json!({
1968      "$.query.Q1": { "match": "regex", "regex": "1" },
1969      "$.query.x-test": { "match": "regex", "regex": "2" },
1970      "$.query['x-test-2']": { "match": "regex", "regex": "3" },
1971      "$.headers.HEADERY": { "match": "regex", "regex": "4" },
1972      "$.headers.x-test": { "match": "regex", "regex": "5" },
1973      "$.headers['x-test-2']": { "match": "regex", "regex": "6" }
1974    });
1975    let matching_rules_map = matching_rules_json.as_object().unwrap();
1976
1977    let mut matching_rules = MatchingRules::default();
1978    matching_rules.load_from_v2_map(&matching_rules_map).unwrap();
1979
1980    expect!(matching_rules.rules.iter()).to_not(be_empty());
1981    expect!(matching_rules.categories()).to(be_equal_to(hashset!{
1982      Category::QUERY, Category::HEADER
1983    }));
1984    assert_eq!(matching_rules.rules_for_category("header").unwrap(), MatchingRuleCategory {
1985      name: "header".into(),
1986      rules: hashmap!{
1987        DocPath::new_unwrap("HEADERY") => RuleList { rules: vec![ MatchingRule::Regex("4".to_string()) ], rule_logic: RuleLogic::And, cascaded: false },
1988        DocPath::new_unwrap("x-test") => RuleList { rules: vec![ MatchingRule::Regex("5".to_string()) ], rule_logic: RuleLogic::And, cascaded: false },
1989        DocPath::new_unwrap("x-test-2") => RuleList { rules: vec![ MatchingRule::Regex("6".to_string()) ], rule_logic: RuleLogic::And, cascaded: false }
1990      }
1991    });
1992    assert_eq!(matching_rules.rules_for_category("query").unwrap(), MatchingRuleCategory {
1993      name: "query".into(),
1994      rules: hashmap!{
1995        DocPath::new_unwrap("Q1") => RuleList { rules: vec![ MatchingRule::Regex("1".to_string()) ], rule_logic: RuleLogic::And, cascaded: false },
1996        DocPath::new_unwrap("x-test") => RuleList { rules: vec![ MatchingRule::Regex("2".to_string()) ], rule_logic: RuleLogic::And, cascaded: false },
1997        DocPath::new_unwrap("x-test-2") => RuleList { rules: vec![ MatchingRule::Regex("3".to_string()) ], rule_logic: RuleLogic::And, cascaded: false }
1998      }
1999    });
2000  }
2001
2002  #[test]
2003  fn loads_v3_matching_rules() {
2004    let matching_rules_json = Value::from_str(r#"{"matchingRules": {
2005      "path": {
2006        "matchers": [
2007          { "match": "regex", "regex": "\\w+" }
2008        ]
2009      },
2010      "query": {
2011        "Q1": {
2012          "matchers": [
2013            { "match": "regex", "regex": "\\d+" }
2014          ]
2015        }
2016      },
2017      "header": {
2018        "HEADERY": {
2019          "combine": "OR",
2020          "matchers": [
2021            {"match": "include", "value": "ValueA"},
2022            {"match": "include", "value": "ValueB"}
2023          ]
2024        }
2025      },
2026      "body": {
2027        "$.animals": {
2028          "matchers": [{"min": 1, "match": "type"}]
2029        },
2030        "$.animals[*].*": {
2031          "matchers": [{"match": "type"}]
2032        },
2033        "$.animals[*].children": {
2034          "matchers": [{"min": 1}]
2035        },
2036        "$.animals[*].children[*].*": {
2037          "matchers": [{"match": "type"}]
2038        }
2039      }
2040    }}"#).unwrap();
2041
2042    let matching_rules = matchers_from_json(&matching_rules_json, &None);
2043    let matching_rules = matching_rules.unwrap();
2044
2045    expect!(matching_rules.rules.iter()).to_not(be_empty());
2046    expect!(matching_rules.categories()).to(be_equal_to(hashset!{
2047      Category::PATH, Category::QUERY, Category::HEADER, Category::BODY
2048    }));
2049    expect!(matching_rules.rules_for_category("path")).to(be_some().value(MatchingRuleCategory {
2050      name: "path".into(),
2051      rules: hashmap! { DocPath::empty() => RuleList { rules: vec![ MatchingRule::Regex("\\w+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
2052    }));
2053    expect!(matching_rules.rules_for_category("query")).to(be_some().value(MatchingRuleCategory {
2054      name: "query".into(),
2055      rules: hashmap!{ DocPath::root().join("Q1") => RuleList { rules: vec![ MatchingRule::Regex("\\d+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
2056    }));
2057    expect!(matching_rules.rules_for_category("header")).to(be_some().value(MatchingRuleCategory {
2058      name: "header".into(),
2059      rules: hashmap!{ DocPath::root().join("HEADERY") => RuleList { rules: vec![
2060        MatchingRule::Include("ValueA".to_string()),
2061        MatchingRule::Include("ValueB".to_string()) ], rule_logic: RuleLogic::Or, cascaded: false } }
2062    }));
2063    expect!(matching_rules.rules_for_category("body")).to(be_some().value(MatchingRuleCategory {
2064      name: "body".into(),
2065      rules: hashmap!{
2066        DocPath::new_unwrap("$.animals") => RuleList { rules: vec![ MatchingRule::MinType(1) ], rule_logic: RuleLogic::And, cascaded: false },
2067        DocPath::new_unwrap("$.animals[*].*") => RuleList { rules: vec![ MatchingRule::Type ], rule_logic: RuleLogic::And, cascaded: false },
2068        DocPath::new_unwrap("$.animals[*].children") => RuleList { rules: vec![ MatchingRule::MinType(1) ], rule_logic: RuleLogic::And, cascaded: false },
2069        DocPath::new_unwrap("$.animals[*].children[*].*") => RuleList { rules: vec![ MatchingRule::Type ], rule_logic: RuleLogic::And, cascaded: false }
2070      }
2071    }));
2072  }
2073
2074  #[test]
2075  fn correctly_loads_v3_matching_rules_with_incorrect_path_format() {
2076    let matching_rules_json = Value::from_str(r#"{"matchingRules": {
2077      "path": {
2078        "": {
2079          "matchers": [
2080            { "match": "regex", "regex": "\\w+" }
2081          ]
2082        }
2083      }
2084    }}"#).unwrap();
2085
2086    let matching_rules = matchers_from_json(&matching_rules_json, &None);
2087    let matching_rules = matching_rules.unwrap();
2088
2089    expect!(matching_rules.rules.iter()).to_not(be_empty());
2090    expect!(matching_rules.categories()).to(be_equal_to(hashset!{ Category::PATH }));
2091    expect!(matching_rules.rules_for_category("path")).to(be_some().value(MatchingRuleCategory {
2092      name: "path".into(),
2093      rules: hashmap! { DocPath::empty() => RuleList { rules: vec![ MatchingRule::Regex("\\w+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
2094    }));
2095  }
2096
2097  speculate! {
2098    describe "generating matcher JSON" {
2099      before {
2100        let matchers = matchingrules!{
2101          "body" => {
2102            "$.a.b" => [ MatchingRule::Type ]
2103          },
2104          "path" => { "" => [ MatchingRule::Regex("/path/\\d+".to_string()) ] },
2105          "query" => {
2106            "a" => [ MatchingRule::Regex("\\w+".to_string()) ],
2107            "$['principal_identifier[account_id]']" => [ MatchingRule::Regex("\\w+".to_string()) ]
2108          },
2109          "header" => {
2110            "item1" => [ MatchingRule::Regex("5".to_string()) ],
2111            "$['principal_identifier[account_id]']" => [ MatchingRule::Regex("\\w+".to_string()) ]
2112          },
2113          "metadata" => {}
2114        };
2115      }
2116
2117      it "generates V2 matcher format" {
2118        pretty_assertions::assert_eq!(matchers.to_v2_json().to_string(),
2119          "{\"$.body.a.b\":{\"match\":\"type\"},\
2120          \"$.headers.item1\":{\"match\":\"regex\",\"regex\":\"5\"},\
2121          \"$.headers['principal_identifier[account_id]']\":{\"match\":\"regex\",\"regex\":\"\\\\w+\"},\
2122          \"$.path\":{\"match\":\"regex\",\"regex\":\"/path/\\\\d+\"},\
2123          \"$.query.a\":{\"match\":\"regex\",\"regex\":\"\\\\w+\"},\
2124          \"$.query['principal_identifier[account_id]']\":{\"match\":\"regex\",\"regex\":\"\\\\w+\"}\
2125          }"
2126        );
2127      }
2128
2129      it "generates V3 matcher format" {
2130        pretty_assertions::assert_eq!(matchers.to_v3_json().to_string(),
2131          "{\"body\":{\"$.a.b\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"type\"}]}},\
2132          \"header\":{\"item1\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"regex\",\"regex\":\"5\"}]},\
2133          \"principal_identifier[account_id]\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"regex\",\"regex\":\"\\\\w+\"}]}\
2134          },\
2135          \"path\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"regex\",\"regex\":\"/path/\\\\d+\"}]},\
2136          \"query\":{\"a\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"regex\",\"regex\":\"\\\\w+\"}]},\
2137          \"principal_identifier[account_id]\":{\"combine\":\"AND\",\"matchers\":[{\"match\":\"regex\",\"regex\":\"\\\\w+\"}]}\
2138          }}"
2139        );
2140      }
2141    }
2142  }
2143
2144  #[test]
2145  fn loads_v3_matching_rules_supports_headers_and_query_parameters_with_brackets() {
2146    let matching_rules_json = Value::from_str(r#"{"matchingRules": {
2147      "query": {
2148        "Q[]": {
2149          "matchers": [
2150            { "match": "regex", "regex": "\\d+" }
2151          ]
2152        }
2153      },
2154      "header": {
2155        "Y[]": {
2156          "combine": "OR",
2157          "matchers": [
2158            {"match": "include", "value": "ValueA"},
2159            {"match": "include", "value": "ValueB"}
2160          ]
2161        }
2162      }
2163    }}"#).unwrap();
2164
2165    let matching_rules = matchers_from_json(&matching_rules_json, &None);
2166    let matching_rules = matching_rules.unwrap();
2167
2168    expect!(matching_rules.rules.iter()).to_not(be_empty());
2169    expect!(matching_rules.categories()).to(be_equal_to(hashset!{
2170      Category::QUERY, Category::HEADER
2171    }));
2172    expect!(matching_rules.rules_for_category("query")).to(be_some().value(MatchingRuleCategory {
2173      name: "query".into(),
2174      rules: hashmap!{ DocPath::root().join("Q[]") => RuleList { rules: vec![ MatchingRule::Regex("\\d+".to_string()) ], rule_logic: RuleLogic::And, cascaded: false } }
2175    }));
2176    expect!(matching_rules.rules_for_category("header")).to(be_some().value(MatchingRuleCategory {
2177      name: "header".into(),
2178      rules: hashmap!{ DocPath::root().join("Y[]") => RuleList { rules: vec![
2179        MatchingRule::Include("ValueA".to_string()),
2180        MatchingRule::Include("ValueB".to_string()) ], rule_logic: RuleLogic::Or, cascaded: false } }
2181    }));
2182  }
2183
2184  #[test]
2185  fn matching_rule_from_json_test() {
2186    expect!(MatchingRule::from_json(&Value::from_str("\"test string\"").unwrap())).to(be_err());
2187    expect!(MatchingRule::from_json(&Value::from_str("null").unwrap())).to(be_err());
2188    expect!(MatchingRule::from_json(&Value::from_str("{}").unwrap())).to(be_err());
2189    expect!(MatchingRule::from_json(&Value::from_str("[]").unwrap())).to(be_err());
2190    expect!(MatchingRule::from_json(&Value::from_str("true").unwrap())).to(be_err());
2191    expect!(MatchingRule::from_json(&Value::from_str("false").unwrap())).to(be_err());
2192    expect!(MatchingRule::from_json(&Value::from_str("100").unwrap())).to(be_err());
2193    expect!(MatchingRule::from_json(&Value::from_str("100.10").unwrap())).to(be_err());
2194    expect!(MatchingRule::from_json(&Value::from_str("{\"stuff\": 100}").unwrap())).to(be_err());
2195    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"stuff\"}").unwrap())).to(be_err());
2196
2197    expect!(MatchingRule::from_json(&Value::from_str("{\"regex\": \"[0-9]\"}").unwrap())).to(
2198      be_ok().value(MatchingRule::Regex("[0-9]".to_string())));
2199    expect!(MatchingRule::from_json(&Value::from_str("{\"min\": 100}").unwrap())).to(
2200      be_ok().value(MatchingRule::MinType(100)));
2201    expect!(MatchingRule::from_json(&Value::from_str("{\"max\": 100}").unwrap())).to(
2202      be_ok().value(MatchingRule::MaxType(100)));
2203    expect!(MatchingRule::from_json(&Value::from_str("{\"timestamp\": \"yyyy\"}").unwrap())).to(
2204      be_ok().value(MatchingRule::Timestamp("yyyy".to_string())));
2205    expect!(MatchingRule::from_json(&Value::from_str("{\"date\": \"yyyy\"}").unwrap())).to(
2206      be_ok().value(MatchingRule::Date("yyyy".to_string())));
2207    expect!(MatchingRule::from_json(&Value::from_str("{\"time\": \"hh:mm\"}").unwrap())).to(
2208      be_ok().value(MatchingRule::Time("hh:mm".to_string())));
2209
2210    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"regex\", \"regex\": \"[0-9]\"}").unwrap())).to(
2211      be_ok().value(MatchingRule::Regex("[0-9]".to_string())));
2212    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"regex\"}").unwrap())).to(be_err());
2213
2214    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"equality\"}").unwrap())).to(
2215      be_ok().value(MatchingRule::Equality));
2216
2217    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"include\", \"value\": \"A\"}").unwrap())).to(
2218      be_ok().value(MatchingRule::Include("A".to_string())));
2219    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"include\"}").unwrap())).to(be_err());
2220
2221    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"type\", \"min\": 1}").unwrap())).to(
2222      be_ok().value(MatchingRule::MinType(1)));
2223    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"type\", \"max\": \"1\"}").unwrap())).to(
2224      be_ok().value(MatchingRule::MaxType(1)));
2225    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"type\", \"min\": 1, \"max\": \"1\"}").unwrap())).to(
2226      be_ok().value(MatchingRule::MinMaxType(1, 1)));
2227    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"type\"}").unwrap())).to(
2228      be_ok().value(MatchingRule::Type));
2229    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"type\", \"value\": 100}").unwrap())).to(
2230      be_ok().value(MatchingRule::Type));
2231    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"min\", \"min\": 1}").unwrap())).to(
2232      be_ok().value(MatchingRule::MinType(1)));
2233    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"max\", \"max\": \"1\"}").unwrap())).to(
2234      be_ok().value(MatchingRule::MaxType(1)));
2235    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"min-type\", \"min\": 1}").unwrap())).to(
2236      be_ok().value(MatchingRule::MinType(1)));
2237    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"max-type\", \"max\": \"1\"}").unwrap())).to(
2238      be_ok().value(MatchingRule::MaxType(1)));
2239    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"min\"}").unwrap())).to(be_err());
2240    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"max\"}").unwrap())).to(be_err());
2241
2242    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"number\"}").unwrap())).to(
2243      be_ok().value(MatchingRule::Number));
2244    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"integer\"}").unwrap())).to(
2245      be_ok().value(MatchingRule::Integer));
2246    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"decimal\"}").unwrap())).to(
2247      be_ok().value(MatchingRule::Decimal));
2248    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"real\"}").unwrap())).to(
2249      be_ok().value(MatchingRule::Decimal));
2250    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"boolean\"}").unwrap())).to(
2251      be_ok().value(MatchingRule::Boolean));
2252
2253    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"timestamp\", \"timestamp\": \"A\"}").unwrap())).to(
2254      be_ok().value(MatchingRule::Timestamp("A".to_string())));
2255    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"timestamp\"}").unwrap())).to(be_ok());
2256    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"time\", \"time\": \"A\"}").unwrap())).to(
2257      be_ok().value(MatchingRule::Time("A".to_string())));
2258    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"time\"}").unwrap())).to(be_ok());
2259    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"date\", \"date\": \"A\"}").unwrap())).to(
2260      be_ok().value(MatchingRule::Date("A".to_string())));
2261    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"date\"}").unwrap())).to(be_ok());
2262
2263    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"null\"}").unwrap())).to(
2264      be_ok().value(MatchingRule::Null));
2265
2266    let json = json!({
2267      "match": "arrayContains",
2268      "variants": []
2269    });
2270    expect!(MatchingRule::from_json(&json)).to(be_ok().value(MatchingRule::ArrayContains(vec![])));
2271
2272    let json = json!({
2273      "match": "arrayContains",
2274      "variants": [
2275        {
2276          "index": 0,
2277          "rules": {
2278            "matchers": [ { "match": "equality" } ]
2279          }
2280        }
2281      ]
2282    });
2283    expect!(MatchingRule::from_json(&json)).to(be_ok().value(
2284      MatchingRule::ArrayContains(
2285        vec![
2286          (0, matchingrules_list! { "body"; [ MatchingRule::Equality ] }, HashMap::default())
2287        ])
2288    ));
2289
2290    let json = json!({
2291      "match": "arrayContains",
2292      "variants": [
2293        {
2294          "index": 0,
2295          "rules": {
2296            "matchers": [ { "match": "equality" } ]
2297          },
2298          "generators": {
2299            "a": { "type": "Uuid" }
2300          }
2301        }
2302      ]
2303    });
2304    let generators = hashmap!{ DocPath::new_unwrap("a") => Generator::Uuid(None) };
2305    expect!(MatchingRule::from_json(&json)).to(be_ok().value(
2306      MatchingRule::ArrayContains(
2307        vec![
2308          (0, matchingrules_list! { "body"; [ MatchingRule::Equality ] }, generators)
2309        ])
2310    ));
2311
2312    let json = json!({
2313      "match": "statusCode",
2314      "status": "success"
2315    });
2316    expect!(MatchingRule::from_json(&json)).to(be_ok().value(
2317      MatchingRule::StatusCode(HttpStatus::Success)
2318    ));
2319
2320    let json = json!({
2321      "match": "statusCode",
2322      "status": [200, 201, 204]
2323    });
2324    expect!(MatchingRule::from_json(&json)).to(be_ok().value(
2325      MatchingRule::StatusCode(HttpStatus::StatusCodes(vec![200, 201, 204]))
2326    ));
2327  }
2328
2329  #[test]
2330  fn matching_rule_from_json_supports_integration_form() {
2331    let json = json!({
2332      "pact:matcher:type": "each-value",
2333      "value": {
2334        "price": 1.23
2335      },
2336      "rules": [
2337        {
2338          "pact:matcher:type": "decimal"
2339        }
2340      ]
2341    });
2342    expect!(MatchingRule::from_json(&json)).to(be_ok().value(
2343      MatchingRule::EachValue(MatchingRuleDefinition::new("{\"price\":1.23}".to_string(),
2344        ValueType::Unknown, MatchingRule::Decimal, None, "".to_string())))
2345    );
2346  }
2347
2348  #[test]
2349  fn date_time_matchers_can_parse_the_updated_spec_format() {
2350    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"timestamp\", \"format\": \"A\"}").unwrap())).to(
2351      be_ok().value(MatchingRule::Timestamp("A".to_string())));
2352    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"datetime\", \"format\": \"A\"}").unwrap())).to(
2353      be_ok().value(MatchingRule::Timestamp("A".to_string())));
2354    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"time\", \"format\": \"A\"}").unwrap())).to(
2355      be_ok().value(MatchingRule::Time("A".to_string())));
2356    expect!(MatchingRule::from_json(&Value::from_str("{\"match\": \"date\", \"format\": \"A\"}").unwrap())).to(
2357      be_ok().value(MatchingRule::Date("A".to_string())));
2358  }
2359
2360  #[test]
2361  fn matching_rule_to_json_test() {
2362    expect!(MatchingRule::StatusCode(HttpStatus::ClientError).to_json()).to(
2363      be_equal_to(json!({
2364        "match": "statusCode",
2365        "status": "clientError"
2366      })));
2367    expect!(MatchingRule::StatusCode(HttpStatus::StatusCodes(vec![400, 401, 404])).to_json()).to(
2368      be_equal_to(json!({
2369        "match": "statusCode",
2370        "status": [400, 401, 404]
2371      })));
2372
2373    expect!(MatchingRule::Timestamp("YYYY".to_string()).to_json()).to(
2374      be_equal_to(json!({
2375        "match": "datetime",
2376        "format": "YYYY"
2377      })));
2378    expect!(MatchingRule::Date("YYYY".to_string()).to_json()).to(
2379      be_equal_to(json!({
2380        "match": "date",
2381        "format": "YYYY"
2382      })));
2383    expect!(MatchingRule::Time("HH".to_string()).to_json()).to(
2384      be_equal_to(json!({
2385        "match": "time",
2386        "format": "HH"
2387      })));
2388  }
2389
2390  #[test]
2391  fn matcher_is_defined_returns_false_when_there_are_no_matchers() {
2392    let matchers = matchingrules!{};
2393    expect!(matchers.matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_false());
2394  }
2395
2396  #[test]
2397  fn matcher_is_defined_returns_false_when_the_path_does_not_have_a_matcher_entry() {
2398    let matchers = matchingrules!{
2399      "body" => { }
2400    };
2401    expect!(matchers.matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_false());
2402  }
2403
2404  #[test]
2405  fn matcher_is_defined_returns_true_when_the_path_does_have_a_matcher_entry() {
2406    let matchers = matchingrules! {
2407      "body" => {
2408        "$.a.b" => [ MatchingRule::Type ]
2409      }
2410    };
2411    expect!(matchers.matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_true());
2412  }
2413
2414  #[test]
2415  fn matcher_is_defined_returns_false_when_the_path_is_empty() {
2416    let matchers = matchingrules! {
2417      "body" => {
2418        "$.a.b" => [ MatchingRule::Type ]
2419      }
2420    };
2421    expect!(matchers.matcher_is_defined("body", &vec![])).to(be_false());
2422  }
2423
2424  #[test]
2425  fn matcher_is_defined_returns_true_when_the_parent_of_the_path_does_have_a_matcher_entry() {
2426    let matchers = matchingrules!{
2427            "body" => {
2428                "$.a.b" => [ MatchingRule::Type ]
2429            }
2430        };
2431    expect!(matchers.matcher_is_defined("body", &vec!["$", "a", "b", "c"])).to(be_true());
2432  }
2433
2434  #[test]
2435  fn wildcard_matcher_is_defined_returns_false_when_there_are_no_matchers() {
2436    let matchers = matchingrules!{};
2437    expect!(matchers.wildcard_matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_false());
2438  }
2439
2440  #[test]
2441  fn wildcard_matcher_is_defined_returns_false_when_the_path_does_not_have_a_matcher_entry() {
2442    let matchers = matchingrules!{
2443      "body" => { }
2444    };
2445    expect!(matchers.wildcard_matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_false());
2446  }
2447
2448  #[test]
2449  fn wildcard_matcher_is_defined_returns_false_when_the_path_does_have_a_matcher_entry_and_it_is_not_a_wildcard() {
2450    let matchers = matchingrules!{
2451            "body" => {
2452                "$.a.b" => [ MatchingRule::Type ],
2453                "$.*" => [ MatchingRule::Type ]
2454            }
2455        };
2456    expect!(matchers.wildcard_matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_false());
2457  }
2458
2459  #[test]
2460  fn wildcard_matcher_is_defined_returns_true_when_the_path_does_have_a_matcher_entry_and_it_is_a_widcard() {
2461    let matchers = matchingrules!{
2462            "body" => {
2463                "$.a.*" => [ MatchingRule::Type ]
2464            }
2465        };
2466    expect!(matchers.wildcard_matcher_is_defined("body", &vec!["$", "a", "b"])).to(be_true());
2467  }
2468
2469  #[test]
2470  fn wildcard_matcher_is_defined_returns_false_when_the_parent_of_the_path_does_have_a_matcher_entry() {
2471    let matchers = matchingrules!{
2472            "body" => {
2473                "$.a.*" => [ MatchingRule::Type ]
2474            }
2475        };
2476    expect!(matchers.wildcard_matcher_is_defined("body", &vec!["$", "a", "b", "c"])).to(be_false());
2477  }
2478
2479  #[test]
2480  fn min_and_max_values_get_serialised_to_json_as_numbers() {
2481    expect!(MatchingRule::MinType(1).to_json().to_string()).to(be_equal_to("{\"match\":\"type\",\"min\":1}"));
2482    expect!(MatchingRule::MaxType(1).to_json().to_string()).to(be_equal_to("{\"match\":\"type\",\"max\":1}"));
2483    expect!(MatchingRule::MinMaxType(1, 10).to_json().to_string()).to(be_equal_to("{\"match\":\"type\",\"max\":10,\"min\":1}"));
2484  }
2485
2486  #[test]
2487  fn rule_list_values_matcher_defined() {
2488    let rule_list_with_value_matcher = RuleList {
2489      rules: vec![ MatchingRule::Type, MatchingRule::Values, MatchingRule::Null ],
2490      rule_logic: RuleLogic::And,
2491      cascaded: false
2492    };
2493
2494    let rule_list_with_each_value_matcher = RuleList {
2495      rules: vec![MatchingRule::Type, MatchingRule::EachValue(MatchingRuleDefinition {
2496        value: "".to_string(),
2497        value_type: ValueType::Unknown,
2498        rules: vec![],
2499        generator: None,
2500        expression: "".to_string()
2501      }), MatchingRule::Null ],
2502      rule_logic: RuleLogic::And,
2503      cascaded: false
2504    };
2505
2506    let rule_list_with_no_value_matcher = RuleList {
2507      rules: vec![ MatchingRule::Type, MatchingRule::Boolean, MatchingRule::Null ],
2508      rule_logic: RuleLogic::And,
2509      cascaded: false
2510    };
2511
2512    expect!(rule_list_with_value_matcher.values_matcher_defined()).to(be_true());
2513    expect!(rule_list_with_each_value_matcher.values_matcher_defined()).to(be_true());
2514    expect!(rule_list_with_no_value_matcher.values_matcher_defined()).to(be_false());
2515  }
2516
2517  // Issue https://github.com/pact-foundation/pact-js-core/issues/400
2518  #[test]
2519  fn to_json_with_matching_rule_on_path_test() {
2520    let matching_rules = MatchingRules {
2521      rules: hashmap!{
2522        Category::PATH => MatchingRuleCategory {
2523          name: Category::PATH,
2524          rules: hashmap!{
2525            DocPath::root() => RuleList {
2526              rules: vec![MatchingRule::Type],
2527              rule_logic: RuleLogic::And,
2528              cascaded: false
2529            }
2530          }
2531        }
2532      }
2533    };
2534
2535    let json = matchers_to_json(&matching_rules, &PactSpecification::V3);
2536    expect!(json).to(be_equal_to(json!({
2537      "path": {
2538        "combine": "AND",
2539        "matchers": [ { "match": "type" } ]
2540      }
2541    })));
2542  }
2543
2544  // Issue #355
2545  #[test]
2546  fn to_json_with_matching_rules_for_headers_and_query_parameters() {
2547    let matching_rules = MatchingRules {
2548      rules: hashmap!{
2549        Category::HEADER => MatchingRuleCategory {
2550          name: Category::HEADER,
2551          rules: hashmap!{
2552            DocPath::new_unwrap("$.A") => RuleList {
2553              rules: vec![MatchingRule::Type],
2554              rule_logic: RuleLogic::And,
2555              cascaded: false
2556            },
2557            DocPath::new_unwrap("$['se-token']") => RuleList {
2558              rules: vec![MatchingRule::Type],
2559              rule_logic: RuleLogic::And,
2560              cascaded: false
2561            }
2562          }
2563        },
2564        Category::QUERY => MatchingRuleCategory {
2565          name: Category::QUERY,
2566          rules: hashmap!{
2567            DocPath::new_unwrap("$.A") => RuleList {
2568              rules: vec![MatchingRule::Type],
2569              rule_logic: RuleLogic::And,
2570              cascaded: false
2571            },
2572            DocPath::new_unwrap("$['se-token']") => RuleList {
2573              rules: vec![MatchingRule::Type],
2574              rule_logic: RuleLogic::And,
2575              cascaded: false
2576            }
2577          }
2578        }
2579      }
2580    };
2581
2582    let json = matchers_to_json(&matching_rules, &PactSpecification::V3);
2583    assert_eq!(json, json!({
2584      "header": {
2585        "A": {
2586          "combine": "AND",
2587          "matchers": [ { "match": "type" } ]
2588        },
2589        "se-token": {
2590          "combine": "AND",
2591          "matchers": [ { "match": "type" } ]
2592        }
2593      },
2594      "query": {
2595        "A": {
2596          "combine": "AND",
2597          "matchers": [ { "match": "type" } ]
2598        },
2599        "se-token": {
2600          "combine": "AND",
2601          "matchers": [ { "match": "type" } ]
2602        }
2603      }
2604    }));
2605
2606    let json = matchers_to_json(&matching_rules, &PactSpecification::V2);
2607    assert_eq!(json, json!({
2608      "$.headers.A": {
2609        "match": "type"
2610      },
2611      "$.headers['se-token']": {
2612        "match": "type"
2613      },
2614      "$.query.A": {
2615        "match": "type"
2616      },
2617      "$.query['se-token']": {
2618        "match": "type"
2619      }
2620    }));
2621  }
2622
2623  #[test]
2624  fn hash_test_for_matchingrules() {
2625    let m1 = MatchingRules::default();
2626    expect!(h(&m1)).to(be_equal_to(15130871412783076140));
2627
2628    let m2 = MatchingRules {
2629      rules: hashmap!{
2630        Category::PATH => MatchingRuleCategory {
2631          name: Category::PATH,
2632          rules: hashmap!{
2633            DocPath::root() => RuleList {
2634              rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2635              rule_logic: RuleLogic::And,
2636              cascaded: false
2637            }
2638          }
2639        }
2640      }
2641    };
2642    expect!(h(&m2)).to(be_equal_to(4156854679723555579));
2643
2644    let m3 = MatchingRules {
2645      rules: hashmap! {
2646        Category::PATH => MatchingRuleCategory::empty(Category::PATH),
2647        Category::HEADER => MatchingRuleCategory::empty(Category::HEADER)
2648      }
2649    };
2650    expect!(h(&m3)).to(be_equal_to(15130871412783076140));
2651  }
2652
2653  #[test]
2654  fn equals_test_for_matchingrules() {
2655    let m1 = MatchingRules::default();
2656    expect!(h(&m1)).to(be_equal_to(15130871412783076140));
2657
2658    let m2 = MatchingRules {
2659      rules: hashmap!{
2660        Category::PATH => MatchingRuleCategory {
2661          name: Category::PATH,
2662          rules: hashmap!{
2663            DocPath::root() => RuleList {
2664              rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2665              rule_logic: RuleLogic::And,
2666              cascaded: false
2667            }
2668          }
2669        }
2670      }
2671    };
2672    expect!(h(&m2)).to(be_equal_to(4156854679723555579));
2673
2674    let m3 = MatchingRules {
2675      rules: hashmap! {
2676        Category::PATH => MatchingRuleCategory::empty(Category::PATH),
2677        Category::HEADER => MatchingRuleCategory::empty(Category::HEADER)
2678      }
2679    };
2680
2681    assert_eq!(m1, m1);
2682    assert_eq!(m2, m2);
2683    assert_eq!(m3, m3);
2684    assert_eq!(m1, m3);
2685
2686    assert_ne!(m1, m2);
2687    assert_ne!(m2, m3);
2688  }
2689
2690  #[test]
2691  fn hash_test_for_matchingrule_category() {
2692    let m1 = MatchingRuleCategory::default();
2693    expect!(h(&m1)).to(be_equal_to(6185506036438099345));
2694
2695    let m2 = MatchingRuleCategory {
2696      name: Category::PATH,
2697      rules: hashmap!{
2698        DocPath::root() => RuleList {
2699          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2700          rule_logic: RuleLogic::And,
2701          cascaded: false
2702        }
2703      }
2704    };
2705    expect!(h(&m2)).to(be_equal_to(5907609104530210046));
2706
2707    let m3 = MatchingRuleCategory::empty(Category::HEADER);
2708    expect!(h(&m3)).to(be_equal_to(11876854719037224982));
2709
2710    let m4 = MatchingRuleCategory::empty(Category::PATH);
2711    expect!(h(&m4)).to(be_equal_to(2206609067086327257));
2712
2713    let m5 = MatchingRuleCategory {
2714      name: Category::PATH,
2715      rules: hashmap!{
2716        DocPath::root() => RuleList {
2717          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2718          rule_logic: RuleLogic::And,
2719          cascaded: false
2720        },
2721        DocPath::root().join("a") => RuleList {
2722          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2723          rule_logic: RuleLogic::And,
2724          cascaded: false
2725        }
2726      }
2727    };
2728    expect!(h(&m5)).to(be_equal_to(2282399821086239745));
2729
2730    let m6 = MatchingRuleCategory {
2731      name: Category::PATH,
2732      rules: hashmap!{
2733        DocPath::root().join("a") => RuleList {
2734          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2735          rule_logic: RuleLogic::And,
2736          cascaded: false
2737        },
2738        DocPath::root() => RuleList {
2739          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2740          rule_logic: RuleLogic::And,
2741          cascaded: false
2742        }
2743      }
2744    };
2745    expect!(h(&m6)).to(be_equal_to(2282399821086239745));
2746  }
2747
2748  #[test]
2749  fn equals_test_for_matchingrule_category() {
2750    let m1 = MatchingRuleCategory::default();
2751    let m2 = MatchingRuleCategory {
2752      name: Category::PATH,
2753      rules: hashmap!{
2754        DocPath::root() => RuleList {
2755          rules: vec![MatchingRule::Include("/1/2/3".to_string())],
2756          rule_logic: RuleLogic::And,
2757          cascaded: false
2758        }
2759      }
2760    };
2761    let m3 = MatchingRuleCategory::empty(Category::PATH);
2762    let m4 = MatchingRuleCategory::empty(Category::HEADER);
2763
2764    assert_eq!(m1, m1);
2765    assert_eq!(m2, m2);
2766    assert_eq!(m3, m3);
2767    assert_eq!(m4, m4);
2768
2769    assert_ne!(m1, m2);
2770    assert_ne!(m1, m3);
2771    assert_ne!(m1, m4);
2772    assert_ne!(m2, m3);
2773  }
2774
2775  #[test]
2776  fn matchingrules_merge() {
2777    let mut m1 = MatchingRules::default();
2778    let m2 = matchingrules!{
2779      "body" => {
2780        "$.a.b" => [ MatchingRule::Type ]
2781      }
2782    };
2783    let m3 = matchingrules!{
2784      "body" => {
2785        "$.a.c" => [ MatchingRule::Equality ]
2786      }
2787    };
2788    let m4 = matchingrules!{
2789      "header" => {
2790        "$.x-test" => [ MatchingRule::Regex(".*".to_owned()) ]
2791      }
2792    };
2793
2794    assert_eq!(m1.rules.len(), 0);
2795    assert_eq!(m1.rules.values().map(|v| v.rules.len()).sum::<usize>(), 0);
2796    m1.merge(&m2);
2797    assert_eq!(m1.rules.len(), 1);
2798    assert_eq!(m1.rules.values().map(|v| v.rules.len()).sum::<usize>(), 1);
2799    m1.merge(&m3);
2800    assert_eq!(m1.rules.len(), 1);
2801    assert_eq!(m1.rules.values().map(|v| v.rules.len()).sum::<usize>(), 2);
2802    m1.merge(&m4);
2803    assert_eq!(m1.rules.len(), 2);
2804    assert_eq!(m1.rules.values().map(|v| v.rules.len()).sum::<usize>(), 3);
2805
2806    assert_eq!(
2807      m1,
2808      matchingrules!{
2809        "body" => {
2810          "$.a.b" => [ MatchingRule::Type ],
2811          "$.a.c" => [ MatchingRule::Equality ]
2812        },
2813        "header" => {
2814          "$.x-test" => [ MatchingRule::Regex(".*".to_owned()) ]
2815        }
2816      }
2817    )
2818  }
2819
2820  #[test]
2821  #[should_panic]
2822  fn each_value_matching_rule_comparison_test() {
2823    assert_eq!(
2824      matchingrules_list!{"body"; "$.array_values" => [
2825        MatchingRule::EachValue(MatchingRuleDefinition::new("[\"string value\"]".to_string(), ValueType::Unknown, MatchingRule::Type, None, "".to_string()))
2826      ]},
2827      matchingrules_list!{"body"; "$.array_values" => [
2828        MatchingRule::EachValue(MatchingRuleDefinition::new("[\"something else\"]".to_string(), ValueType::Unknown, MatchingRule::Type, None, "".to_string()))
2829      ]}
2830    )
2831  }
2832
2833  #[test]
2834  #[should_panic]
2835  fn each_key_matching_rule_comparison_test() {
2836    assert_eq!(
2837      matchingrules_list!{"body"; "$.array_values" => [
2838        MatchingRule::EachKey(MatchingRuleDefinition::new("a_key".to_string(), ValueType::Unknown, MatchingRule::Type, None, "".to_string()))
2839      ]},
2840      matchingrules_list!{"body"; "$.array_values" => [
2841        MatchingRule::EachKey(MatchingRuleDefinition::new("another_key".to_string(), ValueType::Unknown, MatchingRule::Type, None, "".to_string()))
2842      ]}
2843    )
2844  }
2845}