diff --git a/docs/libraries/azure-resource-manager/reference/interfaces.md b/docs/libraries/azure-resource-manager/reference/interfaces.md index 611374846..c5a53c998 100644 --- a/docs/libraries/azure-resource-manager/reference/interfaces.md +++ b/docs/libraries/azure-resource-manager/reference/interfaces.md @@ -672,7 +672,7 @@ op Azure.ResourceManager.ArmListBySubscription(apiVersion: string, subscriptionI ### `ArmProviderActionAsync` {#Azure.ResourceManager.ArmProviderActionAsync} ```typespec -op Azure.ResourceManager.ArmProviderActionAsync(apiVersion: string, subscriptionId: Azure.Core.uuid, location: string, resourceGroupName: string, resourceUri: string, provider: "Microsoft.ThisWillBeReplaced", body: Request): Response | Azure.ResourceManager.CommonTypes.ErrorResponse +op Azure.ResourceManager.ArmProviderActionAsync(apiVersion: string, subscriptionId: Azure.Core.uuid, location: string, resourceGroupName: string, resourceUri: string, provider: "Microsoft.ThisWillBeReplaced", body: Request): Azure.ResourceManager.ArmAcceptedLroResponse | Response | Azure.ResourceManager.CommonTypes.ErrorResponse ``` #### Template Parameters diff --git a/packages/samples/specs/resource-manager/operations/operation-provider/main.tsp b/packages/samples/specs/resource-manager/operations/operation-provider/main.tsp index 1e8c49a9c..acb6484e3 100644 --- a/packages/samples/specs/resource-manager/operations/operation-provider/main.tsp +++ b/packages/samples/specs/resource-manager/operations/operation-provider/main.tsp @@ -48,19 +48,13 @@ model LogAnalyticsCollection is Page; interface ProviderOperations { /** Operation to get virtual machines for subscription (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/getVmSizes) */ @get - getVmSizes is ArmProviderActionAsync< - Response = VmSizeCollection, - Scope = SubscriptionActionScope - >; + getVmSizes is ArmProviderActionSync; /** Operation to get virtual machines for tenant (/providers/Microsoft.ContosoProviderHub/getVmSizesTenant) */ @get - getVmSizesTenant is ArmProviderActionAsync< - Response = VmSizeCollection, - Scope = TenantActionScope - >; + getVmSizesTenant is ArmProviderActionSync; /** Operation to get virtual machines for subscription for specific location (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/locations/{location}/getVmSizesLocation) */ @get - getVmSizesLocation is ArmProviderActionAsync< + getVmSizesLocation is ArmProviderActionSync< Response = VmSizeCollection, Scope = SubscriptionActionScope, Parameters = LocationParameter @@ -68,15 +62,36 @@ interface ProviderOperations { /** Operation to get throttled requests sharing action (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/logAnalytics/apiAccess/getThrottledRequests) */ @get @action("logAnalytics/apiAccess/getThrottledRequests") - getThrottledRequestsSubscription is ArmProviderActionAsync< + getThrottledRequestsSubscription is ArmProviderActionSync< Response = LogAnalyticsCollection, Scope = SubscriptionActionScope >; /** Operation to get throttled requests sharing action for tenant (/providers/Microsoft.ContosoProviderHub/logAnalytics/apiAccess/getThrottledRequests) */ @get @action("logAnalytics/apiAccess/getThrottledRequests") - getThrottledRequestsTenant is ArmProviderActionAsync< + getThrottledRequestsTenant is ArmProviderActionSync< Response = LogAnalyticsCollection, Scope = TenantActionScope >; + /** Operation to post virtual machines for subscription (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizes) */ + postVmSizes is ArmProviderActionAsync< + Response = LogAnalyticsCollection, + Scope = SubscriptionActionScope + >; + /** Operation to post virtual machines for subscription for specific location (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/locations/{location}/postVmSizesLocation) */ + postVmSizesLocation is ArmProviderActionAsync< + Response = LogAnalyticsCollection, + Scope = SubscriptionActionScope, + Parameters = LocationParameter + >; + /** Operation to post virtual machines for subscription with retry after header (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizesRetry) */ + postVmSizesRetry is ArmProviderActionAsync< + Response = Azure.Core.Foundations.RetryAfterHeader, + Scope = SubscriptionActionScope + >; + /** Operation to post virtual machines for subscription with ARM combined header (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizesArmCombined) */ + postVmSizesArmCombined is ArmProviderActionAsync< + Response = ArmCombinedLroHeaders, + Scope = SubscriptionActionScope + >; } diff --git a/packages/samples/test/output/azure/resource-manager/operations/operation-provider/@azure-tools/typespec-autorest/2022-11-01-preview/openapi.json b/packages/samples/test/output/azure/resource-manager/operations/operation-provider/@azure-tools/typespec-autorest/2022-11-01-preview/openapi.json index 53a68f634..94f6986fc 100644 --- a/packages/samples/test/output/azure/resource-manager/operations/operation-provider/@azure-tools/typespec-autorest/2022-11-01-preview/openapi.json +++ b/packages/samples/test/output/azure/resource-manager/operations/operation-provider/@azure-tools/typespec-autorest/2022-11-01-preview/openapi.json @@ -210,6 +210,61 @@ } } }, + "/subscriptions/{subscriptionId}/providers/Microsoft.OperationStatusSample/locations/{location}/postVmSizesLocation": { + "post": { + "operationId": "ProviderOperations_PostVmSizesLocation", + "tags": [ + "ProviderOperations" + ], + "description": "Operation to post virtual machines for subscription for specific location (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/locations/{location}/postVmSizesLocation)", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/LocationParameter" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/LogAnalyticsCollection" + } + }, + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, "/subscriptions/{subscriptionId}/providers/Microsoft.OperationStatusSample/logAnalytics/apiAccess/getThrottledRequests": { "get": { "operationId": "ProviderOperations_GetThrottledRequestsSubscription", @@ -243,9 +298,179 @@ "nextLinkName": "nextLink" } } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.OperationStatusSample/postVmSizes": { + "post": { + "operationId": "ProviderOperations_PostVmSizes", + "tags": [ + "ProviderOperations" + ], + "description": "Operation to post virtual machines for subscription (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizes)", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/LogAnalyticsCollection" + } + }, + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.OperationStatusSample/postVmSizesArmCombined": { + "post": { + "operationId": "ProviderOperations_PostVmSizesArmCombined", + "tags": [ + "ProviderOperations" + ], + "description": "Operation to post virtual machines for subscription with ARM combined header (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizesArmCombined)", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "headers": { + "Azure-AsyncOperation": { + "type": "string", + "format": "uri", + "description": "A link to the status monitor" + }, + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + } + } + }, + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.OperationStatusSample/postVmSizesRetry": { + "post": { + "operationId": "ProviderOperations_PostVmSizesRetry", + "tags": [ + "ProviderOperations" + ], + "description": "Operation to post virtual machines for subscription with retry after header (/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/postVmSizesRetry)", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "headers": { + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } } }, "definitions": { + "Azure.Core.Foundations.RetryAfterHeader": { + "type": "object", + "description": "The retry-after envelope." + }, + "Azure.ResourceManager.ArmCombinedLroHeaders, void, TypeSpec.Rest.ResourceLocation, string>": { + "type": "object", + "description": "Provide Both Azure-AsyncOperation and Location headers" + }, "LogAnalyticsCollection": { "type": "object", "description": "LogAnalytics collection of operation status response", diff --git a/packages/typespec-azure-resource-manager/CHANGELOG.md b/packages/typespec-azure-resource-manager/CHANGELOG.md index 9d8d24890..6b53dec9a 100644 --- a/packages/typespec-azure-resource-manager/CHANGELOG.md +++ b/packages/typespec-azure-resource-manager/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log - @azure-tools/typespec-azure-resource-manager +## 0.47.1 + +### Bug Fixes + +- [#1673](https://github.com/Azure/typespec-azure/pull/1673) Fix `ArmProviderActionAsync` to correctly return 202 responses. + + ## 0.47.0 ### Bug Fixes diff --git a/packages/typespec-azure-resource-manager/lib/operations.tsp b/packages/typespec-azure-resource-manager/lib/operations.tsp index 9deaa0bb0..53ddba5a1 100644 --- a/packages/typespec-azure-resource-manager/lib/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/operations.tsp @@ -752,4 +752,4 @@ op ArmProviderActionAsync< /** The request body */ @bodyRoot body: Request, -): Response | ErrorResponse; +): ArmAcceptedLroResponse<"Resource operation accepted.", LroHeaders> | Response | ErrorResponse; diff --git a/packages/typespec-azure-resource-manager/package.json b/packages/typespec-azure-resource-manager/package.json index 516c866aa..6ebf391f8 100644 --- a/packages/typespec-azure-resource-manager/package.json +++ b/packages/typespec-azure-resource-manager/package.json @@ -1,6 +1,6 @@ { "name": "@azure-tools/typespec-azure-resource-manager", - "version": "0.47.0", + "version": "0.47.1", "author": "Microsoft Corporation", "description": "TypeSpec Azure Resource Manager library", "homepage": "https://azure.github.io/typespec-azure", diff --git a/packages/typespec-client-generator-core/CHANGELOG.md b/packages/typespec-client-generator-core/CHANGELOG.md index 5b9c1ebea..e220928f9 100644 --- a/packages/typespec-client-generator-core/CHANGELOG.md +++ b/packages/typespec-client-generator-core/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log - @azure-tools/typespec-client-generator-core +## 0.47.2 + +### Bug Fixes + +- [#1606](https://github.com/Azure/typespec-azure/pull/1606) overwrite original value when set multiple value for same decorator + + ## 0.47.1 ### Bug Fixes diff --git a/packages/typespec-client-generator-core/package.json b/packages/typespec-client-generator-core/package.json index 34c85b083..b52b05b01 100644 --- a/packages/typespec-client-generator-core/package.json +++ b/packages/typespec-client-generator-core/package.json @@ -1,6 +1,6 @@ { "name": "@azure-tools/typespec-client-generator-core", - "version": "0.47.1", + "version": "0.47.2", "author": "Microsoft Corporation", "description": "TypeSpec Data Plane Generation library", "homepage": "https://azure.github.io/typespec-azure", diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index d1e8be528..12e1e7ad4 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -28,7 +28,6 @@ import { isTemplateDeclarationOrInstance, listServices, projectProgram, - validateDecoratorUniqueOnNode, } from "@typespec/compiler"; import { buildVersionProjections, getVersions } from "@typespec/versioning"; import { @@ -108,36 +107,27 @@ function setScopedDecoratorData( target: Type, value: unknown, scope?: LanguageScopes, - transitivity: boolean = false, -): boolean { +) { + // if no scope specified, then set with the new value + if (!scope) { + context.program.stateMap(key).set(target, Object.fromEntries([[AllScopes, value]])); + return; + } + + // if scope specified, create or overwrite with the new value + const splitScopes = scope.split(",").map((s) => s.trim()); const targetEntry = context.program.stateMap(key).get(target); - const splitScopes = scope?.split(",").map((s) => s.trim()) || [AllScopes]; - // If target doesn't exist in decorator map, create a new entry + // if target doesn't exist in decorator map, create a new entry if (!targetEntry) { const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); context.program.stateMap(key).set(target, newObject); - return true; + return; } - // If target exists, but there's a specified scope and it doesn't exist in the target entry, add mapping of scope and value to target entry - const scopes = Reflect.ownKeys(targetEntry); - if (!scopes.includes(AllScopes) && scope && !splitScopes.some((s) => scopes.includes(s))) { - const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); - context.program.stateMap(key).set(target, { ...targetEntry, ...newObject }); - return true; - } - // we only want to allow multiple decorators if they each specify a different scope - if (!transitivity) { - validateDecoratorUniqueOnNode(context, target, decorator); - return false; - } - // for transitivity situation, we could allow scope extension - if (!scopes.includes(AllScopes) && !scope) { - const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); - context.program.stateMap(key).set(target, { ...targetEntry, ...newObject }); - } - return false; + // if target exists, overwrite existed value + const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); + context.program.stateMap(key).set(target, { ...targetEntry, ...newObject }); } const clientKey = createStateSymbol("client"); diff --git a/packages/typespec-client-generator-core/src/lib.ts b/packages/typespec-client-generator-core/src/lib.ts index 60fcac220..828dcfa04 100644 --- a/packages/typespec-client-generator-core/src/lib.ts +++ b/packages/typespec-client-generator-core/src/lib.ts @@ -210,6 +210,12 @@ export const $lib = createTypeSpecLibrary({ default: `@usage override conflicts with the usage calculated from operation or other @usage override.`, }, }, + "duplicate-decorator": { + severity: "warning", + messages: { + default: paramMessage`Decorator ${"decoratorName"} cannot be used twice on the same declaration with same scope.`, + }, + }, }, }); diff --git a/packages/typespec-client-generator-core/test/decorators.test.ts b/packages/typespec-client-generator-core/test/decorators.test.ts index 666145f0b..511d1cab8 100644 --- a/packages/typespec-client-generator-core/test/decorators.test.ts +++ b/packages/typespec-client-generator-core/test/decorators.test.ts @@ -272,52 +272,111 @@ describe("typespec-client-generator-core: decorators", () => { strictEqual(getAccess(runnerWithJava.context, funcJava), "internal"); }); - it("duplicate-decorator diagnostic for first non-scoped decorator then scoped decorator", async () => { - const diagnostics = await runner.diagnose(` - @test - @access(Access.internal) - @access(Access.internal, "csharp") - op func( - @query("createdAt") - createdAt: utcDateTime; - ): void; - `); + it("first non-scoped decorator then scoped decorator", async () => { + const code = ` + @test + @access(Access.public, "csharp") + @access(Access.internal) + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; + `; - expectDiagnostics(diagnostics, { - code: "duplicate-decorator", + const { func } = (await runner.compile(code)) as { func: Operation }; + strictEqual(getAccess(runner.context, func), "internal"); + + const runnerWithCsharp = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", }); + const { func: funcCsharp } = (await runnerWithCsharp.compile(code)) as { func: Operation }; + strictEqual(getAccess(runnerWithCsharp.context, funcCsharp), "public"); }); - it("duplicate-decorator diagnostic for first scoped decorator then non-scoped decorator", async () => { - const diagnostics = await runner.diagnose(` - @test - @access(Access.internal, "csharp") - @access(Access.internal) - op func( - @query("createdAt") - createdAt: utcDateTime; - ): void; - `); + it("first scoped decorator then non-scoped decorator", async () => { + const code = ` + @test + @access(Access.internal) + @access(Access.public, "csharp") + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; + `; - expectDiagnostics(diagnostics, { - code: "duplicate-decorator", + const { func } = (await runner.compile(code)) as { func: Operation }; + strictEqual(getAccess(runner.context, func), "internal"); + + const runnerWithCsharp = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", }); + const { func: funcCsharp } = (await runnerWithCsharp.compile(code)) as { func: Operation }; + strictEqual(getAccess(runnerWithCsharp.context, funcCsharp), "internal"); }); - it("duplicate-decorator diagnostic for multiple same scope", async () => { - const diagnostics = await runner.diagnose(` - @test - @access(Access.internal, "csharp") - @access(Access.internal, "csharp") - op func( - @query("createdAt") - createdAt: utcDateTime; - ): void; - `); + it("first non-scoped augmented decorator then scoped augmented decorator", async () => { + const code = ` + @test + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; - expectDiagnostics(diagnostics, { - code: "duplicate-decorator", + @@access(func, Access.public); + @@access(func, Access.internal, "csharp"); + `; + + const { func } = (await runner.compile(code)) as { func: Operation }; + strictEqual(getAccess(runner.context, func), "public"); + + const runnerWithCsharp = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", + }); + const { func: funcCsharp } = (await runnerWithCsharp.compile(code)) as { func: Operation }; + strictEqual(getAccess(runnerWithCsharp.context, funcCsharp), "internal"); + }); + + it("first scoped augmented decorator then non-scoped augmented decorator", async () => { + const code = ` + @test + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; + + @@access(func, Access.internal, "csharp"); + @@access(func, Access.public); + `; + + const { func } = (await runner.compile(code)) as { func: Operation }; + strictEqual(getAccess(runner.context, func), "public"); + + const runnerWithCsharp = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", + }); + const { func: funcCsharp } = (await runnerWithCsharp.compile(code)) as { func: Operation }; + strictEqual(getAccess(runnerWithCsharp.context, funcCsharp), "public"); + }); + + it("two scoped decorator", async () => { + const code = ` + @test + @access(Access.internal, "csharp") + @access(Access.internal, "python") + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; + `; + + const { func } = (await runner.compile(code)) as { func: Operation }; + strictEqual(getAccess(runner.context, func), "internal"); + + const runnerWithCsharp = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", }); + const { func: funcCsharp } = (await runnerWithCsharp.compile(code)) as { func: Operation }; + strictEqual(getAccess(runnerWithCsharp.context, funcCsharp), "internal"); }); it("csv scope list", async () => { @@ -379,22 +438,6 @@ describe("typespec-client-generator-core: decorators", () => { const { Test: TestJava } = (await javaRunner.compile(testCode)) as { Test: Model }; strictEqual(getAccess(javaRunner.context, TestJava), "public"); }); - - it("duplicate-decorator diagnostic for csv scope list", async () => { - const diagnostics = await runner.diagnose(` - @test - @access(Access.internal, "csharp,ts") - @access(Access.internal, "csharp") - op func( - @query("createdAt") - createdAt: utcDateTime; - ): void; - `); - - expectDiagnostics(diagnostics, { - code: "duplicate-decorator", - }); - }); }); describe("@flattenProperty", () => {