Skip to content

Commit bc4b541

Browse files
rholshausenclaude
andcommitted
fix(pact_matching): Fix 3 failing pact_verifier tests under V2 engine
- process_array: use !matchers.cascaded so cascaded MinType rules degrade to type-check only (fixes verifying_a_pact_with_min_type_matcher_and_child_arrays) - Add execute_match_values for match:values action — type-check only, allowing extra keys (fixes verify_pact_with_match_values_matcher) - execute_match: push VALUE(STRING) not ERROR so body type error message survives through the error template (fixes verify_multiple_pacts pact-one) - setup_header_plan: add else error branch to per-header if node so missing headers produce HeaderMismatch not silence (fixes pact-two) - header_mismatches: remove stale BodyMismatch block; fix body/header ordering in into_mismatches - Update header_tests and walk_tree_tests to reflect correct new behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 29302a8 commit bc4b541

5 files changed

Lines changed: 209 additions & 58 deletions

File tree

rust/pact_matching/src/engine/bodies/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl JsonPlanBuilder {
129129
path.last_field().unwrap_or_default(),
130130
matchers.generate_description(true))));
131131
root_node.add(build_matching_rule_node(&ExecutionPlanNode::value_node(json.clone()),
132-
&ExecutionPlanNode::resolve_current_value(path), &matchers, true,
132+
&ExecutionPlanNode::resolve_current_value(path), &matchers, !matchers.cascaded,
133133
context.config.show_types_in_errors));
134134

135135
if let Some(template) = items.first() {

rust/pact_matching/src/engine/interpreter.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ impl ExecutionPlanInterpreter {
269269
self.execute_match_each_key(value_resolver, node, &action_path)
270270
} else if action == "match:each-value" {
271271
self.execute_match_each_value(value_resolver, node, &action_path)
272+
} else if action == "match:values" {
273+
self.execute_match_values(value_resolver, node, &action_path)
272274
} else if action.starts_with("match:") {
273275
match action.strip_prefix("match:") {
274276
None => {
@@ -931,7 +933,7 @@ impl ExecutionPlanInterpreter {
931933
children[2] = else_node.clone();
932934
ExecutionPlanNode {
933935
node_type: node.node_type.clone(),
934-
result: else_node.result.clone(),
936+
result: else_node.result.clone().map(|r| r.truthy()),
935937
children
936938
}
937939
}
@@ -1331,10 +1333,13 @@ impl ExecutionPlanInterpreter {
13311333
}
13321334
Err(err) => {
13331335
if let Some(error_node) = optional.first() {
1334-
self.push_result(Some(NodeResult::ERROR(err.to_string())));
1336+
self.push_result(Some(NodeResult::VALUE(NodeValue::STRING(err.to_string()))));
13351337
match self.walk_tree(action_path.as_slice(), error_node, value_resolver) {
13361338
Ok(error_node) => {
1337-
let message = error_node.value().unwrap_or_default().as_string().unwrap_or_default();
1339+
let message = match error_node.value().unwrap_or_default() {
1340+
NodeResult::ERROR(e) => e,
1341+
other => other.as_string().unwrap_or_default()
1342+
};
13381343
Err(ExecutionPlanNode {
13391344
node_type: node.node_type.clone(),
13401345
result: Some(NodeResult::ERROR(message)),
@@ -1375,6 +1380,37 @@ impl ExecutionPlanInterpreter {
13751380
}
13761381
}
13771382

1383+
fn execute_match_values(
1384+
&mut self,
1385+
value_resolver: &dyn ValueResolver,
1386+
node: &ExecutionPlanNode,
1387+
action_path: &Vec<String>
1388+
) -> ExecutionPlanNode {
1389+
match self.validate_args(4, 0, node, "match:values", value_resolver, action_path) {
1390+
Ok((args, _)) => {
1391+
let expected = args[0].value().unwrap_or_default().as_value();
1392+
let actual = args[1].value().unwrap_or_default().as_value();
1393+
let ok = match (&expected, &actual) {
1394+
(Some(NodeValue::JSON(Value::Object(_))), Some(NodeValue::JSON(Value::Object(_)))) => true,
1395+
(Some(NodeValue::JSON(Value::Array(_))), Some(NodeValue::JSON(Value::Array(_)))) => true,
1396+
(Some(NodeValue::MMAP(_)), Some(NodeValue::MMAP(_))) => true,
1397+
(Some(NodeValue::NULL), _) | (_, Some(NodeValue::NULL)) => true,
1398+
_ => false
1399+
};
1400+
ExecutionPlanNode {
1401+
node_type: node.node_type.clone(),
1402+
result: Some(if ok {
1403+
NodeResult::VALUE(NodeValue::BOOL(true))
1404+
} else {
1405+
NodeResult::ERROR(format!("Expected type {:?} but was {:?}", expected, actual))
1406+
}),
1407+
children: args
1408+
}
1409+
}
1410+
Err(err) => node.clone_with_result(NodeResult::ERROR(err.to_string()))
1411+
}
1412+
}
1413+
13781414
fn execute_match_each_key(
13791415
&mut self,
13801416
value_resolver: &dyn ValueResolver,

rust/pact_matching/src/engine/mod.rs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,12 +1478,12 @@ impl Into<Vec<Mismatch>> for ExecutionPlan {
14781478
if let Some(mismatch) = status_mismatch(&response) {
14791479
result.push(mismatch);
14801480
}
1481+
let body = body_mismatches(&response);
1482+
result.extend(body.mismatches());
14811483
let headers = header_mismatches(&response);
14821484
for mismatches in headers.values() {
14831485
result.extend(mismatches.clone());
14841486
}
1485-
let body = body_mismatches(&response);
1486-
result.extend(body.mismatches());
14871487
}
14881488

14891489
if let Some(message) = self.fetch_node(&[":message"]) {
@@ -1580,7 +1580,7 @@ pub(crate) fn query_mismatches(plan: &ExecutionPlanNode) -> HashMap<String, Vec<
15801580

15811581
pub(crate) fn header_mismatches(plan: &ExecutionPlanNode) -> HashMap<String, Vec<Mismatch>> {
15821582
let headers_node = plan.fetch_child_node(&[":headers"]).unwrap_or_default();
1583-
let mut headers = headers_node.children.iter()
1583+
let headers = headers_node.children.iter()
15841584
.fold(hashmap! {}, |mut acc, child| {
15851585
if let PlanNodeType::CONTAINER(label) = &child.node_type {
15861586
let mismatches = child.errors().iter().map(|err| HeaderMismatch {
@@ -1605,18 +1605,6 @@ pub(crate) fn header_mismatches(plan: &ExecutionPlanNode) -> HashMap<String, Vec
16051605
};
16061606
acc
16071607
});
1608-
let errors = headers_node.child_errors(Terminator::CONTAINERS);
1609-
if !errors.is_empty() {
1610-
let mismatches = errors.iter()
1611-
.map(|err| BodyMismatch {
1612-
path: "".to_string(),
1613-
expected: None,
1614-
actual: None,
1615-
mismatch: err.clone(),
1616-
})
1617-
.collect_vec();
1618-
headers.insert("".to_string(), mismatches);
1619-
}
16201608
headers
16211609
}
16221610

@@ -2017,6 +2005,13 @@ fn setup_header_plan<T: HttpPart>(
20172005
presence_check.add(item_check);
20182006
}
20192007

2008+
presence_check.add(
2009+
ExecutionPlanNode::action("error")
2010+
.add(ExecutionPlanNode::value_node(
2011+
format!("Expected header '{}' but was missing", key)
2012+
))
2013+
);
2014+
20202015
item_node.add(presence_check);
20212016
plan_node.add(item_node);
20222017
}

0 commit comments

Comments
 (0)