Skip to content

Commit

Permalink
Add support for @clientName in autorest emitter (#173)
Browse files Browse the repository at this point in the history
fix #172

---------

Co-authored-by: Mark Cowlishaw <[email protected]>
  • Loading branch information
timotheeguerin and markcowl authored Feb 1, 2024
1 parent f306082 commit a1a2be7
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-insects-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@azure-tools/typespec-autorest": minor
---

Respect `@clientName` decorator from `@azure-rest/typespec-client-generator-core` library where `@projectedName("client")` used to work.
4 changes: 3 additions & 1 deletion packages/typespec-autorest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
],
"peerDependencies": {
"@typespec/compiler": "workspace:~0.52.0",
"@azure-tools/typespec-azure-core": "~0.38.0",
"@azure-tools/typespec-azure-core": "workspace:~0.38.0",
"@azure-tools/typespec-client-generator-core": "workspace:~0.38.0",
"@typespec/http": "workspace:~0.52.0",
"@typespec/rest": "workspace:~0.52.0",
"@typespec/openapi": "workspace:~0.52.0",
Expand All @@ -65,6 +66,7 @@
"@typespec/compiler": "workspace:~0.52.0",
"@typespec/json-schema": "workspace:~0.52.0",
"@azure-tools/typespec-azure-core": "workspace:~0.38.0",
"@azure-tools/typespec-client-generator-core": "workspace:~0.38.0",
"@typespec/http": "workspace:~0.52.0",
"@typespec/rest": "workspace:~0.52.0",
"@typespec/openapi": "workspace:~0.52.0",
Expand Down
25 changes: 20 additions & 5 deletions packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
getUnionAsEnum,
isFixed,
} from "@azure-tools/typespec-azure-core";
import {
SdkContext,
createSdkContext,
getClientNameOverride,
} from "@azure-tools/typespec-client-generator-core";
import {
ArrayModelType,
BooleanLiteral,
Expand Down Expand Up @@ -152,6 +157,7 @@ const defaultOptions = {

export async function $onEmit(context: EmitContext<AutorestEmitterOptions>) {
const resolvedOptions = { ...defaultOptions, ...context.options };
const tcgcSdkContext = createSdkContext(context, "@azure-tools/typespec-autorest");
const armTypesDir = interpolatePath(
resolvedOptions["arm-types-dir"] ?? "{project-root}/../../common-types/resource-management",
{
Expand All @@ -172,7 +178,7 @@ export async function $onEmit(context: EmitContext<AutorestEmitterOptions>) {
useReadOnlyStatusSchema: resolvedOptions["use-read-only-status-schema"],
};

const emitter = createOAPIEmitter(context.program, options);
const emitter = createOAPIEmitter(context.program, tcgcSdkContext, options);
await emitter.emitOpenAPI();
}

Expand Down Expand Up @@ -262,7 +268,11 @@ interface ProcessedSchema extends PendingSchema {
schema: OpenAPI2Schema | undefined;
}

function createOAPIEmitter(program: Program, options: ResolvedAutorestEmitterOptions) {
function createOAPIEmitter(
program: Program,
tcgcSdkContext: SdkContext,
options: ResolvedAutorestEmitterOptions
) {
const tracer = getTracer(program);
tracer.trace("options", JSON.stringify(options, null, 2));
const typeNameOptions: TypeNameOptions = {
Expand Down Expand Up @@ -332,8 +342,7 @@ function createOAPIEmitter(program: Program, options: ResolvedAutorestEmitterOpt
program,
service,
version: record.version,
jsonView,
clientView,
getClientName,
};
const projectedServiceNs: Namespace = projectedProgram
? (projectedProgram.projector.projectedTypes.get(service.type) as Namespace)
Expand Down Expand Up @@ -1015,6 +1024,12 @@ function createOAPIEmitter(program: Program, options: ResolvedAutorestEmitterOpt
return encodedName === type.name ? viaProjection : encodedName;
}

function getClientName(type: Type & { name: string }): string {
const viaProjection = clientView.getProjectedName(type);
const clientName = getClientNameOverride(tcgcSdkContext, type);
return clientName ?? viaProjection;
}

function emitEndpointParameters(methodParams: HttpOperationParameters, visibility: Visibility) {
const consumes: string[] = methodParams.body?.contentTypes ?? [];

Expand Down Expand Up @@ -1676,7 +1691,7 @@ function createOAPIEmitter(program: Program, options: ResolvedAutorestEmitterOpt
}

const jsonName = getJsonName(prop);
const clientName = clientView.getProjectedName(prop);
const clientName = getClientName(prop);

const description = getDoc(program, prop);

Expand Down
12 changes: 4 additions & 8 deletions packages/typespec-autorest/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ModelProperty,
Operation,
Program,
ProjectedNameView,
Service,
Type,
getFriendlyName,
Expand All @@ -18,8 +17,7 @@ export interface AutorestEmitterContext {
program: Program;
service: Service;
version?: string;
jsonView: ProjectedNameView;
clientView: ProjectedNameView;
getClientName: (type: Type & { name: string }) => string;
}

/**
Expand Down Expand Up @@ -61,17 +59,15 @@ export function shouldInline(program: Program, type: Type): boolean {
* @returns Operation ID in this format `<name>` or `<group>_<name>`
*/
export function resolveOperationId(context: AutorestEmitterContext, operation: Operation) {
const { program, clientView } = context;
const { program, getClientName } = context;
const explicitOperationId = getOperationId(program, operation);
if (explicitOperationId) {
return explicitOperationId;
}

const operationName = clientView.getProjectedName(operation);
const operationName = getClientName(operation);
if (operation.interface) {
return pascalCaseForOperationId(
`${clientView.getProjectedName(operation.interface)}_${operationName}`
);
return pascalCaseForOperationId(`${getClientName(operation.interface)}_${operationName}`);
}
const namespace = operation.namespace;
if (
Expand Down
33 changes: 33 additions & 0 deletions packages/typespec-autorest/test/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ describe("typespec-autorest: model definitions", () => {
});
});

it(`@clientName(<>) set the "x-ms-client-name" with the original name`, async () => {
const res = await oapiForModel(
"Foo",
`model Foo {
@clientName("x")
xJson: int32;
};`
);

expect(res.defs.Foo).toMatchObject({
properties: {
xJson: { type: "integer", format: "int32", "x-ms-client-name": "x" },
},
});
});

it(`@clientName(<>) wins over @projectedName("client", <>)`, async () => {
const res = await oapiForModel(
"Foo",
`model Foo {
@clientName("x")
@projectedName("client", "y")
xJson: int32;
};`
);

expect(res.defs.Foo).toMatchObject({
properties: {
xJson: { type: "integer", format: "int32", "x-ms-client-name": "x" },
},
});
});

it("uses json name specified via @encodedName", async () => {
const res = await oapiForModel(
"Foo",
Expand Down
18 changes: 18 additions & 0 deletions packages/typespec-autorest/test/openapi-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,24 @@ describe("typespec-autorest: operations", () => {
strictEqual(res.paths["/interface-only"].get.operationId, "ClientInterfaceName_Same");
strictEqual(res.paths["/interface-and-op"].get.operationId, "ClientInterfaceName_ClientCall");
});

it(`@clientName(<>) updates the operationId`, async () => {
const res = await openApiFor(`
@service namespace MyService;
@route("/op-only") @clientName( "clientCall") op serviceName(): void;
@clientName( "ClientInterfaceName")
interface ServiceInterfaceName {
@route("/interface-only") same(): void;
@route("/interface-and-op") @clientName( "clientCall") serviceName(): void;
}
`);

strictEqual(res.paths["/op-only"].get.operationId, "ClientCall");
strictEqual(res.paths["/interface-only"].get.operationId, "ClientInterfaceName_Same");
strictEqual(res.paths["/interface-and-op"].get.operationId, "ClientInterfaceName_ClientCall");
});
});

describe("typespec-autorest: request", () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/typespec-autorest/test/test-host.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing";
import { SdkTestLibrary as TcgcTestLibrary } from "@azure-tools/typespec-client-generator-core/testing";
import { Diagnostic } from "@typespec/compiler";
import {
TestHost,
Expand All @@ -25,6 +26,7 @@ export async function createAutorestTestHost() {
AutorestTestLibrary,
VersioningTestLibrary,
AzureCoreTestLibrary,
TcgcTestLibrary,
],
});
}
Expand All @@ -42,6 +44,7 @@ export async function createAutorestTestRunner(
"TypeSpec.OpenAPI",
"Autorest",
"Azure.Core",
"Azure.ClientGenerator.Core",
],
compilerOptions: {
emitters: { [AutorestTestLibrary.name]: { ...emitterOptions } },
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a1a2be7

Please sign in to comment.