From d61015faa8a08563785e168cfce6eff5c41d8fa9 Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Sat, 20 Apr 2024 05:04:33 +0800 Subject: [PATCH] Support get common models for specific api version (#700) #### Config 1. A new option `api-version` will be added to the TCGC context and should be configed in languages' `tspconfig.yaml` file. 2. If the `api-version` is not set or set to `latest`, then all returns of TCGC APIs will be the information of latest API version. 3. If the `api-version` is set to a specific version, then all returns of TCGC APIs will be the information of that API version. If the version is not existed, then a diagnostic warning will be passed to emitter and will fallback to latest version. 5. If the `api-version` is set to `all`, then the returns of TCGC APIs will be the information of all API versions. Next part will describe how the versioning infomation is included in TCGC common models. --------- Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- .../tcgc_api_version-2024-3-19-17-15-30.md | 7 + .../src/decorators.ts | 74 ++- .../src/interfaces.ts | 1 + .../src/internal-utils.ts | 22 +- .../test/decorators.test.ts | 547 +++++++++++++++++- .../test/types.test.ts | 9 +- 6 files changed, 653 insertions(+), 7 deletions(-) create mode 100644 .chronus/changes/tcgc_api_version-2024-3-19-17-15-30.md diff --git a/.chronus/changes/tcgc_api_version-2024-3-19-17-15-30.md b/.chronus/changes/tcgc_api_version-2024-3-19-17-15-30.md new file mode 100644 index 0000000000..6a47711f51 --- /dev/null +++ b/.chronus/changes/tcgc_api_version-2024-3-19-17-15-30.md @@ -0,0 +1,7 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +support get common models for specific api version, default to latest api version which may include breaking changes \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index a09373343b..c35a30e001 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -24,9 +24,11 @@ import { isTemplateDeclaration, isTemplateDeclarationOrInstance, listServices, + projectProgram, validateDecoratorUniqueOnNode, } from "@typespec/compiler"; import { isHeader } from "@typespec/http"; +import { buildVersionProjections, getVersions } from "@typespec/versioning"; import { AccessFlags, LanguageScopes, @@ -205,6 +207,58 @@ function hasExplicitClientOrOperationGroup(context: TCGCContext): boolean { ); } +function serviceVersioningProjection(context: TCGCContext, client: SdkClient) { + if (!context.__service_projection) { + context.__service_projection = new Map(); + } + + let projectedService; + let projectedProgram; + + if (context.__service_projection.has(client.service)) { + [projectedService, projectedProgram] = context.__service_projection.get(client.service)!; + } else { + const allApiVersions = getVersions(context.program, client.service)[1] + ?.getVersions() + .map((x) => x.value); + if (!allApiVersions) return; + let apiVersion = context.apiVersion; + if ( + apiVersion === "latest" || + apiVersion === undefined || + !allApiVersions.includes(apiVersion) + ) { + apiVersion = allApiVersions[allApiVersions.length - 1]; + } + if (apiVersion === undefined) return; + const versionProjections = buildVersionProjections(context.program, client.service).filter( + (v) => apiVersion === v.version + ); + if (versionProjections.length !== 1) + throw new Error("Version projects should only contain one element"); + const projectedVersion = versionProjections[0]; + if (projectedVersion.projections.length > 0) { + projectedProgram = context.program = projectProgram( + context.originalProgram, + projectedVersion.projections + ); + } + projectedService = projectedProgram + ? (projectedProgram.projector.projectedTypes.get(client.service) as Namespace) + : client.service; + context.__service_projection.set(client.service, [projectedService, projectedProgram]); + } + + if (client.service !== client.type) { + client.type = projectedProgram + ? (projectedProgram.projector.projectedTypes.get(client.type) as Interface) + : client.type; + } else { + client.type = projectedService; + } + client.service = projectedService; +} + /** * List all the clients. * @@ -212,15 +266,23 @@ function hasExplicitClientOrOperationGroup(context: TCGCContext): boolean { * @returns Array of clients */ export function listClients(context: TCGCContext): SdkClient[] { + if (context.__rawClients) return context.__rawClients; + const explicitClients = [...listScopedDecoratorData(context, clientKey)]; if (explicitClients.length > 0) { + if (context.apiVersion !== "all") { + for (const client of explicitClients as SdkClient[]) { + serviceVersioningProjection(context, client); + } + } + context.__rawClients = explicitClients; return explicitClients; } // if there is no explicit client, we will treat namespaces with service decorator as clients const services = listServices(context.program); - return services.map((service) => { + const clients = services.map((service) => { let originalName = service.type.name; const clientNameOverride = getClientNameOverride(context, service.type); if (clientNameOverride) { @@ -237,8 +299,14 @@ export function listClients(context: TCGCContext): SdkClient[] { type: service.type, arm: isArm(service.type), crossLanguageDefinitionId: `${getNamespaceFullName(service.type)}.${clientName}`, - }; + } as SdkClient; }); + + if (context.apiVersion !== "all") { + clients.map((client) => serviceVersioningProjection(context, client)); + } + context.__rawClients = clients; + return clients; } const operationGroupKey = createStateSymbol("operationGroup"); @@ -523,6 +591,8 @@ export function createSdkContext< packageName: context.options["package-name"], flattenUnionAsEnum: context.options["flatten-union-as-enum"] ?? true, diagnostics: diagnostics.diagnostics, + apiVersion: context.options["api-version"], + originalProgram: context.program, }; sdkContext.experimental_sdkPackage = getSdkPackage(sdkContext); if (sdkContext.diagnostics) { diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index c881b5f424..e3dee7ba15 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -35,6 +35,7 @@ export interface SdkEmitterOptions { "filter-out-core-models"?: boolean; "package-name"?: string; "flatten-union-as-enum"?: boolean; + "api-version"?: string; } export interface SdkClient { diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 2c0cd5ee17..e82aa05b31 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -5,6 +5,7 @@ import { Namespace, Operation, Program, + ProjectedProgram, Type, Union, createDiagnosticCollector, @@ -19,6 +20,7 @@ import { HttpOperation, HttpStatusCodeRange } from "@typespec/http"; import { getAddedOnVersions, getRemovedOnVersions, getVersions } from "@typespec/versioning"; import { SdkBuiltInKinds, + SdkClient, SdkEnumType, SdkHttpResponse, SdkModelPropertyType, @@ -128,7 +130,8 @@ export function getAvailableApiVersions(context: TCGCContext, type: Type): strin let addedCounter = 0; let removeCounter = 0; const retval: string[] = []; - for (const version of apiVersions) { + for (let i = 0; i < apiVersions.length; i++) { + const version = apiVersions[i]; if (addedCounter < addedOnVersions.length && version === addedOnVersions[addedCounter]) { added = true; addedCounter++; @@ -137,7 +140,17 @@ export function getAvailableApiVersions(context: TCGCContext, type: Type): strin added = false; removeCounter++; } - if (added) retval.push(version); + if (added) { + // only add version smaller than config + if ( + context.apiVersion === undefined || + context.apiVersion === "latest" || + context.apiVersion === "all" || + apiVersions.indexOf(context.apiVersion) >= i + ) { + retval.push(version); + } + } } return retval; } @@ -260,6 +273,10 @@ export interface TCGCContext { knownScalars?: Record; diagnostics: readonly Diagnostic[]; __subscriptionIdParameter?: SdkParameter; + __rawClients?: SdkClient[]; + apiVersion?: string; + __service_projection?: Map; + originalProgram: Program; } export function createTCGCContext(program: Program): TCGCContext { @@ -267,6 +284,7 @@ export function createTCGCContext(program: Program): TCGCContext { program, emitterName: "__TCGC_INTERNAL__", diagnostics: [], + originalProgram: program, }; } diff --git a/packages/typespec-client-generator-core/test/decorators.test.ts b/packages/typespec-client-generator-core/test/decorators.test.ts index a4eb83ab5b..419fc63264 100644 --- a/packages/typespec-client-generator-core/test/decorators.test.ts +++ b/packages/typespec-client-generator-core/test/decorators.test.ts @@ -21,7 +21,7 @@ import { shouldGenerateConvenient, shouldGenerateProtocol, } from "../src/decorators.js"; -import { SdkOperationGroup, UsageFlags } from "../src/interfaces.js"; +import { SdkMethodResponse, SdkOperationGroup, UsageFlags } from "../src/interfaces.js"; import { getCrossLanguageDefinitionId, getCrossLanguagePackageId } from "../src/public-utils.js"; import { getAllModels } from "../src/types.js"; import { SdkTestRunner, createSdkContextTestHelper, createSdkTestRunner } from "./test-host.js"; @@ -2499,4 +2499,549 @@ describe("typespec-client-generator-core: decorators", () => { strictEqual(getClientNameOverride(runner.context, func2), undefined); }); }); + + describe("versioning projection", () => { + it("basic default version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 3); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1", "v2", "v3"]); + const get = sdkPackage.clients[0].methods.find((x) => x.name === "get"); + ok(get); + deepStrictEqual(get.apiVersions, ["v2", "v3"]); + const test = sdkPackage.clients[0].methods.find((x) => x.name === "test"); + ok(test); + deepStrictEqual(test.apiVersions, ["v2", "v3"]); + const returnValue = test.response; + ok(returnValue); + strictEqual((returnValue as SdkMethodResponse).type, undefined); + strictEqual(sdkPackage.models.length, 2); + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1", "v2", "v3"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1", "v2", "v3"]); + const description = widget?.properties.find((x) => x.name === "description"); + ok(description); + deepStrictEqual(description.apiVersions, ["v1", "v2", "v3"]); // rename or change type will not change the apiVersions + strictEqual(description.optional, true); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1", "v2", "v3"]); + strictEqual(sdkPackage.enums.length, 1); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + + it("basic latest version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "latest", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 3); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1", "v2", "v3"]); + const get = sdkPackage.clients[0].methods.find((x) => x.name === "get"); + ok(get); + deepStrictEqual(get.apiVersions, ["v2", "v3"]); + const test = sdkPackage.clients[0].methods.find((x) => x.name === "test"); + ok(test); + deepStrictEqual(test.apiVersions, ["v2", "v3"]); + const returnValue = test.response; + ok(returnValue); + strictEqual((returnValue as SdkMethodResponse).type, undefined); + strictEqual(sdkPackage.models.length, 2); + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1", "v2", "v3"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1", "v2", "v3"]); + const description = widget?.properties.find((x) => x.name === "description"); + ok(description); + deepStrictEqual(description.apiVersions, ["v1", "v2", "v3"]); // rename or change type will not change the apiVersions + strictEqual(description.optional, true); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1", "v2", "v3"]); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + + it("basic v3 version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "v3", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 3); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1", "v2", "v3"]); + const get = sdkPackage.clients[0].methods.find((x) => x.name === "get"); + ok(get); + deepStrictEqual(get.apiVersions, ["v2", "v3"]); + const test = sdkPackage.clients[0].methods.find((x) => x.name === "test"); + ok(test); + deepStrictEqual(test.apiVersions, ["v2", "v3"]); + const returnValue = test.response; + ok(returnValue); + strictEqual((returnValue as SdkMethodResponse).type, undefined); + strictEqual(sdkPackage.models.length, 2); + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1", "v2", "v3"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1", "v2", "v3"]); + const description = widget?.properties.find((x) => x.name === "description"); + ok(description); + deepStrictEqual(description.apiVersions, ["v1", "v2", "v3"]); // rename or change type will not change the apiVersions + strictEqual(description.optional, true); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1", "v2", "v3"]); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + + it("basic v2 version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "v2", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 3); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1", "v2"]); + const get = sdkPackage.clients[0].methods.find((x) => x.name === "get"); + ok(get); + deepStrictEqual(get.apiVersions, ["v2"]); + const test = sdkPackage.clients[0].methods.find((x) => x.name === "test"); + ok(test); + deepStrictEqual(test.apiVersions, ["v2"]); + const returnValue = test.response; + ok(returnValue); + strictEqual((returnValue as SdkMethodResponse).type?.kind, "model"); + strictEqual(sdkPackage.models.length, 3); + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1", "v2"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1", "v2"]); + const name = widget?.properties.find((x) => x.name === "name"); + ok(name); + deepStrictEqual(name.apiVersions, ["v1", "v2"]); + strictEqual(name.optional, false); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1", "v2"]); + const testModel = sdkPackage.models.find((x) => x.name === "Test"); + ok(testModel); + deepStrictEqual(testModel.apiVersions, ["v2"]); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + + it("basic v1 version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "v1", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 1); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1"]); + strictEqual(sdkPackage.models.length, 2); + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1"]); + const name = widget?.properties.find((x) => x.name === "name"); + ok(name); + deepStrictEqual(name.apiVersions, ["v1"]); + strictEqual(name.optional, false); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1"]); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + + it("basic all version", async () => { + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "all", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + + @error + model Error { + code: string; + message?: string; + } + + model Widget { + @key + @typeChangedFrom(Versions.v3, string) + id: int32; + + @renamedFrom(Versions.v3, "name") + @madeOptional(Versions.v3) + description?: string; + } + + @added(Versions.v2) + @removed(Versions.v3) + model Test { + prop1: string; + } + + @route("/test") + @added(Versions.v2) + @returnTypeChangedFrom(Versions.v3, Test) + op test(): void | Error; + + op list(): Widget[] | Error; + + @added(Versions.v2) + @route("/widget/{id}") + op get(...Resource.KeysOf): Widget | Error; + `); + + const sdkPackage = runnerWithVersion.context.experimental_sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + strictEqual(sdkPackage.clients[0].methods.length, 3); + const list = sdkPackage.clients[0].methods.find((x) => x.name === "list"); + ok(list); + deepStrictEqual(list.apiVersions, ["v1", "v2", "v3"]); + const get = sdkPackage.clients[0].methods.find((x) => x.name === "get"); + ok(get); + deepStrictEqual(get.apiVersions, ["v2", "v3"]); + const test = sdkPackage.clients[0].methods.find((x) => x.name === "test"); + ok(test); + deepStrictEqual(test.apiVersions, ["v2", "v3"]); + const returnValue = test.response; + ok(returnValue); + strictEqual((returnValue as SdkMethodResponse).type, undefined); + strictEqual(sdkPackage.models.length, 2); // TODO: since Test model has no usage, we could not get it, need to fix + const widget = sdkPackage.models.find((x) => x.name === "Widget"); + ok(widget); + deepStrictEqual(widget.apiVersions, ["v1", "v2", "v3"]); + strictEqual(widget?.properties.length, 2); + const id = widget?.properties.find((x) => x.name === "id"); + ok(id); + deepStrictEqual(id.apiVersions, ["v1", "v2", "v3"]); + const description = widget?.properties.find((x) => x.name === "description"); + ok(description); + deepStrictEqual(description.apiVersions, ["v1", "v2", "v3"]); // rename or change type will not change the apiVersions + strictEqual(description.optional, true); + const error = sdkPackage.models.find((x) => x.name === "Error"); + ok(error); + deepStrictEqual(error.apiVersions, ["v1", "v2", "v3"]); + // const testModel = sdkPackage.models.find(x => x.name === "Test"); + // ok(testModel); + // deepStrictEqual(testModel.apiVersions, ["v2"]); + const versions = sdkPackage.enums.find((x) => x.name === "Versions"); + ok(versions); + deepStrictEqual( + versions.values.map((v) => v.value), + ["v1", "v2", "v3"] + ); + }); + }); }); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 8446f3ecea..6af61dddba 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -1734,7 +1734,12 @@ describe("typespec-client-generator-core: types", () => { strictEqual(values[1].kind, "int32"); }); it("versioning", async function () { - await runner.compile(` + const runnerWithVersion = await createSdkTestRunner({ + "api-version": "all", + emitterName: "@azure-tools/typespec-python", + }); + + await runnerWithVersion.compile(` @versioned(Versions) @service({title: "Widget Service"}) namespace DemoService; @@ -1760,7 +1765,7 @@ describe("typespec-client-generator-core: types", () => { removedProp: string; } `); - const sdkModel = runner.context.experimental_sdkPackage.models.find( + const sdkModel = runnerWithVersion.context.experimental_sdkPackage.models.find( (x) => x.kind === "model" ); ok(sdkModel);