From b0405a2e823032516d61d4be8003344c8a0b468e Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 17:08:42 -0700 Subject: [PATCH 1/7] Implement x-ms-external-resource using @Legacy.armResourceExternal decorator --- packages/typespec-autorest/src/openapi.ts | 4 ++++ .../test/arm/resources.test.ts | 19 +++++++++++++++++++ .../typespec-azure-resource-manager/README.md | 18 ++++++++++++++++++ .../Azure.ResourceManager.Legacy.ts | 8 ++++++++ .../lib/Legacy/decorator.tsp | 6 ++++++ .../src/resource.ts | 19 ++++++++++++++++++- .../src/state.ts | 1 + .../src/tsp-index.ts | 2 ++ .../reference/decorators.md | 17 +++++++++++++++++ .../reference/index.mdx | 1 + 10 files changed, 94 insertions(+), 1 deletion(-) diff --git a/packages/typespec-autorest/src/openapi.ts b/packages/typespec-autorest/src/openapi.ts index cced691284..bd436a2973 100644 --- a/packages/typespec-autorest/src/openapi.ts +++ b/packages/typespec-autorest/src/openapi.ts @@ -17,6 +17,7 @@ import { getArmKeyIdentifiers, getExternalTypeRef, isArmCommonType, + isArmExternalResource, isArmProviderNamespace, isAzureResource, isConditionallyFlattened, @@ -2134,6 +2135,9 @@ export async function getOpenAPIForService( if (getAsEmbeddingVector(program, type as Model) !== undefined) { emitObject["x-ms-embedding-vector"] = true; } + if (type.kind === "Model" && isArmExternalResource(program, type)) { + emitObject["x-ms-external"] = true; + } if (type.kind === "Scalar") { const ext = getArmResourceIdentifierConfig(program, type); if (ext) { diff --git a/packages/typespec-autorest/test/arm/resources.test.ts b/packages/typespec-autorest/test/arm/resources.test.ts index e5c134a88b..f9f6fa0615 100644 --- a/packages/typespec-autorest/test/arm/resources.test.ts +++ b/packages/typespec-autorest/test/arm/resources.test.ts @@ -338,6 +338,25 @@ it("emits x-ms-azure-resource for resource with @azureResourceBase", async () => ok(openApi.definitions?.Widget["x-ms-azure-resource"]); }); +it("emits x-ms-external for resource with @armExternalResource", async () => { + const openApi = await compileOpenAPI( + ` + @armProviderNamespace + @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + namespace Microsoft.Contoso; + + #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "legacy test" + @doc("Widget resource") + @Azure.ResourceManager.Legacy.armExternalResource + model Widget { + name: string; + } +`, + { preset: "azure" }, + ); + ok(openApi.definitions?.Widget["x-ms-external"]); +}); + it("excludes properties marked @invisible from the resource payload", async () => { const openApi = await compileOpenAPI( ` diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 72e20817a3..20a34dd09b 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -541,10 +541,28 @@ This allows sharing Azure Resource Manager resource types across specifications ### Azure.ResourceManager.Legacy +- [`@armExternalResource`](#@armexternalresource) - [`@armOperationRoute`](#@armoperationroute) - [`@customAzureResource`](#@customazureresource) - [`@externalTypeRef`](#@externaltyperef) +#### `@armExternalResource` + +Signifies that a Resource is represented using a library type in generated SDKs. + +```typespec +@Azure.ResourceManager.Legacy.armExternalResource +``` + +##### Target + +The model to that is an external resource +`Model` + +##### Parameters + +None + #### `@armOperationRoute` Signifies that an operation is an Azure Resource Manager operation diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts index e6f45ca7ac..34ea29fc89 100644 --- a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts @@ -35,8 +35,16 @@ export type ArmOperationRouteDecorator = ( route?: ArmOperationOptions, ) => void; +/** + * Signifies that a Resource is represented using a library type in generated SDKs. + * + * @param target The model to that is an external resource + */ +export type ArmExternalResourceDecorator = (context: DecoratorContext, target: Model) => void; + export type AzureResourceManagerLegacyDecorators = { customAzureResource: CustomAzureResourceDecorator; externalTypeRef: ExternalTypeRefDecorator; armOperationRoute: ArmOperationRouteDecorator; + armExternalResource: ArmExternalResourceDecorator; }; diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp index 58bc68a2fe..a0c260bab5 100644 --- a/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp +++ b/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp @@ -31,3 +31,9 @@ extern dec externalTypeRef(entity: Model | ModelProperty, jsonRef: valueof strin * @param route Optional route to associate with the operation */ extern dec armOperationRoute(target: Operation, route?: valueof ArmOperationOptions); + +/** + * Signifies that a Resource is represented using a library type in generated SDKs. + * @param target The model to that is an external resource + */ +extern dec armExternalResource(target: Model); diff --git a/packages/typespec-azure-resource-manager/src/resource.ts b/packages/typespec-azure-resource-manager/src/resource.ts index 6dd9b80718..08f2af93c1 100644 --- a/packages/typespec-azure-resource-manager/src/resource.ts +++ b/packages/typespec-azure-resource-manager/src/resource.ts @@ -23,6 +23,7 @@ import { import { useStateMap } from "@typespec/compiler/utils"; import { getHttpOperation, isPathParam } from "@typespec/http"; import { $autoRoute, getParentResource, getSegment } from "@typespec/rest"; + import { ArmProviderNameValueDecorator, ArmResourceOperationsDecorator, @@ -37,7 +38,10 @@ import { SubscriptionResourceDecorator, TenantResourceDecorator, } from "../generated-defs/Azure.ResourceManager.js"; -import { CustomAzureResourceDecorator } from "../generated-defs/Azure.ResourceManager.Legacy.js"; +import { + ArmExternalResourceDecorator, + CustomAzureResourceDecorator, +} from "../generated-defs/Azure.ResourceManager.Legacy.js"; import { reportDiagnostic } from "./lib.js"; import { getArmProviderNamespace, @@ -80,6 +84,19 @@ export interface ArmResourceDetailsBase { typespecType: Model; } +export const [isArmExternalResource, setArmExternalResource] = useStateMap( + ArmStateKeys.armExternalResource, +); + +export const $armExternalResource: ArmExternalResourceDecorator = ( + context: DecoratorContext, + entity: Model, +) => { + const { program } = context; + if (isTemplateDeclaration(entity)) return; + setArmExternalResource(program, entity, true); +}; + /** Details for RP resources */ export interface ArmResourceDetails extends ArmResourceDetailsBase { /** The set of lifecycle operations and actions for the resource */ diff --git a/packages/typespec-azure-resource-manager/src/state.ts b/packages/typespec-azure-resource-manager/src/state.ts index 9f65e0cd5f..92bc7e2d7f 100644 --- a/packages/typespec-azure-resource-manager/src/state.ts +++ b/packages/typespec-azure-resource-manager/src/state.ts @@ -37,4 +37,5 @@ export const ArmStateKeys = { armCommonParameters: azureResourceManagerCreateStateSymbol("armCommonParameters"), armCommonTypesVersions: azureResourceManagerCreateStateSymbol("armCommonTypesVersions"), armResourceRoute: azureResourceManagerCreateStateSymbol("armResourceRoute"), + armExternalResource: azureResourceManagerCreateStateSymbol("armExternalResource"), }; diff --git a/packages/typespec-azure-resource-manager/src/tsp-index.ts b/packages/typespec-azure-resource-manager/src/tsp-index.ts index 14cc4b9ee8..e2ad6b70db 100644 --- a/packages/typespec-azure-resource-manager/src/tsp-index.ts +++ b/packages/typespec-azure-resource-manager/src/tsp-index.ts @@ -14,6 +14,7 @@ import { $armResourceUpdate, } from "./operations.js"; import { + $armExternalResource, $armProviderNameValue, $armResourceOperations, $armVirtualResource, @@ -60,6 +61,7 @@ export const $decorators = { customAzureResource: $customAzureResource, externalTypeRef: $externalTypeRef, armOperationRoute: $armOperationRoute, + armExternalResource: $armExternalResource, } satisfies AzureResourceManagerLegacyDecorators, }; diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md index 9da665a3b7..6092a155de 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md @@ -458,6 +458,23 @@ This allows sharing Azure Resource Manager resource types across specifications ## Azure.ResourceManager.Legacy +### `@armExternalResource` {#@Azure.ResourceManager.Legacy.armExternalResource} + +Signifies that a Resource is represented using a library type in generated SDKs. + +```typespec +@Azure.ResourceManager.Legacy.armExternalResource +``` + +#### Target + +The model to that is an external resource +`Model` + +#### Parameters + +None + ### `@armOperationRoute` {#@Azure.ResourceManager.Legacy.armOperationRoute} Signifies that an operation is an Azure Resource Manager operation diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx index 120d02fb24..c7267072f3 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx @@ -314,6 +314,7 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager ### Decorators +- [`@armExternalResource`](./decorators.md#@Azure.ResourceManager.Legacy.armExternalResource) - [`@armOperationRoute`](./decorators.md#@Azure.ResourceManager.Legacy.armOperationRoute) - [`@customAzureResource`](./decorators.md#@Azure.ResourceManager.Legacy.customAzureResource) - [`@externalTypeRef`](./decorators.md#@Azure.ResourceManager.Legacy.externalTypeRef) From e09b9f25190847fbf13f2a7623847c2ca506fc35 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 17:12:31 -0700 Subject: [PATCH 2/7] Update changelog --- .chronus/changes/ext-cut-2025-7-11-17-12-14.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/ext-cut-2025-7-11-17-12-14.md diff --git a/.chronus/changes/ext-cut-2025-7-11-17-12-14.md b/.chronus/changes/ext-cut-2025-7-11-17-12-14.md new file mode 100644 index 0000000000..e8a90f73ba --- /dev/null +++ b/.chronus/changes/ext-cut-2025-7-11-17-12-14.md @@ -0,0 +1,8 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-autorest" + - "@azure-tools/typespec-azure-resource-manager" +--- + +Add support for x-ms-external through armExternalResource decorator \ No newline at end of file From 0c22a43109d2d626701635d6bc3d2f58f3a94d5f Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 17:43:08 -0700 Subject: [PATCH 3/7] Fix minor issue with getter for external resource --- packages/typespec-autorest/src/openapi.ts | 8 ++++++-- .../test/arm/resources.test.ts | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/typespec-autorest/src/openapi.ts b/packages/typespec-autorest/src/openapi.ts index bd436a2973..2a5682aa0a 100644 --- a/packages/typespec-autorest/src/openapi.ts +++ b/packages/typespec-autorest/src/openapi.ts @@ -21,6 +21,7 @@ import { isArmProviderNamespace, isAzureResource, isConditionallyFlattened, + isCustomAzureResource, } from "@azure-tools/typespec-azure-resource-manager"; import { getClientNameOverride, @@ -2129,13 +2130,16 @@ export async function getOpenAPIForService( function attachExtensions(type: Type, emitObject: any) { // Attach any OpenAPI extensions const extensions = getExtensions(program, type); - if (isAzureResource(program, type as Model)) { + if ( + type.kind === "Model" && + (isAzureResource(program, type) || isCustomAzureResource(program, type)) + ) { emitObject["x-ms-azure-resource"] = true; } if (getAsEmbeddingVector(program, type as Model) !== undefined) { emitObject["x-ms-embedding-vector"] = true; } - if (type.kind === "Model" && isArmExternalResource(program, type)) { + if (type.kind === "Model" && isArmExternalResource(program, type) === true) { emitObject["x-ms-external"] = true; } if (type.kind === "Scalar") { diff --git a/packages/typespec-autorest/test/arm/resources.test.ts b/packages/typespec-autorest/test/arm/resources.test.ts index f9f6fa0615..1c59c28276 100644 --- a/packages/typespec-autorest/test/arm/resources.test.ts +++ b/packages/typespec-autorest/test/arm/resources.test.ts @@ -357,6 +357,25 @@ it("emits x-ms-external for resource with @armExternalResource", async () => { ok(openApi.definitions?.Widget["x-ms-external"]); }); +it("emits x-ms-azure-resource for resource with @customAzureResource", async () => { + const openApi = await compileOpenAPI( + ` + @armProviderNamespace + @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + namespace Microsoft.Contoso; + + #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "legacy test" + @doc("Widget resource") + @Azure.ResourceManager.Legacy.customAzureResource + model Widget { + name: string; + } +`, + { preset: "azure" }, + ); + ok(openApi.definitions?.Widget["x-ms-azure-resource"]); +}); + it("excludes properties marked @invisible from the resource payload", async () => { const openApi = await compileOpenAPI( ` From 7d5f5ab30db21cce657c8c596ba8b046555d2009 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 17:44:32 -0700 Subject: [PATCH 4/7] Updating changelog --- .chronus/changes/ext-cut-2025-7-11-17-44-13.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/ext-cut-2025-7-11-17-44-13.md diff --git a/.chronus/changes/ext-cut-2025-7-11-17-44-13.md b/.chronus/changes/ext-cut-2025-7-11-17-44-13.md new file mode 100644 index 0000000000..2a326200b8 --- /dev/null +++ b/.chronus/changes/ext-cut-2025-7-11-17-44-13.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-autorest" +--- + +Add support for x-ms-azure-resource extension for custom resources \ No newline at end of file From a1699d692173755fc505fe0a8458cbe94f42e8d3 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 18:35:01 -0700 Subject: [PATCH 5/7] Fix truthiness bug --- packages/typespec-autorest/src/openapi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-autorest/src/openapi.ts b/packages/typespec-autorest/src/openapi.ts index 2a5682aa0a..be7468e7e6 100644 --- a/packages/typespec-autorest/src/openapi.ts +++ b/packages/typespec-autorest/src/openapi.ts @@ -2132,7 +2132,7 @@ export async function getOpenAPIForService( const extensions = getExtensions(program, type); if ( type.kind === "Model" && - (isAzureResource(program, type) || isCustomAzureResource(program, type)) + (isAzureResource(program, type) || isCustomAzureResource(program, type) === true) ) { emitObject["x-ms-azure-resource"] = true; } From d68b5a6a008fcc577d2f8b22ac97c50f8a3c7f7f Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Tue, 12 Aug 2025 15:05:31 -0700 Subject: [PATCH 6/7] Rename external type decorator and preserve behavior of customAzureResource --- packages/typespec-autorest/src/openapi.ts | 9 +++--- .../test/arm/resources.test.ts | 28 +++++++++++++++---- .../typespec-azure-resource-manager/README.md | 12 ++++---- .../Azure.ResourceManager.Legacy.ts | 16 +++++++++-- .../lib/Legacy/decorator.tsp | 12 ++++++-- .../src/resource.ts | 25 +++++++++++------ .../src/state.ts | 2 +- .../src/tsp-index.ts | 4 +-- .../reference/data-types.md | 14 ++++++++++ .../reference/decorators.md | 10 ++++--- .../reference/index.mdx | 3 +- 11 files changed, 100 insertions(+), 35 deletions(-) diff --git a/packages/typespec-autorest/src/openapi.ts b/packages/typespec-autorest/src/openapi.ts index be7468e7e6..cad4b021c6 100644 --- a/packages/typespec-autorest/src/openapi.ts +++ b/packages/typespec-autorest/src/openapi.ts @@ -15,13 +15,13 @@ import { getArmCommonTypeOpenAPIRef, getArmIdentifiers, getArmKeyIdentifiers, + getCustomResourceOptions, getExternalTypeRef, isArmCommonType, - isArmExternalResource, + isArmExternalType, isArmProviderNamespace, isAzureResource, isConditionallyFlattened, - isCustomAzureResource, } from "@azure-tools/typespec-azure-resource-manager"; import { getClientNameOverride, @@ -2132,14 +2132,15 @@ export async function getOpenAPIForService( const extensions = getExtensions(program, type); if ( type.kind === "Model" && - (isAzureResource(program, type) || isCustomAzureResource(program, type) === true) + (isAzureResource(program, type) || + getCustomResourceOptions(program, type)?.isAzureResource === true) ) { emitObject["x-ms-azure-resource"] = true; } if (getAsEmbeddingVector(program, type as Model) !== undefined) { emitObject["x-ms-embedding-vector"] = true; } - if (type.kind === "Model" && isArmExternalResource(program, type) === true) { + if (type.kind === "Model" && isArmExternalType(program, type) === true) { emitObject["x-ms-external"] = true; } if (type.kind === "Scalar") { diff --git a/packages/typespec-autorest/test/arm/resources.test.ts b/packages/typespec-autorest/test/arm/resources.test.ts index 1c59c28276..249cabe007 100644 --- a/packages/typespec-autorest/test/arm/resources.test.ts +++ b/packages/typespec-autorest/test/arm/resources.test.ts @@ -1,5 +1,5 @@ import { deepEqual, deepStrictEqual, ok, strictEqual } from "assert"; -import { it } from "vitest"; +import { expect, it } from "vitest"; import { compileOpenAPI } from "../test-host.js"; it("emits correct paths for tenant resources", async () => { @@ -338,7 +338,7 @@ it("emits x-ms-azure-resource for resource with @azureResourceBase", async () => ok(openApi.definitions?.Widget["x-ms-azure-resource"]); }); -it("emits x-ms-external for resource with @armExternalResource", async () => { +it("emits x-ms-external for resource with @armExternalType", async () => { const openApi = await compileOpenAPI( ` @armProviderNamespace @@ -347,7 +347,7 @@ it("emits x-ms-external for resource with @armExternalResource", async () => { #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "legacy test" @doc("Widget resource") - @Azure.ResourceManager.Legacy.armExternalResource + @Azure.ResourceManager.Legacy.armExternalType model Widget { name: string; } @@ -357,7 +357,7 @@ it("emits x-ms-external for resource with @armExternalResource", async () => { ok(openApi.definitions?.Widget["x-ms-external"]); }); -it("emits x-ms-azure-resource for resource with @customAzureResource", async () => { +it("emits x-ms-azure-resource for resource with @customAzureResource and options", async () => { const openApi = await compileOpenAPI( ` @armProviderNamespace @@ -366,7 +366,7 @@ it("emits x-ms-azure-resource for resource with @customAzureResource", async () #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "legacy test" @doc("Widget resource") - @Azure.ResourceManager.Legacy.customAzureResource + @Azure.ResourceManager.Legacy.customAzureResource(#{isAzureResource: true}) model Widget { name: string; } @@ -375,6 +375,24 @@ it("emits x-ms-azure-resource for resource with @customAzureResource", async () ); ok(openApi.definitions?.Widget["x-ms-azure-resource"]); }); +it("does not emit x-ms-azure-resource for resource with @customAzureResource", async () => { + const openApi = await compileOpenAPI( + ` + @armProviderNamespace + @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + namespace Microsoft.Contoso; + + #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "legacy test" + @doc("Widget resource") + @Azure.ResourceManager.Legacy.customAzureResource + model Widget { + name: string; + } +`, + { preset: "azure" }, + ); + expect(openApi.definitions?.Widget["x-ms-azure-resource"]).toBeUndefined(); +}); it("excludes properties marked @invisible from the resource payload", async () => { const openApi = await compileOpenAPI( diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 20a34dd09b..5e1e9e0a52 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -541,17 +541,17 @@ This allows sharing Azure Resource Manager resource types across specifications ### Azure.ResourceManager.Legacy -- [`@armExternalResource`](#@armexternalresource) +- [`@armExternalType`](#@armexternaltype) - [`@armOperationRoute`](#@armoperationroute) - [`@customAzureResource`](#@customazureresource) - [`@externalTypeRef`](#@externaltyperef) -#### `@armExternalResource` +#### `@armExternalType` Signifies that a Resource is represented using a library type in generated SDKs. ```typespec -@Azure.ResourceManager.Legacy.armExternalResource +@Azure.ResourceManager.Legacy.armExternalType ``` ##### Target @@ -589,7 +589,7 @@ This decorator is used on resources that do not satisfy the definition of a reso but need to be identified as such. ```typespec -@Azure.ResourceManager.Legacy.customAzureResource +@Azure.ResourceManager.Legacy.customAzureResource(options?: valueof Azure.ResourceManager.Legacy.CustomResourceOptions) ``` ##### Target @@ -598,7 +598,9 @@ but need to be identified as such. ##### Parameters -None +| Name | Type | Description | +| ------- | --------------------------------------------------------- | ---------------------------------------------------- | +| options | [valueof `CustomResourceOptions`](#customresourceoptions) | Options for customizing the behavior of the resource | #### `@externalTypeRef` diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts index 34ea29fc89..8b21cbd241 100644 --- a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Legacy.ts @@ -1,5 +1,9 @@ import type { DecoratorContext, Model, ModelProperty, Operation } from "@typespec/compiler"; +export interface CustomResourceOptions { + readonly isAzureResource?: boolean; +} + export interface ArmOperationOptions { readonly useStaticRoute?: boolean; readonly route?: string; @@ -8,8 +12,14 @@ export interface ArmOperationOptions { /** * This decorator is used on resources that do not satisfy the definition of a resource * but need to be identified as such. + * + * @param options Options for customizing the behavior of the resource */ -export type CustomAzureResourceDecorator = (context: DecoratorContext, target: Model) => void; +export type CustomAzureResourceDecorator = ( + context: DecoratorContext, + target: Model, + options?: CustomResourceOptions, +) => void; /** * Specify an external reference that should be used when emitting this type. @@ -40,11 +50,11 @@ export type ArmOperationRouteDecorator = ( * * @param target The model to that is an external resource */ -export type ArmExternalResourceDecorator = (context: DecoratorContext, target: Model) => void; +export type ArmExternalTypeDecorator = (context: DecoratorContext, target: Model) => void; export type AzureResourceManagerLegacyDecorators = { customAzureResource: CustomAzureResourceDecorator; externalTypeRef: ExternalTypeRefDecorator; armOperationRoute: ArmOperationRouteDecorator; - armExternalResource: ArmExternalResourceDecorator; + armExternalType: ArmExternalTypeDecorator; }; diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp index a0c260bab5..1aefbe526f 100644 --- a/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp +++ b/packages/typespec-azure-resource-manager/lib/Legacy/decorator.tsp @@ -12,11 +12,19 @@ model ArmOperationOptions { /** The status route for operations to use */ route?: string; } + +/** Options for customizing the behavior of a custom azure resource */ +model CustomResourceOptions { + /** Should the resource be marked as an Azure resource */ + isAzureResource?: boolean; +} + /** * This decorator is used on resources that do not satisfy the definition of a resource * but need to be identified as such. + * @param options Options for customizing the behavior of the resource */ -extern dec customAzureResource(target: Model); +extern dec customAzureResource(target: Model, options?: valueof CustomResourceOptions); /** * Specify an external reference that should be used when emitting this type. @@ -36,4 +44,4 @@ extern dec armOperationRoute(target: Operation, route?: valueof ArmOperationOpti * Signifies that a Resource is represented using a library type in generated SDKs. * @param target The model to that is an external resource */ -extern dec armExternalResource(target: Model); +extern dec armExternalType(target: Model); diff --git a/packages/typespec-azure-resource-manager/src/resource.ts b/packages/typespec-azure-resource-manager/src/resource.ts index 70394887bd..29bb8f3f29 100644 --- a/packages/typespec-azure-resource-manager/src/resource.ts +++ b/packages/typespec-azure-resource-manager/src/resource.ts @@ -39,8 +39,9 @@ import { TenantResourceDecorator, } from "../generated-defs/Azure.ResourceManager.js"; import { - ArmExternalResourceDecorator, + ArmExternalTypeDecorator, CustomAzureResourceDecorator, + CustomResourceOptions, } from "../generated-defs/Azure.ResourceManager.Legacy.js"; import { reportDiagnostic } from "./lib.js"; import { @@ -84,17 +85,17 @@ export interface ArmResourceDetailsBase { typespecType: Model; } -export const [isArmExternalResource, setArmExternalResource] = useStateMap( - ArmStateKeys.armExternalResource, +export const [isArmExternalType, setArmExternalType] = useStateMap( + ArmStateKeys.armExternalType, ); -export const $armExternalResource: ArmExternalResourceDecorator = ( +export const $armExternalType: ArmExternalTypeDecorator = ( context: DecoratorContext, entity: Model, ) => { const { program } = context; if (isTemplateDeclaration(entity)) return; - setArmExternalResource(program, entity, true); + setArmExternalType(program, entity, true); }; /** Details for RP resources */ @@ -205,11 +206,12 @@ export const $armVirtualResource: ArmVirtualResourceDecorator = ( export const $customAzureResource: CustomAzureResourceDecorator = ( context: DecoratorContext, entity: Model, + options?: CustomResourceOptions, ) => { const { program } = context; + const optionsValue = options ?? { isAzureResource: false }; if (isTemplateDeclaration(entity)) return; - - program.stateMap(ArmStateKeys.customAzureResource).set(entity, "Custom"); + setCustomResource(program, entity, optionsValue); }; function getProperty( @@ -262,6 +264,12 @@ export function getArmVirtualResourceDetails( return undefined; } +const [getCustomResourceOptions, setCustomResource] = useStateMap( + ArmStateKeys.customAzureResource, +); + +export { getCustomResourceOptions }; + /** * Determine if the given model is a custom resource. * @param program The program to process. @@ -269,7 +277,8 @@ export function getArmVirtualResourceDetails( * @returns true if the model or any model it extends is marked as a resource, otherwise false. */ export function isCustomAzureResource(program: Program, target: Model): boolean { - if (program.stateMap(ArmStateKeys.customAzureResource).has(target)) return true; + const resourceOptions = getCustomResourceOptions(program, target); + if (resourceOptions?.isAzureResource) return true; if (target.baseModel) return isCustomAzureResource(program, target.baseModel); return false; } diff --git a/packages/typespec-azure-resource-manager/src/state.ts b/packages/typespec-azure-resource-manager/src/state.ts index 92bc7e2d7f..4bde376e7e 100644 --- a/packages/typespec-azure-resource-manager/src/state.ts +++ b/packages/typespec-azure-resource-manager/src/state.ts @@ -37,5 +37,5 @@ export const ArmStateKeys = { armCommonParameters: azureResourceManagerCreateStateSymbol("armCommonParameters"), armCommonTypesVersions: azureResourceManagerCreateStateSymbol("armCommonTypesVersions"), armResourceRoute: azureResourceManagerCreateStateSymbol("armResourceRoute"), - armExternalResource: azureResourceManagerCreateStateSymbol("armExternalResource"), + armExternalType: azureResourceManagerCreateStateSymbol("armExternalType"), }; diff --git a/packages/typespec-azure-resource-manager/src/tsp-index.ts b/packages/typespec-azure-resource-manager/src/tsp-index.ts index e2ad6b70db..eae6bb932f 100644 --- a/packages/typespec-azure-resource-manager/src/tsp-index.ts +++ b/packages/typespec-azure-resource-manager/src/tsp-index.ts @@ -14,7 +14,7 @@ import { $armResourceUpdate, } from "./operations.js"; import { - $armExternalResource, + $armExternalType, $armProviderNameValue, $armResourceOperations, $armVirtualResource, @@ -61,7 +61,7 @@ export const $decorators = { customAzureResource: $customAzureResource, externalTypeRef: $externalTypeRef, armOperationRoute: $armOperationRoute, - armExternalResource: $armExternalResource, + armExternalType: $armExternalType, } satisfies AzureResourceManagerLegacyDecorators, }; diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md index f43af2c21f..1383bda2c3 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md @@ -3370,6 +3370,20 @@ model Azure.ResourceManager.Legacy.ArmOperationOptions | useStaticRoute? | `boolean` | Should a static route be used | | route? | `string` | The status route for operations to use | +### `CustomResourceOptions` {#Azure.ResourceManager.Legacy.CustomResourceOptions} + +Options for customizing the behavior of a custom azure resource + +```typespec +model Azure.ResourceManager.Legacy.CustomResourceOptions +``` + +#### Properties + +| Name | Type | Description | +| ---------------- | --------- | -------------------------------------------------- | +| isAzureResource? | `boolean` | Should the resource be marked as an Azure resource | + ### `LegacyTrackedResource` {#Azure.ResourceManager.Legacy.LegacyTrackedResource} A tracked resource with the 'location' property optional diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md index 6092a155de..9a14cc3b11 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/decorators.md @@ -458,12 +458,12 @@ This allows sharing Azure Resource Manager resource types across specifications ## Azure.ResourceManager.Legacy -### `@armExternalResource` {#@Azure.ResourceManager.Legacy.armExternalResource} +### `@armExternalType` {#@Azure.ResourceManager.Legacy.armExternalType} Signifies that a Resource is represented using a library type in generated SDKs. ```typespec -@Azure.ResourceManager.Legacy.armExternalResource +@Azure.ResourceManager.Legacy.armExternalType ``` #### Target @@ -501,7 +501,7 @@ This decorator is used on resources that do not satisfy the definition of a reso but need to be identified as such. ```typespec -@Azure.ResourceManager.Legacy.customAzureResource +@Azure.ResourceManager.Legacy.customAzureResource(options?: valueof Azure.ResourceManager.Legacy.CustomResourceOptions) ``` #### Target @@ -510,7 +510,9 @@ but need to be identified as such. #### Parameters -None +| Name | Type | Description | +| ------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| options | [valueof `CustomResourceOptions`](./data-types.md#Azure.ResourceManager.Legacy.CustomResourceOptions) | Options for customizing the behavior of the resource | ### `@externalTypeRef` {#@Azure.ResourceManager.Legacy.externalTypeRef} diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx index 7748405405..9ac14cd8a1 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx @@ -314,7 +314,7 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager ### Decorators -- [`@armExternalResource`](./decorators.md#@Azure.ResourceManager.Legacy.armExternalResource) +- [`@armExternalType`](./decorators.md#@Azure.ResourceManager.Legacy.armExternalType) - [`@armOperationRoute`](./decorators.md#@Azure.ResourceManager.Legacy.armOperationRoute) - [`@customAzureResource`](./decorators.md#@Azure.ResourceManager.Legacy.customAzureResource) - [`@externalTypeRef`](./decorators.md#@Azure.ResourceManager.Legacy.externalTypeRef) @@ -339,6 +339,7 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager ### Models - [`ArmOperationOptions`](./data-types.md#Azure.ResourceManager.Legacy.ArmOperationOptions) +- [`CustomResourceOptions`](./data-types.md#Azure.ResourceManager.Legacy.CustomResourceOptions) - [`LegacyTrackedResource`](./data-types.md#Azure.ResourceManager.Legacy.LegacyTrackedResource) - [`ManagedServiceIdentityV4`](./data-types.md#Azure.ResourceManager.Legacy.ManagedServiceIdentityV4) - [`ManagedServiceIdentityV4Property`](./data-types.md#Azure.ResourceManager.Legacy.ManagedServiceIdentityV4Property) From 0eb559e5b89f27f9e39a507289d18d0a5b557863 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Tue, 12 Aug 2025 15:51:21 -0700 Subject: [PATCH 7/7] Fix inadvertent change in isCustomAzureResource --- packages/typespec-azure-resource-manager/src/resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-azure-resource-manager/src/resource.ts b/packages/typespec-azure-resource-manager/src/resource.ts index 29bb8f3f29..3ab93e9e07 100644 --- a/packages/typespec-azure-resource-manager/src/resource.ts +++ b/packages/typespec-azure-resource-manager/src/resource.ts @@ -278,7 +278,7 @@ export { getCustomResourceOptions }; */ export function isCustomAzureResource(program: Program, target: Model): boolean { const resourceOptions = getCustomResourceOptions(program, target); - if (resourceOptions?.isAzureResource) return true; + if (resourceOptions) return true; if (target.baseModel) return isCustomAzureResource(program, target.baseModel); return false; }