Skip to content

Commit

Permalink
Add @access and @usage support for named union (#413)
Browse files Browse the repository at this point in the history
  • Loading branch information
tadelesh authored Mar 13, 2024
1 parent 4e37bca commit 53a985f
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 17 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/fix-tcgc-2024-2-13-14-25-56.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Add `@access` and `@usage` support for named union
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ the access result is undefined.

#### Target

`Model | Operation | Enum`
`Model | Operation | Enum | Union`

#### Parameters

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -469,7 +477,7 @@ you need to take care of all related models/enums.

#### Target

`Model | Enum`
`Model | Enum | Union`

#### Parameters

Expand Down
12 changes: 10 additions & 2 deletions packages/typespec-client-generator-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ the access result is undefined.

##### Target

`Model | Operation | Enum`
`Model | Operation | Enum | Union`

##### Parameters

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -486,7 +494,7 @@ you need to take care of all related models/enums.

##### Target

`Model | Enum`
`Model | Enum | Union`

##### Parameters

Expand Down
18 changes: 15 additions & 3 deletions packages/typespec-client-generator-core/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 18 additions & 9 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
) {
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion packages/typespec-client-generator-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ export function getSdkUnionWithDiagnostics(
getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)
);
clientType.nullable = true;
clientType.__raw = type;
return diagnostics.wrap(clientType);
}

Expand Down Expand Up @@ -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;
Expand Down
84 changes: 84 additions & 0 deletions packages/typespec-client-generator-core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down

0 comments on commit 53a985f

Please sign in to comment.