Skip to content

fix(matching): match combined headers against repeated expectations - #568

Draft
YOU54F wants to merge 2 commits into
masterfrom
fix/repeated-headers
Draft

YOU54F wants to merge 2 commits into
masterfrom
fix/repeated-headers

Conversation

@YOU54F

@YOU54F YOU54F commented Oct 16, 2026

Copy link
Copy Markdown
Member

Normalize combined actual field lines during matching while preserving quoted commas, indexed matchers, singleton headers, and pact data.

Add regression coverage for repeated custom header expectations.

See repro test in pact-foundation/pact-core-mock-server#16
Identified in pact-foundation/pact-net#566
Fix tested locally in pact-foundation/pact-net#567

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@YOU54F

YOU54F commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

I didn't mean to press it anyway 😂

@rholshausen

Copy link
Copy Markdown
Contributor

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

I didn't mean to press it anyway 😂

Likewise, if you want me to review it, you need to pay as well

Comment thread rust/pact_matching/src/headers.rs Outdated

#[test]
fn match_headers_combined_custom_header_compatibility() {
let cases: &[(&[&str], &[&str], bool)] = &[

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use rstest it will generate a separate test per case

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty, handy tip!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Normalization remains inconsistent for list matchers and is bypassed by the V2 matching engine.

2 open issues

Review effort: Balanced

Comment thread rust/pact_matching/src/headers.rs Outdated
Comment on lines +180 to +181
let actual_values = if value.len() > 1 && actual_values.len() < value.len()
&& !SINGLE_VALUE_HEADERS.contains(&key.to_lowercase().as_str()) {
let mut mismatches = vec![];
let actual_values = if value.len() > 1 && actual_values.len() < value.len()
&& !SINGLE_VALUE_HEADERS.contains(&key.to_lowercase().as_str()) {
split_header_field_lines(&actual_values)
Normalize combined actual field lines during matching while preserving
quoted commas, indexed matchers, singleton headers, and pact data.

Add regression coverage for repeated custom header expectations.
Split combined custom-header values consistently for list matching, regardless
of field-line grouping. Apply the same normalization in the V2 request and
response plans while preserving quoted and JSON values, and add regression
coverage.
@YOU54F
YOU54F force-pushed the fix/repeated-headers branch from 1132e06 to 138fc40 Compare October 19, 2026 11:55
@JP-Ellis

Copy link
Copy Markdown
Member

I think there are a few regressions this PR introduces, mostly around values that contain commas. I got Claude to do some vetting and here's what it had to say (after I got it to cut down on the verbiage 😆)


Regression: a repeated header whose values contain commas no longer matches itself. Only the actual side is split, so identical lines now fail. This hits consumer tests and V4 provider verification, in both engines:

Expected Actual (identical lines) master PR
X-Request: ["a, b", "c"] a, b / c pass fail
X-Sunset: ["Sat, 01 Mar 2025 00:00:00 GMT", "Sun, 01 Jun 2025 00:00:00 GMT"] same pass fail
Link: ["<…?fields=id,name>; rel=\"next\"", "<…?fields=id,name>; rel=\"last\""] same pass fail

Splitting the expected values too, under the same value.len() > 1 condition, would make the comparison symmetric:

case(&["a, b", "c"], &["a, b", "c"], true),

Empty members. Expected ["request1", "request2"]:

  • request1, request2,: legacy passes, V2 fails.
  • request1, , request2: fails in both.

The legacy pass comes from the padding in match_header_maps. RFC 9110 §5.6.1.2 says to ignore empty elements, so dropping them in split_header_field_lines would fix all of these.

The two engines split different headers. Legacy skips only SINGLE_VALUE_HEADERS. V2 also skips PARAMETERISED_HEADERS and MULTI_VALUE_HEADERS. So legacy now splits Content-Type and V2 doesn't. One shared predicate would keep them aligned.

Behaviour change worth a test. Regex ^a, b$ on $.x-request, with expected ["a", "b"], against a, b: this passed on master and fails now. It already failed against two separate lines, so this is correct, but it deserves a test and a changelog line.

Nits: pact_matching/README.md ("Matching Headers") doesn't describe the split. The two len() asserts at headers.rs:648-649 can't fail.


Not sure exactly what is in scope vs out of scope for Pact vs RFC 9110 vs what people use in practice. So a few open questions:

  1. Which values count as a list? The PR treats a header as a list when it has more than one expected value, but RFC 9110 leaves that to each header's grammar, and Pact doesn't know the grammar of a custom header.

    Should the expected values be split the same way as the actual values? Should only known list headers be split, extending MULTI_VALUE_HEADERS? Should we provide a way to specify the grammar?

  2. Matchers: should a header-level matcher apply to each member, or to the whole combined value? The PR applies it to each member, but that is a deviation to how things were done, and will break existing pacts (which leads me to)

  3. Compatibility: having a previously (incorrectly) failing test now pass is fine, but a previously passing test that now fails could be a somewhat unpleasant surprise. How do we want to address this? The main ones here as:

    • ["a, b", "c"] compared against a, b / c used to pass and now fails
    • ["Mon, 01 Jan 2024", "Tue, 02 Jan 2024"] used to pass and now fails
    • regex ^a, b$ on $.x-request used to pass and now fails

One last thing: this also has ramifications on the provider side, especially given that V2 and V3 pacts write repeated values as a single comma-separated string. So a provider that sends two separate lines will fail verification.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants