Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @access and @usage support for named union #413

Merged
merged 5 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to mark them explicitly as deprecated instead of just a comment that most people don't read?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timotheeguerin they've already been previously marked as deprecated. They've been deprecated for a while, is it fair for us to remove support for it now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have still never removed any deprecated element from typespec but for tcgc probably ok if you do as it shouldn't be used in RPSaaSmaster yet and otherwise it would have already failed

*
* 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
Loading