diff --git a/.chronus/changes/fix_example_load-2024-7-27-14-24-44.md b/.chronus/changes/fix_example_load-2024-7-27-14-24-44.md new file mode 100644 index 0000000000..92d77656eb --- /dev/null +++ b/.chronus/changes/fix_example_load-2024-7-27-14-24-44.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +consider renaming when mapping examples \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/example.ts b/packages/typespec-client-generator-core/src/example.ts index 4e83c79b7f..c250815fb1 100644 --- a/packages/typespec-client-generator-core/src/example.ts +++ b/packages/typespec-client-generator-core/src/example.ts @@ -3,11 +3,14 @@ import { Diagnostic, DiagnosticCollector, NoTarget, + Operation, createDiagnosticCollector, + isGlobalNamespace, + isService, resolvePath, } from "@typespec/compiler"; import { HttpStatusCodeRange } from "@typespec/http"; -import { resolveOperationId } from "@typespec/openapi"; +import { getOperationId } from "@typespec/openapi"; import { SdkAnyExample, SdkArrayExample, @@ -37,6 +40,7 @@ import { } from "./interfaces.js"; import { getValidApiVersion } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; +import { getLibraryName } from "./public-utils.js"; interface LoadedExample { readonly relativePath: string; @@ -139,6 +143,30 @@ async function loadExamples( return diagnostics.wrap(map); } +function resolveOperationId(context: TCGCContext, operation: Operation) { + const { program } = context; + // if @operationId was specified use that value + const explicitOperationId = getOperationId(program, operation); + if (explicitOperationId) { + return explicitOperationId; + } + + const operationName = getLibraryName(context, operation); + if (operation.interface) { + return `${getLibraryName(context, operation.interface)}_${operationName}`; + } + const namespace = operation.namespace; + if ( + namespace === undefined || + isGlobalNamespace(program, namespace) || + isService(program, namespace) + ) { + return operationName; + } + + return `${getLibraryName(context, namespace)}_${operationName}`; +} + export async function handleClientExamples( context: TCGCContext, client: SdkClientType @@ -157,7 +185,7 @@ export async function handleClientExamples( // since operation could have customization in client.tsp, we need to handle all the original operation (exclude the templated operation) let operation = method.__raw; while (operation && operation.templateMapper === undefined) { - const operationId = resolveOperationId(context.program, operation).toLowerCase(); + const operationId = resolveOperationId(context, operation).toLowerCase(); if (examples.has(operationId)) { diagnostics.pipe(handleMethodExamples(context, method, examples.get(operationId)!)); break; diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts b/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts index 6567da42af..21d3c84439 100644 --- a/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts +++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts @@ -109,6 +109,37 @@ describe("typespec-client-generator-core: http operation examples", () => { expectDiagnostics(runner.context.diagnostics, []); }); + it("body fallback", async () => { + await runner.host.addRealTypeSpecFile( + "./examples/parameters.json", + `${__dirname}/http-operation-examples/bodyFallback.json` + ); + await runner.compile(` + @service({}) + namespace TestClient { + op bodyTest(prop: string): void; + } + `); + + const operation = ( + runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod + ).operation; + ok(operation); + strictEqual(operation.examples?.length, 1); + strictEqual(operation.examples[0].kind, "http"); + + const parameters = operation.examples[0].parameters; + ok(parameters); + strictEqual(parameters.length, 1); + + strictEqual(parameters[0].value.kind, "model"); + strictEqual(parameters[0].value.value["prop"].kind, "string"); + strictEqual(parameters[0].value.value["prop"].value, "body"); + strictEqual(parameters[0].value.type.kind, "model"); + + expectDiagnostics(runner.context.diagnostics, []); + }); + it("parameters diagnostic", async () => { await runner.host.addRealTypeSpecFile( "./examples/parametersDiagnostic.json", diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/bodyFallback.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/bodyFallback.json new file mode 100644 index 0000000000..03c9106c2d --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/bodyFallback.json @@ -0,0 +1,10 @@ +{ + "operationId": "bodyTest", + "title": "bodyTest", + "parameters": { + "body": { + "prop": "body" + } + }, + "responses": {} +} diff --git a/packages/typespec-client-generator-core/test/examples/load.test.ts b/packages/typespec-client-generator-core/test/examples/load.test.ts index 73bc7bbf0b..7b914f8593 100644 --- a/packages/typespec-client-generator-core/test/examples/load.test.ts +++ b/packages/typespec-client-generator-core/test/examples/load.test.ts @@ -1,7 +1,7 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; -import { SdkHttpOperation, SdkServiceMethod } from "../../src/interfaces.js"; +import { SdkClientAccessor, SdkHttpOperation, SdkServiceMethod } from "../../src/interfaces.js"; import { SdkTestRunner, createSdkTestRunner } from "../test-host.js"; describe("typespec-client-generator-core: load examples", () => { @@ -169,4 +169,46 @@ describe("typespec-client-generator-core: load examples", () => { ok(operation); strictEqual(operation.examples?.length, 1); }); + + it("load multiple example with @clientName", async () => { + await runner.host.addRealTypeSpecFile( + "./examples/clientName.json", + `${__dirname}/load/clientName.json` + ); + await runner.host.addRealTypeSpecFile( + "./examples/clientNameAnother.json", + `${__dirname}/load/clientNameAnother.json` + ); + await runner.compile(` + @service({}) + namespace TestClient { + @clientName("renamedNS") + namespace NS { + @route("/ns") + @clientName("renamedOP") + op get(): string; + } + + @clientName("renamedIF") + namespace IF { + @route("/if") + @clientName("renamedOP") + op get(): string; + } + } + `); + + let operation = ( + (runner.context.sdkPackage.clients[0].methods[0] as SdkClientAccessor) + .response.methods[0] as SdkServiceMethod + ).operation; + ok(operation); + strictEqual(operation.examples?.length, 1); + operation = ( + (runner.context.sdkPackage.clients[0].methods[1] as SdkClientAccessor) + .response.methods[0] as SdkServiceMethod + ).operation; + ok(operation); + strictEqual(operation.examples?.length, 1); + }); }); diff --git a/packages/typespec-client-generator-core/test/examples/load/clientName.json b/packages/typespec-client-generator-core/test/examples/load/clientName.json new file mode 100644 index 0000000000..700b8351f5 --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/load/clientName.json @@ -0,0 +1,11 @@ +{ + "operationId": "renamedNS_renamedOP", + "title": "renamedNS_renamedOP", + "parameters": {}, + "responses": { + "200": { + "description": "ARM operation completed successfully.", + "body": "test" + } + } +} diff --git a/packages/typespec-client-generator-core/test/examples/load/clientNameAnother.json b/packages/typespec-client-generator-core/test/examples/load/clientNameAnother.json new file mode 100644 index 0000000000..c53d45eb73 --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/load/clientNameAnother.json @@ -0,0 +1,11 @@ +{ + "operationId": "renamedIF_renamedOP", + "title": "renamedIF_renamedOP", + "parameters": {}, + "responses": { + "200": { + "description": "ARM operation completed successfully.", + "body": "test" + } + } +}