use std::path::Path;
use anyhow::anyhow;
use bytes::Bytes;
use log::*;
use maplit::*;
use pact_models::bodies::OptionalBody;
use pact_models::generators::{Generator, GeneratorCategory, Generators};
use pact_models::json_utils::{json_to_num, json_to_string};
use pact_models::matchingrules::{Category, MatchingRule, MatchingRuleCategory, RuleLogic};
use pact_models::path_exp::DocPath;
use pact_models::v4::http_parts::{HttpRequest, HttpResponse};
use serde_json::{Map, Value};
const CONTENT_TYPE_HEADER: &str = "Content-Type";
pub fn process_array(
array: &[Value],
matching_rules: &mut MatchingRuleCategory,
generators: &mut Generators,
path: DocPath,
type_matcher: bool,
skip_matchers: bool
) -> Value {
trace!(">>> process_array(array={array:?}, matching_rules={matching_rules:?}, generators={generators:?}, path={path}, type_matcher={type_matcher}, skip_matchers={skip_matchers})");
debug!("Path = {path}");
Value::Array(array.iter().enumerate().map(|(index, val)| {
let mut item_path = path.clone();
if type_matcher {
item_path.push_star_index();
} else {
item_path.push_index(index);
}
match val {
Value::Object(ref map) => process_object(map, matching_rules, generators, item_path, skip_matchers),
Value::Array(ref array) => process_array(array, matching_rules, generators, item_path, false, skip_matchers),
_ => val.clone()
}
}).collect())
}
pub fn process_object(
obj: &Map<String, Value>,
matching_rules: &mut MatchingRuleCategory,
generators: &mut Generators,
path: DocPath,
skip_matchers: bool
) -> Value {
trace!(">>> process_object(obj={obj:?}, matching_rules={matching_rules:?}, generators={generators:?}, path={path}, skip_matchers={skip_matchers})");
debug!("Path = {path}");
let result = if let Some(matcher_type) = obj.get("pact:matcher:type") {
debug!("detected pact:matcher:type, will configure a matcher");
if !skip_matchers {
let matcher_type = json_to_string(matcher_type);
let matching_rule = match matcher_type.as_str() {
"arrayContains" | "array-contains" => match obj.get("variants") {
Some(Value::Array(variants)) => {
let values = variants.iter().enumerate().map(|(index, variant)| {
let mut category = MatchingRuleCategory::empty("body");
let mut generators = Generators::default();
match variant {
Value::Object(map) => {
process_object(map, &mut category, &mut generators, DocPath::root(), false);
}
_ => warn!("arrayContains: JSON for variant {} is not correctly formed: {}", index, variant)
}
(index, category, generators.categories.get(&GeneratorCategory::BODY).cloned().unwrap_or_default())
}).collect();
Ok(MatchingRule::ArrayContains(values))
}
_ => Err(anyhow!("ArrayContains 'variants' attribute is missing or not an array"))
},
_ => {
let attributes = Value::Object(obj.clone());
MatchingRule::create(matcher_type.as_str(), &attributes)
}
};
trace!("matching_rule = {matching_rule:?}");
match &matching_rule {
Ok(rule) => matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And),
Err(err) => error!("Failed to parse matching rule from JSON - {}", err)
};
if let Some(gen) = obj.get("pact:generator:type") {
debug!("detected pact:generator:type, will configure a generators");
if let Some(generator) = Generator::from_map(&json_to_string(gen), obj) {
let category = match matching_rules.name {
Category::BODY => &GeneratorCategory::BODY,
Category::HEADER => &GeneratorCategory::HEADER,
Category::PATH => &GeneratorCategory::PATH,
Category::QUERY => &GeneratorCategory::QUERY,
_ => {
warn!("invalid generator category {} provided, defaulting to body", matching_rules.name);
&GeneratorCategory::BODY
}
};
generators.add_generator_with_subcategory(category, path.clone(), generator);
}
}
let (value, skip_matchers) = if let Ok(rule) = &matching_rule {
match rule {
MatchingRule::ArrayContains(_) => (obj.get("variants"), true),
_ => (obj.get("value"), false)
}
} else {
(obj.get("value"), false)
};
match value {
Some(val) => match val {
Value::Object(ref map) => process_object(map, matching_rules, generators, path, skip_matchers),
Value::Array(array) => process_array(array, matching_rules, generators, path, true, skip_matchers),
_ => val.clone()
},
None => Value::Null
}
} else {
debug!("Skipping the matching rule (skip_matchers == true)");
match obj.get("value") {
Some(val) => match val {
Value::Object(ref map) => process_object(map, matching_rules, generators, path, skip_matchers),
Value::Array(array) => process_array(array, matching_rules, generators, path, false, skip_matchers),
_ => val.clone()
},
None => Value::Null
}
}
} else {
debug!("Configuring a normal object");
Value::Object(obj.iter()
.filter(|(key, _)| !key.starts_with("pact:"))
.map(|(key, val)| {
let path_vec = path.to_vec();
let path_slice = path_vec.iter().map(|p| p.as_str()).collect::<Vec<_>>();
let matchers_for_path = matching_rules.resolve_matchers_for_path(path_slice.as_slice());
let item_path = if matchers_for_path.values_matcher_defined() {
path.join("*")
} else {
path.join(key)
};
(key.clone(), match val {
Value::Object(ref map) => process_object(map, matching_rules, generators, item_path, skip_matchers),
Value::Array(ref array) => process_array(array, matching_rules, generators, item_path, false, skip_matchers),
_ => val.clone()
})
}).collect())
};
trace!("-> result = {result:?}");
result
}
#[deprecated(note = "Replace with MatchingRule::create")]
pub fn matcher_from_integration_json(m: &Map<String, Value>) -> Option<MatchingRule> {
match m.get("pact:matcher:type") {
Some(value) => {
let val = json_to_string(value);
MatchingRule::create(val.as_str(), &Value::Object(m.clone()))
.map_err(|err| error!("Failed to create matching rule from JSON '{:?}': {}", m, err))
.ok()
},
_ => None
}
}
pub fn process_json(body: String, matching_rules: &mut MatchingRuleCategory, generators: &mut Generators) -> String {
trace!("process_json");
match serde_json::from_str(&body) {
Ok(json) => match json {
Value::Object(ref map) => process_object(map, matching_rules, generators, DocPath::root(), false).to_string(),
Value::Array(ref array) => process_array(array, matching_rules, generators, DocPath::root(), false, false).to_string(),
_ => body
},
Err(_) => body
}
}
pub fn process_json_value(body: &Value, matching_rules: &mut MatchingRuleCategory, generators: &mut Generators) -> String {
match body {
Value::Object(ref map) => process_object(map, matching_rules, generators, DocPath::root(), false).to_string(),
Value::Array(ref array) => process_array(array, matching_rules, generators, DocPath::root(), false, false).to_string(),
_ => body.to_string()
}
}
pub fn request_multipart(request: &mut HttpRequest, boundary: &str, body: OptionalBody, content_type: &str, part_name: &str) {
request.body = body;
match request.headers {
Some(ref mut headers) => {
headers.insert(CONTENT_TYPE_HEADER.to_string(), vec![format!("multipart/form-data; boundary={}", boundary)]);
},
None => {
request.headers = Some(hashmap! {
CONTENT_TYPE_HEADER.to_string() => vec![format!("multipart/form-data; boundary={}", boundary)]
});
}
};
let mut path = DocPath::root();
path.push_field(part_name);
request.matching_rules.add_category("body")
.add_rule(path, MatchingRule::ContentType(content_type.into()), RuleLogic::And);
request.matching_rules.add_category("header")
.add_rule(DocPath::new_unwrap("Content-Type"),
MatchingRule::Regex(r"multipart/form-data;(\s*charset=[^;]*;)?\s*boundary=.*".into()), RuleLogic::And);
}
pub fn response_multipart(response: &mut HttpResponse, boundary: &str, body: OptionalBody, content_type: &str, part_name: &str) {
response.body = body;
match response.headers {
Some(ref mut headers) => {
headers.insert(CONTENT_TYPE_HEADER.to_string(), vec![format!("multipart/form-data; boundary={}", boundary)]);
},
None => {
response.headers = Some(hashmap! {
CONTENT_TYPE_HEADER.to_string() => vec![format!("multipart/form-data; boundary={}", boundary)]
});
}
}
let mut path = DocPath::root();
path.push_field(part_name);
response.matching_rules.add_category("body")
.add_rule(path, MatchingRule::ContentType(content_type.into()), RuleLogic::And);
response.matching_rules.add_category("header")
.add_rule(DocPath::new_unwrap("Content-Type"),
MatchingRule::Regex(r"multipart/form-data;(\s*charset=[^;]*;)?\s*boundary=.*".into()), RuleLogic::And);
}
#[derive(Clone, Debug)]
pub struct MultipartBody {
pub body: OptionalBody,
pub boundary: String,
}
pub fn file_as_multipart_body(file: &str, part_name: &str) -> Result<MultipartBody, String> {
let mut multipart = multipart::client::Multipart::from_request(multipart::mock::ClientRequest::default()).unwrap();
multipart.write_file(part_name, Path::new(file)).map_err(format_multipart_error)?;
let http_buffer = multipart.send().map_err(format_multipart_error)?;
Ok(MultipartBody {
body: OptionalBody::Present(Bytes::from(http_buffer.buf), Some("multipart/form-data".into()), None),
boundary: http_buffer.boundary
})
}
pub fn empty_multipart_body() -> Result<MultipartBody, String> {
let multipart = multipart::client::Multipart::from_request(multipart::mock::ClientRequest::default()).unwrap();
let http_buffer = multipart.send().map_err(format_multipart_error)?;
Ok(MultipartBody {
body: OptionalBody::Present(Bytes::from(http_buffer.buf), Some("multipart/form-data".into()), None),
boundary: http_buffer.boundary
})
}
fn format_multipart_error(e: std::io::Error) -> String {
format!("convert_ptr_to_mime_part_body: Failed to generate multipart body: {}", e)
}
#[cfg(test)]
mod test {
use expectest::prelude::*;
use pact_models::{generators, HttpStatus, matchingrules_list};
use pact_models::generators::{Generator, Generators};
use pact_models::matchingrules::{MatchingRule, MatchingRuleCategory};
use pact_models::matchingrules::expressions::{MatchingRuleDefinition, ValueType};
use pact_models::path_exp::DocPath;
use serde_json::{json, Map};
#[allow(deprecated)]
use crate::mock_server::bodies::{matcher_from_integration_json, process_object};
#[test]
fn process_object_with_normal_json_test() {
let json = json!({
"a": "b",
"c": [100, 200, 300]
});
let mut matching_rules = MatchingRuleCategory::default();
let mut generators = Generators::default();
let result = process_object(json.as_object().unwrap(), &mut matching_rules,
&mut generators, DocPath::root(), false);
expect!(result).to(be_equal_to(json));
}
#[test]
fn process_object_with_matching_rule_test() {
let json = json!({
"a": {
"pact:matcher:type": "regex",
"regex": "\\w+",
"value": "b"
},
"c": [100, 200, {
"pact:matcher:type": "integer",
"pact:generator:type": "RandomInt",
"value": 300
}]
});
let mut matching_rules = MatchingRuleCategory::empty("body");
let mut generators = Generators::default();
let result = process_object(json.as_object().unwrap(), &mut matching_rules,
&mut generators, DocPath::root(), false);
expect!(result).to(be_equal_to(json!({
"a": "b",
"c": [100, 200, 300]
})));
expect!(matching_rules).to(be_equal_to(matchingrules_list!{
"body";
"$.a" => [ MatchingRule::Regex("\\w+".into()) ],
"$.c[2]" => [ MatchingRule::Integer ]
}));
expect!(generators).to(be_equal_to(generators! {
"BODY" => {
"$.c[2]" => Generator::RandomInt(0, 10)
}
}));
}
#[test]
fn process_object_with_primitive_json_value() {
let json = json!({
"pact:matcher:type": "regex",
"regex": "\\w+",
"value": "b"
});
let mut matching_rules = MatchingRuleCategory::empty("body");
let mut generators = Generators::default();
let result = process_object(json.as_object().unwrap(), &mut matching_rules,
&mut generators, DocPath::root(), false);
expect!(result).to(be_equal_to(json!("b")));
expect!(matching_rules).to(be_equal_to(matchingrules_list!{
"body";
"$" => [ MatchingRule::Regex("\\w+".into()) ]
}));
expect!(generators).to(be_equal_to(Generators::default()));
}
#[test_log::test]
fn process_object_with_nested_object_has_the_same_property_name_as_a_parent_object() {
let json = json!({
"result": {
"pact:matcher:type": "type",
"value": {
"details": {
"pact:matcher:type": "type",
"value": [
{
"type": {
"pact:matcher:type": "regex",
"value": "Information",
"regex": "(None|Information|Warning|Error)"
}
}
],
"min": 1
},
"findings": {
"pact:matcher:type": "type",
"value": [
{
"details": {
"pact:matcher:type": "type",
"value": [
{
"type": {
"pact:matcher:type": "regex",
"value": "Information",
"regex": "(None|Information|Warning|Error)"
}
}
],
"min": 1
},
"type": {
"pact:matcher:type": "regex",
"value": "Unspecified",
"regex": "(None|Unspecified)"
}
}
],
"min": 1
}
}
}
});
let mut matching_rules = MatchingRuleCategory::default();
let mut generators = Generators::default();
let result = process_object(json.as_object().unwrap(), &mut matching_rules,
&mut generators, DocPath::root(), false);
expect!(result).to(be_equal_to(json!({
"result": {
"details": [
{
"type": "Information"
}
],
"findings": [
{
"details": [
{
"type": "Information"
}
],
"type": "Unspecified"
}
]
}
})));
expect!(matching_rules.to_v3_json().to_string()).to(be_equal_to(matchingrules_list!{
"body";
"$.result" => [ MatchingRule::Type ],
"$.result.details" => [ MatchingRule::MinType(1) ],
"$.result.details[*].type" => [ MatchingRule::Regex("(None|Information|Warning|Error)".into()) ],
"$.result.findings" => [ MatchingRule::MinType(1) ],
"$.result.findings[*].details" => [ MatchingRule::MinType(1) ],
"$.result.findings[*].details[*].type" => [ MatchingRule::Regex("(None|Information|Warning|Error)".into()) ],
"$.result.findings[*].type" => [ MatchingRule::Regex("(None|Unspecified)".into()) ]
}.to_v3_json().to_string()));
expect!(generators).to(be_equal_to(Generators::default()));
}
#[test_log::test]
fn process_object_with_nested_object_with_type_matchers_and_decimal_matcher() {
let json = json!({
"pact:matcher:type": "type",
"value": {
"name": {
"pact:matcher:type": "type",
"value": "APL"
},
"price": {
"pact:matcher:type": "decimal",
"value": 1.23
}
}
});
let mut matching_rules = MatchingRuleCategory::default();
let mut generators = Generators::default();
let result = process_object(json.as_object().unwrap(), &mut matching_rules,
&mut generators, DocPath::root(), false);
expect!(result).to(be_equal_to(json!({
"name": "APL",
"price": 1.23
})));
expect!(matching_rules).to(be_equal_to(matchingrules_list!{
"body";
"$" => [ MatchingRule::Type ],
"$.name" => [ MatchingRule::Type ],
"$.price" => [ MatchingRule::Decimal ]
}));
expect!(generators).to(be_equal_to(Generators::default()));
}
#[test_log::test]
#[allow(deprecated)]
fn matcher_from_integration_json_test() {
expect!(matcher_from_integration_json(&Map::default())).to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "Other" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "regex" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "regex", "regex": "[a-z]" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Regex("[a-z]".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "equality" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Equality));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "include" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "include", "value": "[a-z]" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Include("[a-z]".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "type" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Type));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "type", "min": 100 }).as_object().unwrap()))
.to(be_some().value(MatchingRule::MinType(100)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "type", "max": 100 }).as_object().unwrap()))
.to(be_some().value(MatchingRule::MaxType(100)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "type", "min": 10, "max": 100 }).as_object().unwrap()))
.to(be_some().value(MatchingRule::MinMaxType(10, 100)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "number" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Number));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "integer" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Integer));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "decimal" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Decimal));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "real" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Decimal));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "min" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "min", "min": 100 }).as_object().unwrap()))
.to(be_some().value(MatchingRule::MinType(100)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "max" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "max", "max": 100 }).as_object().unwrap()))
.to(be_some().value(MatchingRule::MaxType(100)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "timestamp" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "timestamp", "format": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Timestamp("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "timestamp", "timestamp": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Timestamp("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "datetime" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "date" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "date", "format": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Date("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "date", "date": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Date("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "time" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "time", "format": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Time("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "time", "time": "yyyy-MM-dd" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Time("yyyy-MM-dd".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "null" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Null));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "boolean" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Boolean));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "contentType" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "contentType", "value": "text/plain" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::ContentType("text/plain".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "content-type" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "content-type", "value": "text/plain" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::ContentType("text/plain".to_string())));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "arrayContains" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "arrayContains", "variants": "text" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "arrayContains", "variants": [] }).as_object().unwrap()))
.to(be_some().value(MatchingRule::ArrayContains(vec![])));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "array-contains" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "array-contains", "variants": "text" }).as_object().unwrap()))
.to(be_none());
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "array-contains", "variants": [] }).as_object().unwrap()))
.to(be_some().value(MatchingRule::ArrayContains(vec![])));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "values" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Values));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "statusCode" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::StatusCode(HttpStatus::Success)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "statusCode", "status": [200] }).as_object().unwrap()))
.to(be_some().value(MatchingRule::StatusCode(HttpStatus::StatusCodes(vec![200]))));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "status-code" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::StatusCode(HttpStatus::Success)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "status-code", "status": "success" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::StatusCode(HttpStatus::Success)));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "notEmpty" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::NotEmpty));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "not-empty" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::NotEmpty));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "semver" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::Semver));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "eachKey" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::EachKey(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None,
})));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "each-key" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::EachKey(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None,
})));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "eachValue" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::EachValue(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None,
})));
expect!(matcher_from_integration_json(&json!({ "pact:matcher:type": "each-value" }).as_object().unwrap()))
.to(be_some().value(MatchingRule::EachValue(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None,
})));
}
}