Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: internal
packages:
- "@typespec/http-specs"
- "@typespec/rest"
---

Add suppressions for deprecated behavior
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: deprecation
packages:
- "@typespec/http"
---

Deprecate use of `@patch(#{implicitOptionality: true})`.

Migrate using one of the following patterns depending on intended semantics:

1. Preserve previous behavior with an explicit patch model (optional properties)

```diff lang=typespec
model Pet {
name: string;
age: int32;
}

+ model PetPatch {
+ name?: string;
+ age?: int32;
+ }


- @patch(#{implicitOptionality: true}) op updatePet(@body patch: Pet): void;
+ @patch op updatePet(@body patch: PetPatch): void;
```

2. Use merge-patch semantics explicitly with `MergePatchUpdate<T>`

```typespec
model Pet {
name: string;
age: int32;
}

@patch op updatePet(@body patch: MergePatchUpdate<Pet>): void;
```

Use `MergePatchCreateOrUpdate<T>` when the operation supports create-or-update behavior.
1 change: 1 addition & 0 deletions packages/http-specs/specs/type/model/visibility/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ op headModel(@bodyRoot input: VisibilityModel): OkResponse;
@put
op putModel(@body input: VisibilityModel): void;

#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy test"
@scenario
@scenarioDoc("""
Generate abd send put model with write/update properties.
Expand Down
7 changes: 3 additions & 4 deletions packages/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,10 @@ Specify the HTTP verb for the target operation to be `PATCH`.
@patch op update(pet: Pet): void;
```

###### Using MergePatch template for proper merge-patch semantics

```typespec
// Disable implicit optionality, making the body of the PATCH operation use the
// optionality as defined in the `Pet` model.
@patch(#{ implicitOptionality: false })
op update(pet: Pet): void;
@patch op update(@body pet: MergePatchUpdate<Pet>): void;
```

#### `@path`
Expand Down
7 changes: 2 additions & 5 deletions packages/http/generated-defs/TypeSpec.Http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,9 @@ export type PostDecorator = (
* ```typespec
* @patch op update(pet: Pet): void;
* ```
* @example
* @example Using MergePatch template for proper merge-patch semantics
* ```typespec
* // Disable implicit optionality, making the body of the PATCH operation use the
* // optionality as defined in the `Pet` model.
* @patch(#{ implicitOptionality: false })
* op update(pet: Pet): void;
* @patch op update(@body pet: MergePatchUpdate<Pet>): void;
* ```
*/
export type PatchDecorator = (
Expand Down
13 changes: 8 additions & 5 deletions packages/http/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ model PatchOptions {
/**
* If set to `false`, disables the implicit transform that makes the body of a
* PATCH operation deeply optional.
*
* @deprecated `implicitOptionality` is deprecated and will be removed.
* To preserve the previous behavior, define and use an explicit patch model
* with optional properties for your `@body` parameter.
* For actual JSON Merge Patch behavior, use `MergePatchUpdate<T>` as the
* `@body` type (for example: `@patch op update(@body pet: MergePatchUpdate<Pet>): void;`).
*/
implicitOptionality?: boolean;
}
Expand All @@ -281,12 +287,9 @@ model PatchOptions {
* @patch op update(pet: Pet): void;
* ```
*
* @example
* @example Using MergePatch template for proper merge-patch semantics
* ```typespec
* // Disable implicit optionality, making the body of the PATCH operation use the
* // optionality as defined in the `Pet` model.
* @patch(#{ implicitOptionality: false })
* op update(pet: Pet): void;
* @patch op update(@body pet: MergePatchUpdate<Pet>): void;
* ```
*/
extern dec patch(target: Operation, options?: valueof PatchOptions);
Expand Down
10 changes: 9 additions & 1 deletion packages/http/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,15 @@ export const $patch: PatchDecorator = (
) => {
_patch(context, entity);

if (options) setPatchOptions(context.program, entity, options);
if (options) {
if (options.implicitOptionality === true) {
reportDiagnostic(context.program, {
code: "deprecated-implicit-optionality",
target: entity,
});
}
setPatchOptions(context.program, entity, options);
}
};

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/http/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ export const $lib = createTypeSpecLibrary({
default: paramMessage`The 'contents' property of the file model must be a scalar type that extends 'string' or 'bytes'. Found '${"type"}'.`,
},
},
"deprecated-implicit-optionality": {
severity: "warning",
messages: {
default: `The implicitOptionality option is deprecated. To preserve previous behavior, use an explicit patch model with optional properties. For actual merge-patch semantics, use MergePatchUpdate<T> for the @body type.`,
},
},
"merge-patch-contains-null": {
severity: "error",
messages: {
Expand Down
20 changes: 20 additions & 0 deletions packages/http/test/http-decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ describe("http: decorators", () => {
message: `Argument of type '"/test"' is not assignable to parameter of type 'valueof TypeSpec.Http.PatchOptions'`,
});
});

it(`@patch emits deprecation warning when implicitOptionality: true`, async () => {
const diagnostics = await Tester.diagnose(`
@patch(#{ implicitOptionality: true }) op test(): string;
`);

expectDiagnostics(diagnostics, {
code: "@typespec/http/deprecated-implicit-optionality",
message:
"The implicitOptionality option is deprecated. To preserve previous behavior, use an explicit patch model with optional properties. For actual merge-patch semantics, use MergePatchUpdate<T> for the @body type.",
});
});

it(`@patch does not emit deprecation warning when implicitOptionality: false`, async () => {
const diagnostics = await Tester.diagnose(`
@patch(#{ implicitOptionality: false }) op test(): string;
`);

expectDiagnosticEmpty(diagnostics);
});
});

describe("@header", () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/openapi3/test/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
@visibility(Lifecycle.Read, Lifecycle.Update, Lifecycle.Create) ruc?: string;
}
@parameterVisibility(Lifecycle.Create, Lifecycle.Update)
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@route("/") @patch(#{implicitOptionality: true}) op createOrUpdate(...M): M;
`);

Expand Down Expand Up @@ -176,7 +177,8 @@ worksFor(supportedVersions, ({ openApiFor }) => {
person: Person;
relationship: string;
}
@route("/") @patch(#{implicitOptionality: true}) op update(...Person): Person;
@route("/") #suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op update(...Person): Person;
`);

const response = res.paths["/"].patch.responses["200"].content["application/json"].schema;
Expand Down Expand Up @@ -217,6 +219,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
weight: float64;
}
@post op create(...Widget): void;
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op update(...Widget): void;
`);

Expand Down Expand Up @@ -358,6 +361,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
@get get(...M): M;
@post create(...M): M;
@put createOrUpdate(...M): M;
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) update(...M): M;
@delete delete(...M): void;
}
Expand All @@ -367,6 +371,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
@get get(...D): D;
@post create(...D): D;
@put createOrUpdate(...D): D;
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) update(...D): D;
@delete delete(...D): void;
}
Expand All @@ -376,6 +381,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
@get op get(id: string): R;
@post op create(...R): R;
@put op createOrUpdate(...R): R;
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op update(...R): R;
@delete op delete(...D): void;
}
Expand All @@ -384,6 +390,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
@get op get(id: string): U;
@post op create(...U): U;
@put op createOrUpdate(...U): U;
#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op update(...U): U;
@delete op delete(...U): void;
}
Expand Down Expand Up @@ -1087,6 +1094,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
id: uuid;
}

#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op test(...Bar): Bar;
`);

Expand All @@ -1102,6 +1110,7 @@ worksFor(supportedVersions, ({ openApiFor }) => {
id: string;
}

#suppress "@typespec/http/deprecated-implicit-optionality" "testing legacy behavior"
@patch(#{implicitOptionality: true}) op test(bar: Bar): void;

model Foo {
Expand Down
5 changes: 5 additions & 0 deletions packages/rest/lib/resource.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ interface ResourceCreateOrUpdate<Resource extends {}, Error> {
* @template Resource The resource model to create or update.
* @template Error The error response.
*/
#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy behavior"
@autoRoute
@doc("Creates or update an instance of the resource.")
@createsOrUpdatesResource(Resource)
Expand Down Expand Up @@ -185,6 +186,7 @@ interface ResourceUpdate<Resource extends {}, Error> {
* @template Resource The resource model to update.
* @template Error The error response.
*/
#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy behavior"
@autoRoute
@doc("Updates an existing instance of the resource.")
@updatesResource(Resource)
Expand Down Expand Up @@ -335,6 +337,7 @@ interface SingletonResourceUpdate<Singleton extends {}, Resource extends {}, Err
* @template Singleton The singleton resource model.
* @template Resource The resource model.
*/
#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy behavior"
@autoRoute
@doc("Updates the singleton resource.")
@segmentOf(Singleton)
Expand Down Expand Up @@ -395,6 +398,7 @@ interface ExtensionResourceCreateOrUpdate<Extension extends {}, Resource extends
* @template Extension The extension resource model.
* @template Resource The resource model.
*/
#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy behavior"
@autoRoute
@doc("Creates or update an instance of the extension resource.")
@createsOrUpdatesResource(Extension)
Expand Down Expand Up @@ -443,6 +447,7 @@ interface ExtensionResourceUpdate<Extension extends {}, Resource extends {}, Err
* @template Extension The extension resource model.
* @template Resource The resource model.
*/
#suppress "@typespec/http/deprecated-implicit-optionality" "for legacy behavior"
@autoRoute
@doc("Updates an existing instance of the extension resource.")
@updatesResource(Extension)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,10 @@ Specify the HTTP verb for the target operation to be `PATCH`.
@patch op update(pet: Pet): void;
```

##### Using MergePatch template for proper merge-patch semantics

```typespec
// Disable implicit optionality, making the body of the PATCH operation use the
// optionality as defined in the `Pet` model.
@patch(#{ implicitOptionality: false })
op update(pet: Pet): void;
@patch op update(@body pet: MergePatchUpdate<Pet>): void;
```

### `@path` {#@TypeSpec.Http.path}
Expand Down
Loading