Skip to content

Fix merge-patch deletion for record values - #11695

Open
Vivek JM (vivekjm) wants to merge 2 commits into
microsoft:mainfrom
vivekjm:fix/merge-patch-record-values
Open

Fix merge-patch deletion for record values#11695
Vivek JM (vivekjm) wants to merge 2 commits into
microsoft:mainfrom
vivekjm:fix/merge-patch-record-values

Conversation

@vivekjm

Copy link
Copy Markdown

Summary

  • make transformed Record value types nullable for JSON Merge Patch requests
  • preserve the existing recursive visibility transform for model-valued records before adding the null deletion branch
  • add regression coverage for scalar and model-valued records in both update modes

Fixes #11533.

Root cause

MergePatchUpdate made the record property itself nullable, allowing an entire map to be cleared, but only transformed the record's value when it was a complex merge-patch subject. A scalar value such as Record<string> therefore remained string, so { "tags": { "a": null } } could not represent deletion of one key.

Testing

  • pnpm --filter '@typespec/http...' run build
  • pnpm --filter @typespec/http exec vitest run test/merge-patch.test.ts — 38 tests passed
  • pnpm --filter @typespec/http run build
  • pnpm exec prettier --check packages/http/src/merge-patch.ts packages/http/test/merge-patch.test.ts .chronus/changes/merge-patch-null-record-values-2026-08-16.md
  • pnpm --filter @typespec/http run lint
  • git diff --check

Local setup note: the first focused test invocation ran before the HTTP package and its optional Streams dependency were built, so package resolution failed before any assertions executed. Building the HTTP dependency closure resolved that bootstrap issue; the focused suite and all checks above then passed.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Enables MergePatchUpdate-transformed Record properties to accept null values so clients can delete individual record keys via merge patch semantics.

Changes:

  • Update merge-patch transformation so record indexer values become nullable (T | null).
  • Add/adjust tests to assert record element/indexer value types are nullable unions.
  • Add Chronus change entry documenting the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/http/src/merge-patch.ts Makes record indexer values nullable to represent per-key deletion in merge patches.
packages/http/test/merge-patch.test.ts Adds and updates tests validating nullable record values and related visibility transforms.
.chronus/changes/merge-patch-null-record-values-2026-08-16.md Documents the behavioral fix for @typespec/http.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/http/src/merge-patch.ts
Copilot AI review requested due to automatic review settings August 16, 2026 07:01

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/http/src/merge-patch.ts:506

  • Deduping via types.includes(type) makes nullableRecordValue O(n²) in the number of variants when flattening larger unions. Consider tracking a Set<Type> for membership checks while still building an array for union.create(...) to keep variant collection O(n).
  function nullableRecordValue(realm: Realm, type: Type): Union {
    const types: Type[] = [];
    addType(type);
    addType($(realm).intrinsic.null);
    return $(realm).union.create(types);

    function addType(type: Type): void {
      if ($(realm).union.is(type)) {
        for (const variant of type.variants.values()) {
          addType(variant.type);
        }
      } else if (!types.includes(type)) {
        types.push(type);
      }
    }
  }

packages/http/test/merge-patch.test.ts:1026

  • This test checks null-ness via direct reference equality to $(runner.program).intrinsic.null, while other helpers (e.g., checkNullableUnion) detect null intrinsics by kind/name. To avoid brittle failures if null intrinsics aren’t guaranteed to be referentially identical, consider using the same intrinsic check pattern here (e.g., $(program).intrinsic.is(...) and name comparison).
      expect(
        [...recordValue.variants.values()].filter(
          (variant) => variant.type === $(runner.program).intrinsic.null,
        ),
      ).toHaveLength(1);

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: MergePatchUpdate omits the null branch on Record values, so per-key deletion fails validation

2 participants