1use 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#[derive(Debug, Clone, Eq)]
63pub enum MatchingRule {
64 Equality,
66 Regex(String),
68 Type,
70 MinType(usize),
72 MaxType(usize),
74 MinMaxType(usize, usize),
76 Timestamp(String),
78 Time(String),
80 Date(String),
82 Include(String),
84 Number,
86 Integer,
88 Decimal,
90 Null,
92 ContentType(String),
94 ArrayContains(Vec<(usize, MatchingRuleCategory, HashMap<DocPath, Generator>)>),
96 Values,
98 Boolean,
100 StatusCode(HttpStatus),
102 NotEmpty,
104 Semver,
106 EachKey(MatchingRuleDefinition),
108 EachValue(MatchingRuleDefinition)
110}
111
112impl MatchingRule {
113
114 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 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 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 pub fn generators(&self) -> Vec<Generator> {
249 match self {
250 MatchingRule::ArrayContains(variants) => vec![Generator::ArrayContains(variants.clone())],
251 _ => vec![]
252 }
253 }
254
255 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 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 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 pub fn is_values_matcher(&self) -> bool {
481 match self {
482 MatchingRule::Values => true,
483 MatchingRule::EachValue(_) => true,
484 _ => false
485 }
486 }
487
488 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 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 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 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 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 pub fn can_match(&self, content_type: &ContentType) -> bool {
585 match self {
586 MatchingRule::NotEmpty => true,
587 MatchingRule::ArrayContains(_) => true, 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#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, PartialOrd, Ord)]
650pub enum RuleLogic {
651 And,
653 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#[derive(Debug, Clone, Eq)]
668pub struct RuleList {
669 pub rules: Vec<MatchingRule>,
671 pub rule_logic: RuleLogic,
673 pub cascaded: bool
675}
676
677impl RuleList {
678
679 pub fn empty(rule_logic: RuleLogic) -> RuleList {
681 RuleList {
682 rules: Vec::new(),
683 rule_logic,
684 cascaded: false
685 }
686 }
687
688 pub fn equality() -> RuleList {
690 RuleList {
691 rules: vec![ MatchingRule::Equality ],
692 rule_logic: RuleLogic::And,
693 cascaded: false
694 }
695 }
696
697 pub fn new(rule: MatchingRule) -> RuleList {
699 RuleList {
700 rules: vec![ rule ],
701 rule_logic: RuleLogic::And,
702 cascaded: false
703 }
704 }
705
706 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 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 pub fn values_matcher_defined(&self) -> bool {
738 self.rules.iter().any(MatchingRule::is_values_matcher)
739 }
740
741 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 pub fn add_rule(&mut self, rule: &MatchingRule) {
754 self.rules.push(rule.clone())
755 }
756
757 pub fn as_cascaded(&self, b: bool) -> RuleList {
759 RuleList {
760 cascaded: b,
761 .. self.clone()
762 }
763 }
764
765 pub fn add_rules(&mut self, rules: &RuleList) {
767 for rule in &rules.rules {
768 self.add_rule(rule);
769 }
770 }
771
772 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 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 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 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 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#[derive(PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord)]
841pub enum Category {
842 METHOD,
844 PATH,
846 HEADER,
848 QUERY,
850 BODY,
852 STATUS,
854 CONTENTS,
856 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#[derive(Debug, Clone, Eq, Default)]
936pub struct MatchingRuleCategory {
937 pub name: Category,
939 pub rules: HashMap<DocPath, RuleList>
941}
942
943impl MatchingRuleCategory {
944 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 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 pub fn is_empty(&self) -> bool {
968 self.rules.is_empty()
969 }
970
971 pub fn is_not_empty(&self) -> bool {
973 !self.rules.is_empty()
974 }
975
976 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 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 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 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 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 pub fn type_matcher_defined(&self) -> bool {
1065 self.rules.values().any(|rule_list| rule_list.type_matcher_defined())
1066 }
1067
1068 pub fn values_matcher_defined(&self) -> bool {
1070 self.rules.values().any(|rule_list| rule_list.values_matcher_defined())
1071 }
1072
1073 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 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 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 pub fn as_rule_list(&self) -> RuleList {
1102 self.rules.values().next().cloned().unwrap_or_default()
1103 }
1104
1105 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 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 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 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 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#[derive(Debug, Clone, Eq)]
1261pub struct MatchingRules {
1262 pub rules: HashMap<Category, MatchingRuleCategory>
1264}
1265
1266impl MatchingRules {
1267
1268 pub fn is_empty(&self) -> bool {
1270 self.rules.values().all(|category| category.is_empty())
1271 }
1272
1273 pub fn is_not_empty(&self) -> bool {
1275 self.rules.values().any(|category| category.is_not_empty())
1276 }
1277
1278 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 pub fn categories(&self) -> HashSet<Category> {
1291 self.rules.keys().cloned().collect()
1292 }
1293
1294 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 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 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 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 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 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 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 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 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
1530pub 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
1556pub 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_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_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(®ex1)).to(be_equal_to(h(®ex1)));
1679 expect!(®ex1).to(be_equal_to(®ex1));
1680 expect!(h(®ex1)).to_not(be_equal_to(h(®ex2)));
1681 expect!(®ex1).to_not(be_equal_to(®ex2));
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 #[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 #[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}