diff --git a/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md b/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md new file mode 100644 index 0000000000..ea29528314 --- /dev/null +++ b/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - "@azure-tools/azure-http-specs" +--- + +Remove use of implicit optionality which was a noop in one operation diff --git a/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md b/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md new file mode 100644 index 0000000000..f3361a2e4c --- /dev/null +++ b/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md @@ -0,0 +1,41 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Removed `implicitOptionality: true` from the `@patch` decorator on the following operation templates: + +- `ArmTagsPatchAsync` +- `ArmResourcePatchAsync` +- `ArmTagsPatchSync` +- `ArmResourcePatchSync` +- `ResourceUpdateSync.update` (in `ResourceUpdateSync` interface) + +Previously, these templates used `@patch(#{ implicitOptionality: true })` which caused all properties in the PATCH request body to be implicitly treated as optional, regardless of how they were declared in the model. This behavior is now removed — properties will retain their declared optionality. + +#### Migration + +If you use these ARM operation templates, the PATCH body will no longer implicitly make all properties optional. In most cases, ARM resources already use `ResourceUpdateModel` which produces the correct optional property envelope — if so, **no changes are needed**. + +If you have a custom patch body that relied on implicit optionality, explicitly mark properties as optional: + +**Before** (implicit optionality made all properties optional automatically): + +```tsp +model MyResourceProperties { + displayName: string; + config: MyConfig; +} +``` + +**After** (explicitly declare optional properties for update): + +```tsp +model MyResourceUpdateProperties { + displayName?: string; + config?: MyConfig; +} +``` + +Or continue using `ResourceUpdateModel` which already handles this transformation for standard ARM resource patterns. diff --git a/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md b/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md new file mode 100644 index 0000000000..b50a0d974d --- /dev/null +++ b/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md @@ -0,0 +1,37 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-azure-core" +--- + +Removed `implicitOptionality: true` from the `@patch` decorator on the following operation templates: + +- `ResourceCreateOrUpdate` +- `LongRunningResourceCreateOrUpdate` +- `ResourceUpdate` + +Previously, these templates used `@patch(#{ implicitOptionality: true })` which caused all properties in the PATCH request body to be implicitly treated as optional, regardless of how they were declared in the model. This behavior is now removed — properties will retain their declared optionality. + +#### Migration + +If your service relies on all properties being optional in the PATCH body, you need to explicitly mark them as optional in your model: + +**Before** (implicit optionality made all properties optional automatically): + +```tsp +model Widget { + name: string; + color: string; +} +``` + +**After** (explicitly declare optional properties): + +```tsp +model WidgetUpdate { + name?: string; + color?: string; +} +``` + +Alternatively, you can use the `ResourceUpdateModel` template or `OptionalProperties` utility to derive an all-optional version of your model for PATCH operations. If you were already using `ResourceUpdateModel` or manually defining optional properties in your update model, no changes are needed. diff --git a/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp b/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp index 7cdb654230..8f68d84a36 100644 --- a/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp +++ b/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp @@ -104,7 +104,7 @@ model UpdateWidgetColorRequest { ``` """) @doc("Update widget color (preview only)") -@patch(#{ implicitOptionality: true }) +@patch @route("/widgets/{id}/color") @added(ApiVersions.v2024_12_01_preview) op updateWidgetColor( diff --git a/packages/typespec-azure-core/lib/operations.tsp b/packages/typespec-azure-core/lib/operations.tsp index 1244533695..09af9cf371 100644 --- a/packages/typespec-azure-core/lib/operations.tsp +++ b/packages/typespec-azure-core/lib/operations.tsp @@ -155,12 +155,10 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("ResourceCreateOrUpdate", "PATCH") @createsOrUpdatesResource(Resource) @parameterVisibility(Lifecycle.Create, Lifecycle.Update) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch ResourceCreateOrUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} @@ -193,12 +191,10 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("LongRunningResourceCreateOrUpdate", "PATCH") @createsOrUpdatesResource(Resource) @parameterVisibility(Lifecycle.Create, Lifecycle.Update) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch LongRunningResourceCreateOrUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} @@ -232,10 +228,9 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("ResourceUpdate", "PATCH") @updatesResource(Resource) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch ResourceUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} diff --git a/packages/typespec-azure-resource-manager/lib/extension/operations.tsp b/packages/typespec-azure-resource-manager/lib/extension/operations.tsp index d653da0bda..52c20b64c6 100644 --- a/packages/typespec-azure-resource-manager/lib/extension/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/extension/operations.tsp @@ -232,7 +232,7 @@ op CreateOrReplaceAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, @@ -271,7 +271,7 @@ op CustomPatchAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/interfaces.tsp b/packages/typespec-azure-resource-manager/lib/interfaces.tsp index abb4be4578..1e7fb305b5 100644 --- a/packages/typespec-azure-resource-manager/lib/interfaces.tsp +++ b/packages/typespec-azure-resource-manager/lib/interfaces.tsp @@ -326,8 +326,7 @@ interface ResourceUpdateSync< * @template Properties RP-specific property bag for the resource * @template BaseParameters The http parameters that are part of the request */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - @patch(#{ implicitOptionality: true }) + @patch update is ArmCustomPatchSync, BaseParameters>; } diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp index aca5aa6950..c3b46748b3 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp @@ -150,7 +150,7 @@ op CreateOrReplaceSync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, @@ -193,7 +193,7 @@ op CustomPatchAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp index a458beee95..978419cb24 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp @@ -107,7 +107,7 @@ interface ExtensionOperations< @armOperationRoute(OperationOptions) @doc("Update a {name}", Resource) @legacyExtensionResourceOperation(Resource, "update", OverrideResourceName) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -146,7 +146,7 @@ interface ExtensionOperations< @armOperationRoute(OperationOptions) @doc("Update a {name}", Resource) @legacyExtensionResourceOperation(Resource, "update", OverrideResourceName) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp index 7e8eae33e8..3c65dd08a9 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp @@ -135,7 +135,7 @@ interface RoutedOperations< @armOperationRoute(OverrideRouteOptions) @legacyResourceOperation(Resource, "update", OverrideResourceName) @Private.armUpdateProviderNamespace - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -175,7 +175,7 @@ interface RoutedOperations< @armOperationRoute(OverrideRouteOptions) @legacyResourceOperation(Resource, "update", OverrideResourceName) @Private.armUpdateProviderNamespace - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -464,7 +464,7 @@ model ProviderParameter { @doc("Update a {name}", Resource) @armResourceUpdate(Resource) @Private.enforceConstraint(Resource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends TypeSpec.Reflection.Model | void = TagsUpdateModel, @@ -503,7 +503,7 @@ op CustomPatchAsync< @doc("Update a {name}", Resource) @armResourceUpdate(Resource) @Private.enforceConstraint(Resource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends TypeSpec.Reflection.Model | void = TagsUpdateModel, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp index 54d72bd67d..6253eb206b 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp @@ -132,7 +132,7 @@ op CreateOrReplaceAsync< @doc("Update a {name} PrivateEndpointConnection", ParentResource) @armResourceUpdate(Resource) @Private.enforceConstraint(ParentResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource, @@ -175,7 +175,7 @@ op CustomPatchAsync< @doc("Update a {name} PrivateEndpointConnection", ParentResource) @armResourceUpdate(Resource) @Private.enforceConstraint(ParentResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource, diff --git a/packages/typespec-azure-resource-manager/lib/operations.tsp b/packages/typespec-azure-resource-manager/lib/operations.tsp index b012624bcd..fe68408508 100644 --- a/packages/typespec-azure-resource-manager/lib/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/operations.tsp @@ -342,8 +342,7 @@ op ArmResourceCreateOrReplaceAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmTagsPatchAsync< Resource extends Foundations.SimpleResource, @@ -377,8 +376,7 @@ op ArmTagsPatchAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmResourcePatchAsync< Resource extends Foundations.SimpleResource, @@ -417,7 +415,7 @@ op ArmResourcePatchAsync< @autoRoute @doc("Update a {name}", Resource) @armResourceUpdate(Resource) -@patch(#{ implicitOptionality: false }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmCustomPatchAsync< Resource extends Foundations.SimpleResource, @@ -449,8 +447,7 @@ op ArmCustomPatchAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmTagsPatchSync< Resource extends Foundations.SimpleResource, @@ -475,8 +472,7 @@ op ArmTagsPatchSync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmResourcePatchSync< Resource extends Foundations.SimpleResource, @@ -507,7 +503,7 @@ op ArmResourcePatchSync< @autoRoute @doc("Update a {name}", Resource) @armResourceUpdate(Resource) -@patch(#{ implicitOptionality: false }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmCustomPatchSync< Resource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp b/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp index 81ad660f00..51513c71d9 100644 --- a/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp +++ b/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp @@ -244,7 +244,7 @@ interface PrivateEndpoints< @doc("Update a {name} PrivateEndpointConnection", ParentResource) @builtInResourceOperation(ParentResource, Resource, "update", OverrideResourceName) @Private.enforceConstraint(ParentResource, Foundations.Resource) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource = PrivateEndpointResource, @@ -286,7 +286,7 @@ interface PrivateEndpoints< @doc("Update a {name PrivateEndpointConnection}", ParentResource) @builtInResourceOperation(ParentResource, Resource, "update", OverrideResourceName) @Private.enforceConstraint(ParentResource, Foundations.Resource) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource = PrivateEndpointResource,