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

[tcgc] support generic type decorators in sdk types #966

Merged
merged 27 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03efd05
initial version
tadelesh Jun 6, 2024
411a17e
changelog
tadelesh Jun 6, 2024
9546ed4
review
tadelesh Jun 7, 2024
a181a88
Merge branch 'main' into decorators_list
tadelesh Jun 7, 2024
ac0976b
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 12, 2024
7ff40cc
refine with review
tadelesh Jun 12, 2024
e844262
Merge branch 'main' into decorators_list
tadelesh Jun 12, 2024
60736e5
Merge branch 'main' into decorators_list
tadelesh Jun 13, 2024
f072219
Merge branch 'main' into decorators_list
tadelesh Jun 14, 2024
bd57509
refine with review
tadelesh Jun 14, 2024
367e95e
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 17, 2024
a65efd8
format
tadelesh Jun 17, 2024
7c9249e
update with review
tadelesh Jun 18, 2024
017c3b2
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 18, 2024
30a9052
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 18, 2024
16635c8
update dep
tadelesh Jun 18, 2024
12223e7
Merge branch 'main' into decorators_list
tadelesh Jun 19, 2024
4ab74a6
refine with review
tadelesh Jun 19, 2024
d63952a
fix
tadelesh Jun 19, 2024
079ffde
fix diagnostic issue and add test
tadelesh Jun 19, 2024
157a726
format, lint, add `@useFinalStateVia` to white list and add test
tadelesh Jun 20, 2024
c099e4e
rename and doc
tadelesh Jun 24, 2024
93be6bb
refine
tadelesh Jun 25, 2024
a60f7f2
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 25, 2024
7426d3c
typo and format
tadelesh Jun 25, 2024
a540867
Merge remote-tracking branch 'origin/main' into decorators_list
tadelesh Jun 26, 2024
0f63c09
update dep
tadelesh Jun 26, 2024
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/decorators_list-2024-5-6-18-38-7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

export decorators in white list to all sdk types
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ export function createSdkContext<
__namespaceToApiVersionParameter: new Map(),
__tspTypeToApiVersions: new Map(),
__namespaceToApiVersionClientDefaultValue: new Map(),
decoratorsWhiteList: context.options["decorators-white-list"] ?? [],
};
sdkContext.experimental_sdkPackage = getSdkPackage(sdkContext);
if (sdkContext.diagnostics) {
Expand Down
5 changes: 5 additions & 0 deletions packages/typespec-client-generator-core/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
getAvailableApiVersions,
getDocHelper,
getLocationOfOperation,
getTypeDecorators,
isAcceptHeader,
isContentTypeHeader,
isNeverOrVoidType,
Expand Down Expand Up @@ -166,6 +167,7 @@ function getSdkHttpParameters(
optional: false,
correspondingMethodParams,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, httpOperation.operation)}.body`,
decorators: getTypeDecorators(context, tspBody.type),
};
}
if (retval.bodyParam) {
Expand Down Expand Up @@ -234,6 +236,7 @@ function createContentTypeOrAcceptHeader(
let type: SdkType = {
kind: "string",
encode: "string",
decorators: {},
};
// for contentType, we treat it as a constant IFF there's one value and it's application/json.
// this is to prevent a breaking change when a service adds more content types in the future.
Expand All @@ -254,6 +257,7 @@ function createContentTypeOrAcceptHeader(
valueType: type,
name: `${httpOperation.operation.name}ContentType`,
isGeneratedName: true,
decorators: {},
};
}
// No need for clientDefaultValue because it's a constant, it only has one value
Expand All @@ -267,6 +271,7 @@ function createContentTypeOrAcceptHeader(
onClient: false,
optional: false,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, httpOperation.operation)}.${name}`,
decorators: {},
};
}

Expand Down
14 changes: 10 additions & 4 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface SdkEmitterOptions {
"package-name"?: string;
"flatten-union-as-enum"?: boolean;
"api-version"?: string;
"decorators-white-list"?: string[];
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
}

export interface SdkClient {
Expand All @@ -54,7 +55,8 @@ export interface SdkInitializationType extends SdkModelType {
properties: SdkParameter[];
}

export interface SdkClientType<TServiceOperation extends SdkServiceOperation> {
export interface SdkClientType<TServiceOperation extends SdkServiceOperation>
extends DecoratedType {
kind: "client";
name: string;
description?: string;
Expand All @@ -77,7 +79,11 @@ export interface SdkOperationGroup {
service: Namespace;
}

interface SdkTypeBase {
interface DecoratedType {
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
decorators: Record<string, Array<any>>;
}

interface SdkTypeBase extends DecoratedType {
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
__raw?: Type;
kind: string;
deprecation?: string;
Expand Down Expand Up @@ -318,7 +324,7 @@ export interface SdkEndpointType extends SdkTypeBase {
templateArguments: SdkPathParameter[];
}

export interface SdkModelPropertyTypeBase {
export interface SdkModelPropertyTypeBase extends DecoratedType {
__raw?: ModelProperty;
type: SdkType;
/**
Expand Down Expand Up @@ -461,7 +467,7 @@ export interface SdkHttpOperation extends SdkServiceOperationBase {
export type SdkServiceOperation = SdkHttpOperation;
export type SdkServiceParameter = SdkHttpParameter;

interface SdkMethodBase {
interface SdkMethodBase extends DecoratedType {
__raw?: Operation;
name: string;
access: AccessFlags;
Expand Down
67 changes: 66 additions & 1 deletion packages/typespec-client-generator-core/src/internal-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {
Interface,
Model,
Namespace,
Numeric,
NumericLiteral,
Operation,
Program,
ProjectedProgram,
StringLiteral,
Type,
Union,
Value,
createDiagnosticCollector,
getDeprecationDetails,
getDoc,
Expand Down Expand Up @@ -242,6 +244,7 @@ interface DefaultSdkTypeBase<TKind> {
__raw: Type;
deprecation?: string;
kind: TKind;
decorators: Record<string, Array<any>>;
}

/**
Expand All @@ -257,9 +260,69 @@ export function getSdkTypeBaseHelper<TKind>(
__raw: type,
deprecation: getDeprecationDetails(context.program, type)?.message,
kind,
decorators: getTypeDecorators(context, type),
};
}

export function getTypeDecorators(context: TCGCContext, type: Type): Record<string, Array<any>> {
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
const retval: Record<string, Array<any>> = {};
if ("decorators" in type) {
for (const decorator of type.decorators) {
// only process explicitly defined decorators
if (decorator.definition) {
const decoratorName = `${decorator.definition?.namespace ? getNamespaceFullName(decorator.definition.namespace) + "." : ""}${decorator.definition?.name}`;
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
// white list filtering
if (!context.decoratorsWhiteList) {
continue;
}
let found = false;
for (const item of context.decoratorsWhiteList) {
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
if (decoratorName === item) {
found = true;
break;
}
const regex = new RegExp(item);
if (regex.test(decoratorName)) {
found = true;
break;
}
}
if (!found) {
continue;
}
retval[decoratorName] = [];
for (const arg of decorator.args) {
retval[decoratorName].push(getDecoratorArgValue(arg.jsValue));
}
}
}
}
return retval;
}

function getDecoratorArgValue(
arg:
| Type
| Record<string, unknown>
| Value
| unknown[]
| string
| number
| boolean
| Numeric
| null
): any {
if (typeof arg === "object" && arg !== null && "kind" in arg) {
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
if (arg.kind === "EnumMember") {
return arg.value ?? arg.name;
}
if (arg.kind === "String" || arg.kind === "Number" || arg.kind === "Boolean") {
return arg.value;
}
}
return arg;
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
}

export function intOrFloat(value: number): "int32" | "float32" {
return value.toString().indexOf(".") === -1 ? "int32" : "float32";
}
Expand Down Expand Up @@ -325,6 +388,7 @@ export interface TCGCContext {
apiVersion?: string;
__service_projection?: Map<Namespace, [Namespace, ProjectedProgram | undefined]>;
originalProgram: Program;
decoratorsWhiteList?: string[];
}

export function createTCGCContext(program: Program): TCGCContext {
Expand Down Expand Up @@ -426,9 +490,10 @@ export function isNeverOrVoidType(type: Type): boolean {
return isNeverType(type) || isVoidType(type);
}

export function getAnyType(): SdkBuiltInType {
export function getAnyType(context: TCGCContext, type: Type): SdkBuiltInType {
return {
kind: "any",
encode: "string",
decorators: getTypeDecorators(context, type),
};
}
12 changes: 12 additions & 0 deletions packages/typespec-client-generator-core/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
getHashForType,
getLocationOfOperation,
getSdkTypeBaseHelper,
getTypeDecorators,
isNeverOrVoidType,
updateWithApiVersionInformation,
} from "./internal-utils.js";
Expand Down Expand Up @@ -214,6 +215,7 @@ function getSdkMethodResponse<
values: allResponseBodies,
name: createGeneratedName(context, operation, "UnionResponse"),
isGeneratedName: true,
decorators: {},
};
} else if (responseTypes) {
type = allResponseBodies[0];
Expand All @@ -222,6 +224,7 @@ function getSdkMethodResponse<
type = {
kind: "nullable",
type: type,
decorators: {},
};
}
return {
Expand Down Expand Up @@ -307,6 +310,7 @@ function getSdkBasicServiceMethod<
return undefined; // currently we only return a value for paging or lro
},
crossLanguageDefintionId: getCrossLanguageDefinitionId(context, operation),
decorators: getTypeDecorators(context, operation),
});
}

Expand Down Expand Up @@ -392,6 +396,7 @@ function getSdkInitializationType<
apiVersions: context.__tspTypeToApiVersions.get(client.type)!,
isFormDataType: false,
isError: false,
decorators: {},
});
}

Expand Down Expand Up @@ -422,6 +427,7 @@ function getSdkMethodParameter(
isApiVersionParam: false,
onClient: false,
crossLanguageDefinitionId: "anonymous",
decorators: getTypeDecorators(context, type),
});
}
return diagnostics.wrap({
Expand Down Expand Up @@ -455,6 +461,7 @@ function getSdkMethods<TOptions extends object, TServiceOperation extends SdkSer
response: operationGroupClient,
apiVersions: getAvailableApiVersions(context, operationGroup.type, client.type),
crossLanguageDefintionId: getCrossLanguageDefinitionId(context, operationGroup.type),
decorators: {},
});
}
return diagnostics.wrap(retval);
Expand Down Expand Up @@ -494,8 +501,10 @@ function getSdkEndpointParameter(
isApiVersionParam: false,
apiVersions: context.__tspTypeToApiVersions.get(client.type)!,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`,
decorators: {},
},
],
decorators: {},
};
} else {
// this means we have one server
Expand All @@ -504,6 +513,7 @@ function getSdkEndpointParameter(
kind: "endpoint",
serverUrl: servers[0].url,
templateArguments,
decorators: {},
};
for (const param of servers[0].parameters.values()) {
const sdkParam = diagnostics.pipe(getSdkHttpParameter(context, param, undefined, "path"));
Expand Down Expand Up @@ -543,6 +553,7 @@ function getSdkEndpointParameter(
optional,
isApiVersionParam: false,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`,
decorators: {},
});
}

Expand Down Expand Up @@ -572,6 +583,7 @@ function createSdkClientType<
), // MUST call this after getSdkMethods has been called
// eslint-disable-next-line deprecation/deprecation
arm: client.kind === "SdkClient" ? client.arm : false,
decorators: getTypeDecorators(context, client.type),
};
context.__clients!.push(sdkClientType);
return diagnostics.wrap(sdkClientType);
Expand Down
Loading
Loading