Skip to content

[Bug]: MergePatchUpdate keeps allOf on the unpatched base, so an empty merge patch is rejected #11534

Description

Describe the bug

For a model that uses extends, MergePatchUpdate transforms the model's own properties but emits allOf pointing at the unpatched base schema. The inherited properties never go through the transform, so they stay required and non-nullable.

The generated patch schema therefore demands every inherited required property on every request, including on the empty patch. The algorithm in RFC 7396, which this template's doc comment says the transform follows, iterates over the pairs present in the patch document, so {} makes no changes and is always a valid patch. A schema that rejects {} is not describing a merge-patch body.

Versions: @typespec/compiler 1.14.0, @typespec/http 1.14.0, @typespec/openapi3 1.14.0.

Reproduction

import "@typespec/http";

using Http;

@service(#{ title: "Extends repro" })
namespace ExtendsRepro;

model Base {
  id: string;
}

model Widget extends Base {
  label?: string;
}

model WidgetPatch is MergePatchUpdate<Widget>;

@route("/widgets/{id}")
@patch
op update(@path id: string, @body body: WidgetPatch): Widget;

Emitted schemas:

Base:
  type: object
  required: [id]        # <- never transformed
  properties:
    id: { type: string }

WidgetPatch:
  type: object
  properties:
    label:              # <- own property: transformed correctly
      anyOf:
        - type: string
        - type: 'null'
  allOf:
    - $ref: '#/components/schemas/Base'   # <- unpatched base

Validating patch bodies against WidgetPatch:

INVALID  {}                        <- must have required property 'id'
INVALID  {"label":"x"}             <- must have required property 'id'
INVALID  {"label":null}            <- must have required property 'id'
VALID    {"id":"keep","label":"x"}

Expected: inherited properties go through the same transform as the model's own, so id becomes optional and nullable in the patch shape and {} validates.

The same happens when the extending model is used as a nested property rather than as the patch root; the generated *MergePatchUpdateOrCreate schema carries the same allOf to the unpatched base.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions