From 53a985f696e6e583b2893177e51e5689e489702e Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Thu, 14 Mar 2024 02:28:27 +0800 Subject: [PATCH] Add `@access` and `@usage` support for named union (#413) --- .../changes/fix-tcgc-2024-2-13-14-25-56.md | 7 ++ .../reference/decorators.md | 12 ++- .../typespec-client-generator-core/README.md | 12 ++- .../lib/decorators.tsp | 18 +++- .../src/decorators.ts | 27 ++++-- .../src/types.ts | 3 +- .../test/types.test.ts | 84 +++++++++++++++++++ 7 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 .chronus/changes/fix-tcgc-2024-2-13-14-25-56.md diff --git a/.chronus/changes/fix-tcgc-2024-2-13-14-25-56.md b/.chronus/changes/fix-tcgc-2024-2-13-14-25-56.md new file mode 100644 index 0000000000..9fcfcd5900 --- /dev/null +++ b/.chronus/changes/fix-tcgc-2024-2-13-14-25-56.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Add `@access` and `@usage` support for named union \ No newline at end of file diff --git a/docs/libraries/typespec-client-generator-core/reference/decorators.md b/docs/libraries/typespec-client-generator-core/reference/decorators.md index acbd7a8437..408d308352 100644 --- a/docs/libraries/typespec-client-generator-core/reference/decorators.md +++ b/docs/libraries/typespec-client-generator-core/reference/decorators.md @@ -28,7 +28,7 @@ the access result is undefined. #### Target -`Model | Operation | Enum` +`Model | Operation | Enum | Union` #### Parameters @@ -201,6 +201,8 @@ interface MyInterface {} ### `@clientFormat` {#@Azure.ClientGenerator.Core.clientFormat} +DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead. + Can be used to explain the client type that the current TYPESPEC type should map to. @@ -289,6 +291,8 @@ op test: void; ### `@exclude` {#@Azure.ClientGenerator.Core.exclude} +DEPRECATED: Use `@usage` and `@access` decorator instead. + Whether to exclude a model from generation for specific languages. By default we generate all models that are included in operations. @@ -345,6 +349,8 @@ model Bar {} ### `@include` {#@Azure.ClientGenerator.Core.include} +DEPRECATED: Use `@usage` and `@access` decorator instead. + Whether to include a model in generation for specific languages. By default we generate all models that are included in operations. @@ -373,6 +379,8 @@ model ModelToInclude { ### `@internal` {#@Azure.ClientGenerator.Core.internal} +DEPRECATED: Use `@access` decorator instead. + Whether to mark an operation as internal for specific languages, meaning it should not be exposed to end SDK users @@ -469,7 +477,7 @@ you need to take care of all related models/enums. #### Target -`Model | Enum` +`Model | Enum | Union` #### Parameters diff --git a/packages/typespec-client-generator-core/README.md b/packages/typespec-client-generator-core/README.md index 1488b9935e..45c0f8f59f 100644 --- a/packages/typespec-client-generator-core/README.md +++ b/packages/typespec-client-generator-core/README.md @@ -45,7 +45,7 @@ the access result is undefined. ##### Target -`Model | Operation | Enum` +`Model | Operation | Enum | Union` ##### Parameters @@ -218,6 +218,8 @@ interface MyInterface {} #### `@clientFormat` +DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead. + Can be used to explain the client type that the current TYPESPEC type should map to. @@ -306,6 +308,8 @@ op test: void; #### `@exclude` +DEPRECATED: Use `@usage` and `@access` decorator instead. + Whether to exclude a model from generation for specific languages. By default we generate all models that are included in operations. @@ -362,6 +366,8 @@ model Bar {} #### `@include` +DEPRECATED: Use `@usage` and `@access` decorator instead. + Whether to include a model in generation for specific languages. By default we generate all models that are included in operations. @@ -390,6 +396,8 @@ model ModelToInclude { #### `@internal` +DEPRECATED: Use `@access` decorator instead. + Whether to mark an operation as internal for specific languages, meaning it should not be exposed to end SDK users @@ -486,7 +494,7 @@ you need to take care of all related models/enums. ##### Target -`Model | Enum` +`Model | Enum | Union` ##### Parameters diff --git a/packages/typespec-client-generator-core/lib/decorators.tsp b/packages/typespec-client-generator-core/lib/decorators.tsp index b680a0dc90..a0731d8d4e 100644 --- a/packages/typespec-client-generator-core/lib/decorators.tsp +++ b/packages/typespec-client-generator-core/lib/decorators.tsp @@ -92,6 +92,8 @@ extern dec client(target: Namespace | Interface, value?: {}, scope?: valueof str extern dec operationGroup(target: Namespace | Interface, scope?: valueof string); /** + * DEPRECATED: Use `@usage` and `@access` decorator instead. + * * Whether to exclude a model from generation for specific languages. By default we generate * all models that are included in operations. * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters @@ -108,6 +110,8 @@ extern dec operationGroup(target: Namespace | Interface, scope?: valueof string) extern dec exclude(target: Model, scope?: valueof string); /** + * DEPRECATED: Use `@usage` and `@access` decorator instead. + * * Whether to include a model in generation for specific languages. By default we generate * all models that are included in operations. * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters @@ -126,6 +130,8 @@ extern dec include(target: Model, scope?: valueof string); alias ClientFormat = "unixtime" | "iso8601" | "rfc1123" | "seconds"; /** + * DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead. + * * Can be used to explain the client type that the current TYPESPEC * type should map to. * @param value The client format to apply. @@ -137,10 +143,12 @@ alias ClientFormat = "unixtime" | "iso8601" | "rfc1123" | "seconds"; * } * ``` */ -#deprecated "@clientFormat decorator is deprecated. Use `@encode` decorator in `@typespec/core` instead." +#deprecated "@clientFormat decorator is deprecated. Use `@encode` decorator in `@typespec/compiler` instead." extern dec clientFormat(target: ModelProperty, value: valueof ClientFormat); /** + * DEPRECATED: Use `@access` decorator instead. + * * Whether to mark an operation as internal for specific languages, * meaning it should not be exposed to end SDK users * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters @@ -233,7 +241,7 @@ enum Usage { * op getModel(): Fish; * ``` */ -extern dec usage(target: Model | Enum, value: EnumMember | Union, scope?: valueof string); +extern dec usage(target: Model | Enum | Union, value: EnumMember | Union, scope?: valueof string); /** * Access value. @@ -389,7 +397,11 @@ enum Access { * ): void; * ``` */ -extern dec access(target: Model | Operation | Enum, value: EnumMember, scope?: valueof string); +extern dec access( + target: Model | Operation | Enum | Union, + value: EnumMember, + scope?: valueof string +); /** * Set whether a model property should be flattened or not. diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 32a3c7f7bf..130a84dad8 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -41,7 +41,7 @@ import { TCGCContext, parseEmitterName } from "./internal-utils.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js"; import { experimental_getSdkPackage } from "./package.js"; import { getLibraryName } from "./public-utils.js"; -import { getSdkEnum, getSdkModel } from "./types.js"; +import { getSdkEnum, getSdkModel, getSdkUnion } from "./types.js"; export const namespace = "Azure.ClientGenerator.Core"; const AllScopes = Symbol.for("@azure-core/typespec-client-generator-core/all-scopes"); @@ -697,7 +697,7 @@ const usageKey = createStateSymbol("usage"); export function $usage( context: DecoratorContext, - entity: Model | Enum, + entity: Model | Enum | Union, value: EnumMember | Union, scope?: LanguageScopes ) { @@ -735,7 +735,7 @@ export function $usage( export function getUsageOverride( context: TCGCContext, - entity: Model | Enum + entity: Model | Enum | Union ): UsageFlags | undefined { return getScopedDecoratorData(context, usageKey, entity); } @@ -750,7 +750,7 @@ const accessKey = createStateSymbol("access"); export function $access( context: DecoratorContext, - entity: Model | Enum | Operation, + entity: Model | Enum | Operation | Union, value: EnumMember, scope?: LanguageScopes ) { @@ -766,23 +766,32 @@ export function $access( export function getAccessOverride( context: TCGCContext, - entity: Model | Enum | Operation + entity: Model | Enum | Operation | Union ): AccessFlags | undefined { return getScopedDecoratorData(context, accessKey, entity); } export function getAccess( context: TCGCContext, - entity: Model | Enum | Operation + entity: Model | Enum | Operation | Union ): AccessFlags | undefined { const override = getScopedDecoratorData(context, accessKey, entity); if (override || entity.kind === "Operation") { return override; } - return entity.kind === "Model" - ? getSdkModel(context, entity).access - : getSdkEnum(context, entity).access; + switch (entity.kind) { + case "Model": + return getSdkModel(context, entity).access; + case "Enum": + return getSdkEnum(context, entity).access; + case "Union": + const type = getSdkUnion(context, entity); + if (type.kind === "enum" || type.kind === "model") { + return type.access; + } + return undefined; + } } const flattenPropertyKey = createStateSymbol("flattenPropertyKey"); diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 8dd903144f..e468551aca 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -351,6 +351,7 @@ export function getSdkUnionWithDiagnostics( getClientTypeWithDiagnostics(context, nonNullOptions[0], operation) ); clientType.nullable = true; + clientType.__raw = type; return diagnostics.wrap(clientType); } @@ -1309,7 +1310,7 @@ function updateAccessOfModel(context: TCGCContext): void { continue; } - const accessOverride = getAccessOverride(context, type as any); + const accessOverride = getAccessOverride(context, sdkType.__raw as any); if (accessOverride) { sdkType.access = accessOverride; continue; diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 3695b9f419..43dc1b7617 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -658,6 +658,90 @@ describe("typespec-client-generator-core: types", () => { } } }); + + it("usage", async function () { + await runner.compileWithBuiltInService(` + union UnionAsEnum { + "A", + "B", + string, + } + + model Foo { + prop: string; + } + + union NullableUnion { + Foo, + null + } + + model Bar { + prop1: UnionAsEnum; + prop2: NullableUnion; + } + + @access(Access.internal) + op func( + @body body: Bar + ): void; + `); + + const models = runner.context.experimental_sdkPackage.models; + strictEqual(models.length, 2); + const foo = models.find((x) => x.name === "Foo")! as SdkModelType; + strictEqual(foo.usage, UsageFlags.Input); + strictEqual(foo.access, "internal"); + const enums = runner.context.experimental_sdkPackage.enums; + strictEqual(enums.length, 1); + const unionAsEnum = enums.find((x) => x.name === "UnionAsEnum")! as SdkEnumType; + strictEqual(unionAsEnum.usage, UsageFlags.Input); + strictEqual(unionAsEnum.access, "internal"); + }); + + it("usage override", async function () { + await runner.compileWithBuiltInService(` + @usage(Usage.input | Usage.output) + @access(Access.public) + union UnionAsEnum { + "A", + "B", + string, + } + + model Foo { + prop: string; + } + + @usage(Usage.input | Usage.output) + @access(Access.public) + union NullableUnion { + Foo, + null + } + + model Bar { + prop1: UnionAsEnum; + prop2: NullableUnion; + } + + @access(Access.internal) + op func( + @body body: Bar + ): void; + `); + + const models = runner.context.experimental_sdkPackage.models; + strictEqual(models.length, 2); + const foo = models.find((x) => x.name === "Foo")! as SdkModelType; + strictEqual(foo.usage, UsageFlags.Input | UsageFlags.Output); + strictEqual(foo.access, "public"); + const enums = runner.context.experimental_sdkPackage.enums; + strictEqual(enums.length, 1); + const unionAsEnum = enums.find((x) => x.name === "UnionAsEnum")! as SdkEnumType; + strictEqual(unionAsEnum.usage, UsageFlags.Input | UsageFlags.Output); + strictEqual(unionAsEnum.access, "public"); + }); }); describe("SdkEnumType", () => { it("string extensible", async function () {