From abfd0f4b565de3edf9cfe56e344bd34bd7f07858 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 26 Mar 2024 17:43:07 +0800 Subject: [PATCH 01/12] fix wrong union `generatedName` flag and refine templated model naming --- packages/typespec-client-generator-core/src/types.ts | 2 +- packages/typespec-client-generator-core/test/package.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 9157e66c86..c1aff2a3eb 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -922,7 +922,7 @@ function getSdkCredentialType( values: credentialTypes, nullable: false, name: createGeneratedName(client.service, "CredentialUnion"), - generatedName: false, + generatedName: true, }; } return credentialTypes[0]; diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 7999129f4f..e491d30a9c 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -231,6 +231,8 @@ describe("typespec-client-generator-core: package", () => { strictEqual(credentialParam.onClient, true); strictEqual(credentialParam.optional, false); strictEqual(credentialParam.type.kind, "union"); + strictEqual(credentialParam.type.name, "ServiceCredentialUnion"); + strictEqual(credentialParam.type.generatedName, true); strictEqual(credentialParam.type.values.length, 2); const schemes = credentialParam.type.values .filter((v): v is SdkCredentialType => v.kind === "credential") From f552052ea8e3db5483621c92082f1f306b62527d Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 26 Mar 2024 17:43:45 +0800 Subject: [PATCH 02/12] changelog --- .chronus/changes/fix_tcgc_type_issue-2024-2-26-17-43-28.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/fix_tcgc_type_issue-2024-2-26-17-43-28.md diff --git a/.chronus/changes/fix_tcgc_type_issue-2024-2-26-17-43-28.md b/.chronus/changes/fix_tcgc_type_issue-2024-2-26-17-43-28.md new file mode 100644 index 0000000000..c9526950cf --- /dev/null +++ b/.chronus/changes/fix_tcgc_type_issue-2024-2-26-17-43-28.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +fix wrong union `generatedName` flag and refine templated model naming \ No newline at end of file From cb1265bd5b43c6e8724f93e32bcde062c2aa6e3a Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 26 Mar 2024 17:47:19 +0800 Subject: [PATCH 03/12] renaming --- packages/typespec-client-generator-core/src/public-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index b7c2b9b6ac..e770ae8c84 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -174,7 +174,7 @@ export function getLibraryName( // 5. if type is derived from template and name is the same as template, add template parameters' name as suffix if (typeof type.name === "string" && type.kind === "Model" && type.templateMapper?.args) { - return type.name + type.templateMapper.args.map((arg) => (arg as Model).name).join(""); + return type.name + type.templateMapper.args.map((arg) => pascalCase((arg as Model).name)).join(""); } return typeof type.name === "string" ? type.name : ""; From c01e284986cfa25bdae453ef06aff7508f9ce472 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Wed, 27 Mar 2024 11:38:01 +0800 Subject: [PATCH 04/12] format --- packages/typespec-client-generator-core/src/public-utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index e770ae8c84..19f3374aef 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -174,7 +174,9 @@ export function getLibraryName( // 5. if type is derived from template and name is the same as template, add template parameters' name as suffix if (typeof type.name === "string" && type.kind === "Model" && type.templateMapper?.args) { - return type.name + type.templateMapper.args.map((arg) => pascalCase((arg as Model).name)).join(""); + return ( + type.name + type.templateMapper.args.map((arg) => pascalCase((arg as Model).name)).join("") + ); } return typeof type.name === "string" ? type.name : ""; From d37388371e84b3a6a20d8f0b49abe67685d0cd22 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Thu, 28 Mar 2024 21:51:11 +0800 Subject: [PATCH 05/12] spread method parameter feature --- .../src/http.ts | 18 +-- .../src/interfaces.ts | 2 +- .../src/package.ts | 44 +++++- .../test/package.test.ts | 134 +++++++++++------- 4 files changed, 134 insertions(+), 64 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 07cd26f431..755842e27b 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -20,6 +20,7 @@ import { isPathParam, isQueryParam, } from "@typespec/http"; +import { camelCase } from "change-case"; import { CollectionFormat, SdkBodyParameter, @@ -123,10 +124,14 @@ function getSdkHttpParameters( if (getParamResponse.kind !== "body") throw new Error("blah"); retval.bodyParam = getParamResponse; } else { + const type = diagnostics.pipe( + getClientTypeWithDiagnostics(context, tspBody.type, httpOperation.operation) + ); + const name = camelCase((type as { name: string }).name ?? "body"); retval.bodyParam = { kind: "body", - name: "body", - nameInClient: "body", + name, + nameInClient: name, description: getDocHelper(context, tspBody.type).description, details: getDocHelper(context, tspBody.type).details, onClient: false, @@ -134,9 +139,7 @@ function getSdkHttpParameters( defaultContentType: "application/json", // actual content type info is added later isApiVersionParam: false, apiVersions: getAvailableApiVersions(context, tspBody.type), - type: diagnostics.pipe( - getClientTypeWithDiagnostics(context, tspBody.type, httpOperation.operation) - ), + type, optional: false, nullable: isNullable(tspBody.type), correspondingMethodParams, @@ -411,10 +414,7 @@ export function getCorrespondingMethodParams( const correspondingProperties = methodParameters.filter((x) => paramInProperties(x, serviceParamType) ); - const bodyPropertyNames = serviceParamType.properties.filter((x) => - paramInProperties(x, serviceParamType) - ); - if (correspondingProperties.length !== bodyPropertyNames.length) { + if (correspondingProperties.length !== serviceParamType.properties.length) { throw new Error("Can't find corresponding properties for spread body parameter"); } return correspondingProperties; diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index e87eaf0579..4980726f8f 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -256,7 +256,7 @@ export interface SdkEnumValueType extends SdkTypeBase { name: string; value: string | number; enumType: SdkEnumType; - valueType: SdkType; + valueType: SdkBuiltInType; description?: string; details?: string; } diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 0d6b7343da..88c3f0a790 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -1,6 +1,7 @@ import { getLroMetadata, getPagedResult } from "@azure-tools/typespec-azure-core"; import { Diagnostic, + Model, ModelProperty, Operation, createDiagnosticCollector, @@ -9,6 +10,7 @@ import { } from "@typespec/compiler"; import { getServers } from "@typespec/http"; import { resolveVersions } from "@typespec/versioning"; +import { camelCase } from "change-case"; import { getAccess, listClients, @@ -218,9 +220,24 @@ function getSdkBasicServiceMethod< ): [SdkServiceMethod, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); // when we spread, all of the inputtable properties of our model get flattened onto the method - const methodParameters = Array.from(operation.parameters.properties.values()) - .map((x) => diagnostics.pipe(getSdkMethodParameter(context, x))) - .filter((x): x is SdkMethodParameter => x.kind === "method"); + const methodParameters: SdkMethodParameter[] = []; + const spreadModelNames: string[] = []; + for (const prop of operation.parameters.properties.values()) { + if (prop.sourceProperty?.model?.name) { + if (!spreadModelNames.includes(prop.sourceProperty.model.name)) { + spreadModelNames.push(prop.sourceProperty.model.name); + methodParameters.push( + diagnostics.pipe(getSdkMethodParameter(context, prop.sourceProperty.model)) + ); + } + } else { + const methodParameter = diagnostics.pipe(getSdkMethodParameter(context, prop)); + if (methodParameter.kind === "method") { + methodParameters.push(methodParameter); + } + } + } + // if there's an api version parameter, we want to bubble it up to the client // we don't want it on the method level, but we will keep it on the service operation level const apiVersionParam = methodParameters.find((x) => x.isApiVersionParam); @@ -331,9 +348,28 @@ function getSdkInitializationType< function getSdkMethodParameter( context: TCGCContext, - type: ModelProperty + type: ModelProperty | Model ): [SdkMethodParameter, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); + if (type.kind === "Model") { + const name = camelCase(getLibraryName(context, type) ?? "body"); + const propertyType = diagnostics.pipe(getClientTypeWithDiagnostics(context, type)); + return diagnostics.wrap({ + kind: "method", + description: getDocHelper(context, type).description, + details: getDocHelper(context, type).details, + apiVersions: getAvailableApiVersions(context, type), + type: propertyType, + nameInClient: name, + name, + optional: false, + nullable: false, + discriminator: false, + serializedName: name, + onClient: false, + isApiVersionParam: false, + }); + } return diagnostics.wrap({ ...diagnostics.pipe(getSdkModelPropertyType(context, type)), kind: "method", diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index c8548211dc..712f7e9078 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -997,11 +997,11 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.parameters.length, 1); const pathMethod = method.parameters[0]; strictEqual(pathMethod.kind, "method"); - strictEqual(pathMethod.name, "name"); + strictEqual(pathMethod.name, "nameParameter"); strictEqual(pathMethod.optional, false); strictEqual(pathMethod.onClient, false); strictEqual(pathMethod.isApiVersionParam, false); - strictEqual(pathMethod.type.kind, "string"); + strictEqual(pathMethod.type.kind, "model"); strictEqual(pathMethod.nullable, false); const serviceOperation = method.operation; @@ -1018,7 +1018,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(pathParam.urlEncode, true); strictEqual(pathParam.nullable, false); strictEqual(pathParam.correspondingMethodParams.length, 1); - deepStrictEqual(pathParam.correspondingMethodParams[0], pathMethod); + deepStrictEqual(pathParam.correspondingMethodParams[0], pathMethod.type.properties[0]); }); it("header basic", async () => { @@ -1359,13 +1359,13 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.kind, "basic"); strictEqual(method.parameters.length, 2); - const methodParam = method.parameters.find((x) => x.name === "key"); + const methodParam = method.parameters.find((x) => x.name === "input"); ok(methodParam); strictEqual(methodParam.kind, "method"); strictEqual(methodParam.optional, false); strictEqual(methodParam.onClient, false); strictEqual(methodParam.isApiVersionParam, false); - strictEqual(methodParam.type.kind, "string"); + strictEqual(methodParam.type.kind, "model"); const contentTypeParam = method.parameters.find((x) => x.name === "contentType"); ok(contentTypeParam); @@ -1386,11 +1386,7 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = bodyParameter.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - strictEqual( - bodyParameter.type.properties[0].nameInClient, //eslint-disable-line deprecation/deprecation - correspondingMethodParams[0].nameInClient //eslint-disable-line deprecation/deprecation - ); - strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); + strictEqual(bodyParameter.type, correspondingMethodParams[0].type); }); it("body alias spread", async () => { @@ -1448,6 +1444,68 @@ describe("typespec-client-generator-core: package", () => { strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); }); + it("spread with discriminate type", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + + @discriminator("kind") + model Pet { + name?: string; + } + + model Dog { + kind: "dog"; + } + + model Cat { + kind: "cat"; + } + + op test(...Pet): void; + `); + const sdkPackage = runner.context.experimental_sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(sdkPackage.models.length, 1); + strictEqual(method.name, "test"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 2); + + const methodParam = method.parameters.find((x) => x.name === "pet"); + ok(methodParam); + strictEqual(methodParam.kind, "method"); + strictEqual(methodParam.optional, false); + strictEqual(methodParam.onClient, false); + strictEqual(methodParam.isApiVersionParam, false); + strictEqual(methodParam.type.kind, "model"); + + const contentTypeMethodParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethodParam); + strictEqual(contentTypeMethodParam.clientDefaultValue, undefined); + strictEqual(contentTypeMethodParam.type.kind, "constant"); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + strictEqual(bodyParameter.kind, "body"); + deepStrictEqual(bodyParameter.contentTypes, ["application/json"]); + strictEqual(bodyParameter.defaultContentType, "application/json"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type.kind, "model"); + strictEqual(bodyParameter.type.properties.length, 2); + //eslint-disable-next-line deprecation/deprecation + strictEqual(bodyParameter.type.properties[0].nameInClient, "kind"); + strictEqual(bodyParameter.type.properties[0].name, "kind"); + //eslint-disable-next-line deprecation/deprecation + strictEqual(bodyParameter.type.properties[1].nameInClient, "name"); + strictEqual(bodyParameter.type.properties[1].name, "name"); + + const correspondingMethodParams = bodyParameter.correspondingMethodParams; + strictEqual(correspondingMethodParams.length, 1); + strictEqual(bodyParameter.type, correspondingMethodParams[0].type); + }); + it("parameter grouping", async () => { await runner.compile(`@server("http://localhost:3000", "endpoint") @service({}) @@ -1995,18 +2053,18 @@ describe("typespec-client-generator-core: package", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(method.name, "create"); strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 5); + strictEqual(method.parameters.length, 3); deepStrictEqual( method.parameters.map((x) => x.name), - ["id", "weight", "color", "contentType", "accept"] + ["widget", "contentType", "accept"] ); const bodyParameter = method.operation.bodyParam; ok(bodyParameter); strictEqual(bodyParameter.kind, "body"); //eslint-disable-next-line deprecation/deprecation - strictEqual(bodyParameter.nameInClient, "body"); - strictEqual(bodyParameter.name, "body"); + strictEqual(bodyParameter.nameInClient, "widget"); + strictEqual(bodyParameter.name, "widget"); strictEqual(bodyParameter.onClient, false); strictEqual(bodyParameter.optional, false); strictEqual(bodyParameter.type.kind, "model"); @@ -2106,41 +2164,17 @@ describe("typespec-client-generator-core: package", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(method.name, "update"); strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 5); - - const methodParamId = method.parameters[0]; - strictEqual(methodParamId.kind, "method"); - //eslint-disable-next-line deprecation/deprecation - strictEqual(methodParamId.nameInClient, "id"); - strictEqual(methodParamId.name, "id"); - strictEqual(methodParamId.optional, false); - strictEqual(methodParamId.onClient, false); - strictEqual(methodParamId.isApiVersionParam, false); - strictEqual(methodParamId.type.kind, "string"); - - const methodParamWeight = method.parameters[1]; - strictEqual(methodParamWeight.kind, "method"); - //eslint-disable-next-line deprecation/deprecation - strictEqual(methodParamWeight.nameInClient, "weight"); - strictEqual(methodParamWeight.name, "weight"); - strictEqual(methodParamWeight.optional, false); - strictEqual(methodParamWeight.onClient, false); - strictEqual(methodParamWeight.isApiVersionParam, false); - strictEqual(methodParamWeight.type.kind, "int32"); - - const methodParamColor = method.parameters[2]; - strictEqual(methodParamColor.kind, "method"); - //eslint-disable-next-line deprecation/deprecation - strictEqual(methodParamColor.nameInClient, "color"); - strictEqual(methodParamColor.name, "color"); - strictEqual(methodParamColor.optional, false); - strictEqual(methodParamColor.onClient, false); - strictEqual(methodParamColor.isApiVersionParam, false); - strictEqual(methodParamColor.type.kind, "enum"); - strictEqual(methodParamColor.type.values[0].value, "red"); - strictEqual(methodParamColor.type.values[0].valueType.kind, "string"); - strictEqual(methodParamColor.type.values[1].value, "blue"); - strictEqual(methodParamColor.type.values[1].valueType.kind, "string"); + strictEqual(method.parameters.length, 3); + + const methodParam = method.parameters[0]; + strictEqual(methodParam.kind, "method"); + //eslint-disable-next-line deprecation/deprecation + strictEqual(methodParam.nameInClient, "widget"); + strictEqual(methodParam.name, "widget"); + strictEqual(methodParam.optional, false); + strictEqual(methodParam.onClient, false); + strictEqual(methodParam.isApiVersionParam, false); + strictEqual(methodParam.type.kind, "model"); const methodContentTypeParam = method.parameters.find((x) => x.name === "contentType"); ok(methodContentTypeParam); @@ -2194,7 +2228,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(operationAcceptParam.optional, false); const correspondingMethodParams = bodyParameter.correspondingMethodParams.map((x) => x.name); - deepStrictEqual(correspondingMethodParams, ["weight", "color"]); + deepStrictEqual(correspondingMethodParams, ["widget"]); deepStrictEqual( bodyParameter.type.properties.map((p) => p.name), ["id", "weight", "color"] From 09a9adc3d8a8f3f6030dec0274bb3bed13f0e3a4 Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Thu, 28 Mar 2024 22:04:28 +0800 Subject: [PATCH 06/12] Update packages/typespec-client-generator-core/test/package.test.ts Co-authored-by: Timothee Guerin --- packages/typespec-client-generator-core/test/package.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 712f7e9078..e81a7467a9 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -1444,7 +1444,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); }); - it("spread with discriminate type", async () => { + it("spread with discriminate type with implicit property", async () => { await runner.compile(`@server("http://localhost:3000", "endpoint") @service({}) namespace My.Service; From f59d01657f7a34a54438c8d3cd4067928efb8f1d Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 Mar 2024 14:38:21 -0400 Subject: [PATCH 07/12] handle case with multiple method param properties mapping to body param --- .../src/http.ts | 29 ++++++----- .../test/types.test.ts | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 755842e27b..4080731533 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -401,23 +401,26 @@ export function getCorrespondingMethodParams( if (correspondingMethodParameter) { return [correspondingMethodParameter]; } - function paramInProperties(param: SdkModelPropertyType, type: SdkType): boolean { - if (type.kind !== "model") return false; - return Array.from(type.properties.values()) - .filter((x) => x.kind === "property") - .map((x) => x.name) - .includes(param.name); - } + const serviceParamType = serviceParam.type; if (serviceParam.kind === "body" && serviceParamType.kind === "model") { - // Here we have a spread body parameter - const correspondingProperties = methodParameters.filter((x) => - paramInProperties(x, serviceParamType) + const serviceParamPropertyNames = Array.from(serviceParamType.properties.values()).map( + (x) => x.name ); - if (correspondingProperties.length !== serviceParamType.properties.length) { - throw new Error("Can't find corresponding properties for spread body parameter"); + // Here we have a spread method parameter + let correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => + serviceParamPropertyNames.includes(x.name) + ); + if (correspondingProperties.length === serviceParamType.properties.length) + return correspondingProperties; + // now, we check if the service param properties are a subset of the method params + if (methodParameters.length === 1 && methodParameters[0].type.kind === "model") { + correspondingProperties = methodParameters[0].type.properties.filter((x) => + serviceParamPropertyNames.includes(x.name) + ); + if (correspondingProperties) return correspondingProperties; } - return correspondingProperties; + throw new Error("Can't find corresponding properties for spread body parameter"); } for (const methodParam of methodParameters) { if (methodParam.type.kind === "model") { diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 6a389c42f4..b9eb6e941c 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -2983,6 +2983,57 @@ describe("typespec-client-generator-core: types", () => { strictEqual(errorResponse.isFormDataType, false); ok((errorResponse.usage & UsageFlags.MultipartFormData) === 0); }); + + it("expands model into formData parameters", async function () { + await runner.compileWithBuiltInService(` + @doc("A widget.") + model Widget { + @key("widgetName") + name: string; + displayName: string; + description: string; + color: string; + } + + model WidgetForm is Widget { + @header("content-type") + contentType: "multipart/form-data"; + } + + @route("/widgets") + interface Widgets { + @route(":upload") + @post + upload(...WidgetForm): Widget; + } + `); + const formDataMethod = runner.context.experimental_sdkPackage.clients[0].methods[0]; + strictEqual(formDataMethod.kind, "basic"); + strictEqual(formDataMethod.name, "upload"); + strictEqual(formDataMethod.parameters.length, 2); + + const widgetFormParam = formDataMethod.parameters.find((x) => x.name === "widgetForm"); + ok(widgetFormParam); + ok(formDataMethod.parameters.find((x) => x.name === "accept")); + strictEqual(formDataMethod.parameters[0].name, "widgetForm"); + strictEqual(formDataMethod.parameters[0].type.kind, "model"); + strictEqual(formDataMethod.parameters[0].type.name, "WidgetForm"); + + const formDataOp = formDataMethod.operation; + strictEqual(formDataOp.parameters.length, 2); + ok(formDataOp.parameters.find((x) => x.name === "accept" && x.kind === "header")); + ok(formDataOp.parameters.find((x) => x.name === "contentType" && x.kind === "header")); + + const formDataBodyParam = formDataOp.bodyParam; + ok(formDataBodyParam); + strictEqual(formDataBodyParam.type.kind, "model"); + strictEqual(formDataBodyParam.type.name, "Widget"); + strictEqual(formDataBodyParam.correspondingMethodParams.length, 4); + deepStrictEqual( + formDataBodyParam.correspondingMethodParams.map((x) => x.name).sort(), + ["color", "description", "displayName", "name"].sort() + ); + }); }); describe("SdkTupleType", () => { it("model with tupled properties", async function () { From 7758cb2940a9569e47c0acba98eefeefcd6aa712 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 Mar 2024 16:50:17 -0400 Subject: [PATCH 08/12] fix api version test --- .../typespec-client-generator-core/src/http.ts | 15 +++++++++++---- .../src/internal-utils.ts | 2 +- .../typespec-client-generator-core/src/package.ts | 4 ++-- .../src/public-utils.ts | 10 ++-------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 4080731533..b9596c7e4f 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -394,7 +394,14 @@ export function getCorrespondingMethodParams( serviceParam: SdkHttpParameter ): SdkModelPropertyType[] { if (serviceParam.isApiVersionParam) { - if (!context.__api_version_parameter) throw new Error("No api version on the client"); + if (!context.__api_version_parameter) { + const apiVersionParam = methodParameters.find((x) => x.name.includes("apiVersion")); + if (!apiVersionParam) { + throw new Error("Can't find corresponding api version parameter"); + } + if (apiVersionParam.type.kind === "model") throw new Error(apiVersionParam.type.name); + throw new Error(apiVersionParam.type.kind); + } return [context.__api_version_parameter]; } const correspondingMethodParameter = methodParameters.find((x) => x.name === serviceParam.name); @@ -404,9 +411,9 @@ export function getCorrespondingMethodParams( const serviceParamType = serviceParam.type; if (serviceParam.kind === "body" && serviceParamType.kind === "model") { - const serviceParamPropertyNames = Array.from(serviceParamType.properties.values()).map( - (x) => x.name - ); + const serviceParamPropertyNames = Array.from(serviceParamType.properties.values()) + .filter((x) => x.kind === "property") + .map((x) => x.name); // Here we have a spread method parameter let correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => serviceParamPropertyNames.includes(x.name) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 5995ad4a23..a3186746b8 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -82,7 +82,7 @@ export function getClientNamespaceStringHelper( */ export function updateWithApiVersionInformation( context: TCGCContext, - type: ModelProperty + type: ModelProperty | Model ): { isApiVersionParam: boolean; clientDefaultValue?: unknown; diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 88c3f0a790..320083c548 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -55,6 +55,7 @@ import { getHashForType, getSdkTypeBaseHelper, isNullable, + updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; import { @@ -366,8 +367,7 @@ function getSdkMethodParameter( nullable: false, discriminator: false, serializedName: name, - onClient: false, - isApiVersionParam: false, + ...updateWithApiVersionInformation(context, type), }); } return diagnostics.wrap({ diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 19f3374aef..3a7ef7ac0c 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -19,7 +19,6 @@ import { } from "@typespec/compiler"; import { HttpOperation, - HttpOperationParameter, getHeaderFieldName, getHttpOperation, getPathParamName, @@ -62,13 +61,8 @@ export function getDefaultApiVersion( * @param parameter * @returns */ -export function isApiVersion( - context: TCGCContext, - parameter: HttpOperationParameter | ModelProperty -): boolean { - return ( - parameter.name.toLowerCase() === "apiversion" || parameter.name.toLowerCase() === "api-version" - ); +export function isApiVersion(context: TCGCContext, type: { name: string }): boolean { + return ["apiversion", "apiversionparameter", "api-version"].includes(type.name.toLowerCase()); } /** From 0c77ac77977c25f025f564de95835229f1f95b71 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 Mar 2024 18:42:51 -0400 Subject: [PATCH 09/12] can repro --- .../src/http.ts | 18 ++-- .../src/internal-utils.ts | 3 +- .../src/package.ts | 3 +- .../src/public-utils.ts | 6 +- .../test/package.test.ts | 95 +++++++++++++++++++ .../test/types.test.ts | 5 +- 6 files changed, 117 insertions(+), 13 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index b9596c7e4f..a764ef9df0 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -415,19 +415,23 @@ export function getCorrespondingMethodParams( .filter((x) => x.kind === "property") .map((x) => x.name); // Here we have a spread method parameter - let correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => + const correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => serviceParamPropertyNames.includes(x.name) ); if (correspondingProperties.length === serviceParamType.properties.length) return correspondingProperties; // now, we check if the service param properties are a subset of the method params - if (methodParameters.length === 1 && methodParameters[0].type.kind === "model") { - correspondingProperties = methodParameters[0].type.properties.filter((x) => - serviceParamPropertyNames.includes(x.name) - ); - if (correspondingProperties) return correspondingProperties; + for (const methodParam of methodParameters) { + if (methodParam.type.kind === "model") { + correspondingProperties.concat( + methodParam.type.properties.filter((x) => + serviceParamPropertyNames.includes(x.name) + ) + ) + } } - throw new Error("Can't find corresponding properties for spread body parameter"); + if (correspondingProperties) return correspondingProperties; + throw new Error(`Can't find corresponding parameter for ${serviceParam.name} out of ${methodParameters.map((m) => m.name).join(", ")}`); } for (const methodParam of methodParameters) { if (methodParam.type.kind === "model") { diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index a3186746b8..f8d8d0007e 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -1,7 +1,6 @@ import { getUnionAsEnum } from "@azure-tools/typespec-azure-core"; import { Model, - ModelProperty, Namespace, Operation, Program, @@ -82,7 +81,7 @@ export function getClientNamespaceStringHelper( */ export function updateWithApiVersionInformation( context: TCGCContext, - type: ModelProperty | Model + type: { name: string } ): { isApiVersionParam: boolean; clientDefaultValue?: unknown; diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 320083c548..1f44805181 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -7,6 +7,7 @@ import { createDiagnosticCollector, getNamespaceFullName, getService, + isKey, } from "@typespec/compiler"; import { getServers } from "@typespec/http"; import { resolveVersions } from "@typespec/versioning"; @@ -224,7 +225,7 @@ function getSdkBasicServiceMethod< const methodParameters: SdkMethodParameter[] = []; const spreadModelNames: string[] = []; for (const prop of operation.parameters.properties.values()) { - if (prop.sourceProperty?.model?.name) { + if (prop.sourceProperty?.model?.name && !isKey(context.program, prop.sourceProperty)) { if (!spreadModelNames.includes(prop.sourceProperty.model.name)) { spreadModelNames.push(prop.sourceProperty.model.name); methodParameters.push( diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 3a7ef7ac0c..a47f78ea37 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -169,7 +169,11 @@ export function getLibraryName( // 5. if type is derived from template and name is the same as template, add template parameters' name as suffix if (typeof type.name === "string" && type.kind === "Model" && type.templateMapper?.args) { return ( - type.name + type.templateMapper.args.map((arg) => pascalCase((arg as Model).name)).join("") + type.name + + type.templateMapper.args + .filter((arg): arg is Model => arg.kind === "Model" && arg.name.length > 0) + .map((arg) => pascalCase(arg.name)) + .join("") ); } diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index e81a7467a9..5440bbe002 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -2859,7 +2859,102 @@ describe("typespec-client-generator-core: package", () => { ok(clientRequestIdProperty); strictEqual(clientRequestIdProperty.kind, "header"); }); + + it("multiple spread", async () => { + await runner.compile(` + @service({ + title: "Pet Store Service", + }) + namespace PetStore; + using TypeSpec.Rest.Resource; + + @error + model PetStoreError { + code: int32; + message: string; + } + + @resource("pets") + model Pet { + @key("petId") + id: int32; + } + + @resource("checkups") + model Checkup { + @key("checkupId") + id: int32; + + vetName: string; + notes: string; + } + + interface PetCheckups + extends ExtensionResourceCreateOrUpdate, + ExtensionResourceList {} + `); + const a = "b"; + }); }); + describe("versioning", () => { + it("versioned service", async () => { + await runner.compile(` + @service({ + title: "Pet Store Service", + }) + @versioned(Versions) + namespace VersionedApi; + enum Versions { + v1, + v2, + } + + model ApiVersionParam { + @header apiVersion: Versions; + } + + @discriminator("type") + model PetBase { + name: string; + } + + model Dog extends PetBase { + type: "dog"; + nextWalkTime: utcDateTime; + + @madeOptional(Versions.v2) + walkerName?: string; + + @added(Versions.v2) + commandList: string[]; + } + + @added(Versions.v2) + model Cat extends PetBase { + type: "cat"; + catnipDose: int32; + } + + @route("/") + interface MyService { + getPet(...ApiVersionParam): PetBase; + + @added(Versions.v2) + @post + @route("/walkDog") + walkDog(...ApiVersionParam): OkResponse; + + @removed(Versions.v2) + @post + @route("/walkCat") + walkCat(...ApiVersionParam): OkResponse; + } + + `) + + }); + + }) }); function getServiceMethodOfClient( diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index b9eb6e941c..1f85582428 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -3010,7 +3010,7 @@ describe("typespec-client-generator-core: types", () => { const formDataMethod = runner.context.experimental_sdkPackage.clients[0].methods[0]; strictEqual(formDataMethod.kind, "basic"); strictEqual(formDataMethod.name, "upload"); - strictEqual(formDataMethod.parameters.length, 2); + strictEqual(formDataMethod.parameters.length, 3); const widgetFormParam = formDataMethod.parameters.find((x) => x.name === "widgetForm"); ok(widgetFormParam); @@ -3020,7 +3020,8 @@ describe("typespec-client-generator-core: types", () => { strictEqual(formDataMethod.parameters[0].type.name, "WidgetForm"); const formDataOp = formDataMethod.operation; - strictEqual(formDataOp.parameters.length, 2); + strictEqual(formDataOp.parameters.length, 3); + ok(formDataOp.parameters.find((x) => x.name === "widgetName")); ok(formDataOp.parameters.find((x) => x.name === "accept" && x.kind === "header")); ok(formDataOp.parameters.find((x) => x.name === "contentType" && x.kind === "header")); From 4753a2a5dfef838661cd3c659ecb2e4ed8d7f518 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sat, 30 Mar 2024 18:41:24 -0400 Subject: [PATCH 10/12] get tests passing --- .../src/http.ts | 26 ++--- .../src/public-utils.ts | 5 +- .../test/package.test.ts | 102 +++++++++--------- .../test/public-utils.test.ts | 2 +- .../test/types.test.ts | 11 +- 5 files changed, 75 insertions(+), 71 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index a764ef9df0..2d07a2d148 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -415,23 +415,25 @@ export function getCorrespondingMethodParams( .filter((x) => x.kind === "property") .map((x) => x.name); // Here we have a spread method parameter - const correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => + let correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => serviceParamPropertyNames.includes(x.name) ); - if (correspondingProperties.length === serviceParamType.properties.length) - return correspondingProperties; - // now, we check if the service param properties are a subset of the method params for (const methodParam of methodParameters) { - if (methodParam.type.kind === "model") { - correspondingProperties.concat( - methodParam.type.properties.filter((x) => - serviceParamPropertyNames.includes(x.name) + const methodParamIterable = + methodParam.type.kind === "model" ? methodParam.type.properties : [methodParam]; + correspondingProperties = correspondingProperties.concat( + methodParamIterable.filter( + (x) => + serviceParamPropertyNames.includes(x.name) && + !correspondingProperties.find((e) => e.name === x.name) ) - ) - } + ); } - if (correspondingProperties) return correspondingProperties; - throw new Error(`Can't find corresponding parameter for ${serviceParam.name} out of ${methodParameters.map((m) => m.name).join(", ")}`); + if (correspondingProperties.length === serviceParamType.properties.length) + return correspondingProperties; + throw new Error( + `Can't find corresponding parameter for ${serviceParam.name} out of ${methodParameters.map((m) => m.name).join(", ")}` + ); } for (const methodParam of methodParameters) { if (methodParam.type.kind === "model") { diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index a47f78ea37..c4a72aa314 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -62,7 +62,10 @@ export function getDefaultApiVersion( * @returns */ export function isApiVersion(context: TCGCContext, type: { name: string }): boolean { - return ["apiversion", "apiversionparameter", "api-version"].includes(type.name.toLowerCase()); + return ( + type.name.toLowerCase().includes("apiversion") || + type.name.toLowerCase().includes("api-version") + ); } /** diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 5440bbe002..8db4f7f785 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -2893,68 +2893,66 @@ describe("typespec-client-generator-core: package", () => { extends ExtensionResourceCreateOrUpdate, ExtensionResourceList {} `); - const a = "b"; + const sdkPackage = runner.context.experimental_sdkPackage; + strictEqual(sdkPackage.models.length, 4); + deepStrictEqual( + sdkPackage.models.map((x) => x.name).sort(), + ["CheckupCollectionWithNextLink", "Checkup", "PetStoreError", "CheckupUpdate"].sort() + ); + const createOrUpdate = sdkPackage.clients[0].methods[0]; + strictEqual(createOrUpdate.kind, "basic"); + strictEqual(createOrUpdate.name, "createOrUpdate"); + strictEqual(createOrUpdate.parameters.length, 5); + strictEqual(createOrUpdate.parameters[0].name, "petId"); + strictEqual(createOrUpdate.parameters[1].name, "checkupId"); + strictEqual(createOrUpdate.parameters[2].name, "resource"); + strictEqual(createOrUpdate.parameters[2].type.kind, "model"); + strictEqual(createOrUpdate.parameters[2].type.name, "CheckupUpdate"); + strictEqual(createOrUpdate.parameters[3].name, "contentType"); + strictEqual(createOrUpdate.parameters[4].name, "accept"); + + const opParams = createOrUpdate.operation.parameters; + strictEqual(opParams.length, 4); + ok(opParams.find((x) => x.kind === "path" && x.serializedName === "petId")); + ok(opParams.find((x) => x.kind === "path" && x.serializedName === "checkupId")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Content-Type")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Accept")); + strictEqual(createOrUpdate.operation.responses.size, 2); + const response200 = createOrUpdate.operation.responses.get(200); + ok(response200); + ok(response200.type); + strictEqual(response200.type.kind, "model"); + strictEqual(response200.type.name, "Checkup"); + const response201 = createOrUpdate.operation.responses.get(201); + ok(response201); + ok(response201.type); + deepStrictEqual(response200.type, response201?.type); }); }); describe("versioning", () => { - it("versioned service", async () => { - await runner.compile(` - @service({ - title: "Pet Store Service", - }) - @versioned(Versions) - namespace VersionedApi; - enum Versions { - v1, - v2, - } - + it("define own api version param", async () => { + await runner.compileWithBuiltInService(` model ApiVersionParam { @header apiVersion: Versions; } - @discriminator("type") - model PetBase { - name: string; - } - - model Dog extends PetBase { - type: "dog"; - nextWalkTime: utcDateTime; - - @madeOptional(Versions.v2) - walkerName?: string; - - @added(Versions.v2) - commandList: string[]; - } - - @added(Versions.v2) - model Cat extends PetBase { - type: "cat"; - catnipDose: int32; - } - - @route("/") - interface MyService { - getPet(...ApiVersionParam): PetBase; - - @added(Versions.v2) - @post - @route("/walkDog") - walkDog(...ApiVersionParam): OkResponse; - - @removed(Versions.v2) - @post - @route("/walkCat") - walkCat(...ApiVersionParam): OkResponse; + enum Versions { + v1, v2 } - `) - + op getPet(...ApiVersionParam): void; + `); + const sdkPackage = runner.context.experimental_sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.operation.parameters.length, 1); + const apiVersionParam = method.operation.parameters[0]; + strictEqual(apiVersionParam.kind, "header"); + strictEqual(apiVersionParam.serializedName, "api-version"); + strictEqual(apiVersionParam.name, "apiVersion"); + strictEqual(apiVersionParam.onClient, true); + strictEqual(apiVersionParam.isApiVersionParam, true); }); - - }) + }); }); function getServiceMethodOfClient( diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts index a5f4988f18..bb7e351a5f 100644 --- a/packages/typespec-client-generator-core/test/public-utils.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils.test.ts @@ -170,7 +170,7 @@ describe("typespec-client-generator-core: public-utils", () => { it("not api version param", async () => { const { func } = (await runner.compile(` - @test op func(@path notApiVersion: string): void; + @test op func(@path foo: string): void; `)) as { func: Operation }; const pathParam = ignoreDiagnostics(getHttpOperation(runner.context.program, func)).parameters diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 1f85582428..95f79d46bb 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -3015,13 +3015,14 @@ describe("typespec-client-generator-core: types", () => { const widgetFormParam = formDataMethod.parameters.find((x) => x.name === "widgetForm"); ok(widgetFormParam); ok(formDataMethod.parameters.find((x) => x.name === "accept")); - strictEqual(formDataMethod.parameters[0].name, "widgetForm"); - strictEqual(formDataMethod.parameters[0].type.kind, "model"); - strictEqual(formDataMethod.parameters[0].type.name, "WidgetForm"); + strictEqual(formDataMethod.parameters[0].name, "name"); + strictEqual(formDataMethod.parameters[0].type.kind, "string"); + strictEqual(formDataMethod.parameters[1].name, "widgetForm"); + strictEqual(formDataMethod.parameters[1].type.kind, "model"); + strictEqual(formDataMethod.parameters[1].type.name, "WidgetForm"); const formDataOp = formDataMethod.operation; - strictEqual(formDataOp.parameters.length, 3); - ok(formDataOp.parameters.find((x) => x.name === "widgetName")); + strictEqual(formDataOp.parameters.length, 2); ok(formDataOp.parameters.find((x) => x.name === "accept" && x.kind === "header")); ok(formDataOp.parameters.find((x) => x.name === "contentType" && x.kind === "header")); From 1486b522e107ac6c4cc7b7c8dae3c46d6ee3e1ce Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sun, 31 Mar 2024 18:32:19 -0400 Subject: [PATCH 11/12] fix build errors --- packages/typespec-client-generator-core/src/package.ts | 4 +++- packages/typespec-client-generator-core/test/package.test.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 15e6e42773..7a2989b6cb 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -354,7 +354,8 @@ function getSdkMethodParameter( ): [SdkMethodParameter, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); if (type.kind === "Model") { - const name = camelCase(getLibraryName(context, type) ?? "body"); + const libraryName = getLibraryName(context, type); + const name = camelCase(libraryName ?? "body"); const propertyType = diagnostics.pipe(getClientTypeWithDiagnostics(context, type)); return diagnostics.wrap({ kind: "method", @@ -364,6 +365,7 @@ function getSdkMethodParameter( type: propertyType, nameInClient: name, name, + isGeneratedName: Boolean(libraryName), optional: false, nullable: false, discriminator: false, diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 8db4f7f785..e4af2b6ca9 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -224,7 +224,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(credentialParam.optional, false); strictEqual(credentialParam.type.kind, "union"); strictEqual(credentialParam.type.name, "ServiceCredentialUnion"); - strictEqual(credentialParam.type.generatedName, true); + strictEqual(credentialParam.type.isGeneratedName, true); strictEqual(credentialParam.type.values.length, 2); const schemes = credentialParam.type.values .filter((v): v is SdkCredentialType => v.kind === "credential") From 7e9d3cbc869f0a8dafce8a9c896d3e920324e4a4 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sun, 31 Mar 2024 19:48:35 -0400 Subject: [PATCH 12/12] fix case of @body in spread model --- .../src/http.ts | 9 +++ .../test/package.test.ts | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 26b4f2e2b3..1d0a786b26 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -30,6 +30,7 @@ import { SdkHttpResponse, SdkMethodParameter, SdkModelPropertyType, + SdkModelType, SdkParameter, SdkPathParameter, SdkQueryParameter, @@ -417,6 +418,14 @@ export function getCorrespondingMethodParams( .filter((x) => x.kind === "property") .map((x) => x.name); // Here we have a spread method parameter + + // easy case is if the spread method parameter directly has the entire body as a property + const directBodyProperty = methodParameters + .map((x) => x.type) + .filter((x): x is SdkModelType => x.kind === "model") + .flatMap((x) => x.properties) + .find((x) => x.type === serviceParamType); + if (directBodyProperty) return [directBodyProperty]; let correspondingProperties: SdkModelPropertyType[] = methodParameters.filter((x) => serviceParamPropertyNames.includes(x.name) ); diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index e4af2b6ca9..2e9483727c 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -2928,6 +2928,65 @@ describe("typespec-client-generator-core: package", () => { ok(response201.type); deepStrictEqual(response200.type, response201?.type); }); + it("spread with @body in model", async () => { + await runner.compileWithBuiltInService(` + model Shelf { + name: string; + theme?: string; + } + model CreateShelfRequest { + @body + body: Shelf; + } + op createShelf(...CreateShelfRequest): Shelf; + `); + const method = getServiceMethodOfClient(runner.context.experimental_sdkPackage); + const models = runner.context.experimental_sdkPackage.models; + strictEqual(models.length, 1); + const shelfModel = models.find((x) => x.name === "Shelf"); + ok(shelfModel); + strictEqual(method.parameters.length, 3); + const createShelfRequest = method.parameters[0]; + strictEqual(createShelfRequest.kind, "method"); + strictEqual(createShelfRequest.name, "createShelfRequest"); + strictEqual(createShelfRequest.optional, false); + strictEqual(createShelfRequest.isGeneratedName, true); + strictEqual(createShelfRequest.type.kind, "model"); + strictEqual(createShelfRequest.type.properties.length, 1); + deepStrictEqual(createShelfRequest.type.properties[0].type, shelfModel); + const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethoParam); + const acceptMethodParam = method.parameters.find((x) => x.name === "accept"); + ok(acceptMethodParam); + + const op = method.operation; + strictEqual(op.parameters.length, 2); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Content-Type" && + x.correspondingMethodParams[0] === contentTypeMethoParam + ) + ); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Accept" && + x.correspondingMethodParams[0] === acceptMethodParam + ) + ); + + const bodyParam = op.bodyParam; + ok(bodyParam); + strictEqual(bodyParam.kind, "body"); + strictEqual(bodyParam.name, "body"); + strictEqual(bodyParam.optional, false); + strictEqual(bodyParam.isGeneratedName, false); + deepStrictEqual(bodyParam.type, shelfModel); + deepStrictEqual(bodyParam.correspondingMethodParams, createShelfRequest.type.properties); + }); }); describe("versioning", () => { it("define own api version param", async () => {