microsoft / microsoft/typespec
[Bug]: MergePatchUpdate keeps allOf on the unpatched base, so an empty merge patch is rejected
- Dominant language
- Java
- Stars
- 5.9k
- Forks
- 394
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 104
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](https://www.rfc-editor.org/rfc/rfc7396), 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
```tsp
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;
@route("/widgets/{id}")
@patch
op update(@path id: string, @body body: WidgetPatch): Widget;
```
Emitted schemas:
```yaml
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.
Contributor guide
Research direction
Start by reproducing the issue with the provided TypeSpec model and inspect the MergePatchUpdate transform and emitted OpenAPI schemas. Trace how an extending model becomes an allOf reference, then verify that inherited properties receive the same transform as own properties. Done means empty and partial patches validate, including when the extending model is nested.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100