Skip to content

[python] avoid etag/match_condition clientName collision with multiple etag headers#10816

Draft
l0lawrence wants to merge 1 commit into
microsoft:mainfrom
l0lawrence:fix/etag-multi-header-collision
Draft

[python] avoid etag/match_condition clientName collision with multiple etag headers#10816
l0lawrence wants to merge 1 commit into
microsoft:mainfrom
l0lawrence:fix/etag-multi-header-collision

Conversation

@l0lawrence
Copy link
Copy Markdown
Member

Problem

PR #10494 (which added support for custom etag wire names) broke operations that have more than one Azure.Core.eTag-typed header. The Azure Storage Blob copyFromUrl operation is a concrete example — it carries:

  • standard If-Match / If-None-Match
  • custom x-ms-source-if-match / x-ms-source-if-none-match

The emitter in http.ts stamps etagRole = "ifMatch" / "ifNoneMatch" onto every one of those headers. Then in preprocess/__init__.py, update_parameter blindly applies headers_convert(ETAG_MATCH_DATA) / ETAG_NONE_MATCH_DATA to every parameter with that role — overwriting clientName to "etag" and "match_condition" on both pairs. The operation ends up with two parameters named etag and two named match_condition.

The slot-picker in update_client already chose one ifMatch/ifNoneMatch slot per operation, but it did nothing to prevent the per-parameter rename on the others.

Fix

In preprocess/__init__.py update_client:

  1. Collect all ifMatch and ifNoneMatch candidates per operation, not just the first.
  2. New _pick_etag_slot helper prefers the standard If-Match / If-None-Match wire names over custom etag headers (matches pre-PR-10494 behaviour when both are present).
  3. Strip etagRole from non-selected candidates so update_parameter leaves their natural clientName intact (e.g. source_if_match, source_if_none_match).

Result

For copyFromUrl:

  • If-Match / If-None-Match → promoted to the etag / match_condition pair (unchanged behaviour vs. pre-PR-10494).
  • x-ms-source-if-match / x-ms-source-if-none-match → keep their natural source_if_match / source_if_none_match client names.

No more clientName collisions.

Tests

Adds tests/unit/test_preprocess_etag.py with six tests:

  • test_etag_role_preserved_when_only_standard_pair_present
  • test_etag_role_preserved_when_only_custom_pair_present
  • test_standard_etag_wins_over_custom_when_both_present (regression)
  • test_first_custom_pair_chosen_when_multiple_custom_pairs_present
  • test_synthetic_partner_still_works_with_only_one_custom_etag
  • test_full_update_yaml_does_not_collide_client_names (end-to-end)

Verified the three multi-etag tests fail on upstream/main and pass with this change. All existing unit tests still pass.

Spec referenced

The original repro is copyFromUrl in specification/storage/Microsoft.BlobStorage/routes.tsp of Azure/azure-rest-api-specs, which composes SourceIfMatchParameter, SourceIfNoneMatchParameter, IfMatchParameter, and IfNoneMatchParameter (all Azure.Core.eTag).

…ion with multiple etag headers

When an operation has more than one Azure.Core.eTag-typed header (e.g. Storage's copyFromUrl, which carries both standard If-Match/If-None-Match AND custom x-ms-source-if-match/x-ms-source-if-none-match), PR microsoft#10494's per-parameter conversion renamed every etag-typed header to clientName='etag' or 'match_condition', producing two parameters with the same name on the generated method.

Fix: in preprocess update_client, collect all ifMatch/ifNoneMatch candidates per operation, prefer the standard If-Match/If-None-Match wire names for the etag/match_condition slot, and strip etagRole from the non-selected candidates so they retain their natural clientName (e.g. source_if_match).

Adds tests/unit/test_preprocess_etag.py covering the standard-only, custom-only, mixed (regression), multi-custom-pair, synthetic-partner, and end-to-end clientName uniqueness scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:python Issue for the Python client emitter: @typespec/http-client-python label May 27, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-python@10816

commit: f4c7af2

@github-actions
Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/http-client-python
Show changes

@typespec/http-client-python - fix ✏️

Fix etag/match_condition clientName collision when an operation has more than one Azure.Core.eTag-typed header (e.g. Storage's copyFromUrl, which has both If-Match/If-None-Match and x-ms-source-if-match/x-ms-source-if-none-match). The standard If-Match/If-None-Match pair is now preferred for the etag/match_condition slot, and any additional etag-typed headers retain their natural client name (e.g. source_if_match).

@azure-sdk-automation
Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

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

Labels

emitter:client:python Issue for the Python client emitter: @typespec/http-client-python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant