Skip to content

Commit

Permalink
[tcgc] body spread formdata with multiple properties (#565)
Browse files Browse the repository at this point in the history
  • Loading branch information
iscai-msft authored Apr 1, 2024
1 parent 30ded5c commit 583076d
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 287 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/fix_test_ordering-2024-3-1-10-32-24.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@azure-tools/typespec-client-generator-core"
---

order tests into a spread section
2 changes: 1 addition & 1 deletion packages/typespec-client-generator-core/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ export function getCorrespondingMethodParams(
)
);
}
if (correspondingProperties.length === serviceParamType.properties.length)
if (correspondingProperties.length === serviceParamPropertyNames.length)
return correspondingProperties;
throw new Error(
`Can't find corresponding parameter for ${serviceParam.name} out of ${methodParameters.map((m) => m.name).join(", ")}`
Expand Down
352 changes: 186 additions & 166 deletions packages/typespec-client-generator-core/test/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1342,170 +1342,6 @@ describe("typespec-client-generator-core: package", () => {
strictEqual(correspondingContentTypeMethodParams[0], methodContentTypeParam);
});

it("body spread", async () => {
await runner.compile(`@server("http://localhost:3000", "endpoint")
@service({})
namespace My.Service;
model Input {
key: string;
}
op myOp(...Input): void;
`);
const sdkPackage = runner.context.experimental_sdkPackage;
const method = getServiceMethodOfClient(sdkPackage);
strictEqual(method.name, "myOp");
strictEqual(method.kind, "basic");
strictEqual(method.parameters.length, 2);

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, "model");

const contentTypeParam = method.parameters.find((x) => x.name === "contentType");
ok(contentTypeParam);
strictEqual(contentTypeParam.clientDefaultValue, undefined);
strictEqual(contentTypeParam.type.kind, "constant");
strictEqual(contentTypeParam.onClient, false);

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, sdkPackage.models[0]);

const correspondingMethodParams = bodyParameter.correspondingMethodParams;
strictEqual(correspondingMethodParams.length, 1);
strictEqual(bodyParameter.type, correspondingMethodParams[0].type);
});

it("body alias spread", async () => {
await runner.compile(`@server("http://localhost:3000", "endpoint")
@service({})
namespace My.Service;
alias BodyParameter = {
name: string;
};
op myOp(...BodyParameter): void;
`);
const sdkPackage = runner.context.experimental_sdkPackage;
const method = getServiceMethodOfClient(sdkPackage);
strictEqual(sdkPackage.models.length, 1);
strictEqual(method.name, "myOp");
strictEqual(method.kind, "basic");
strictEqual(method.parameters.length, 2);

const methodParam = method.parameters.find((x) => x.name === "name");
ok(methodParam);
strictEqual(methodParam.kind, "method");
strictEqual(methodParam.optional, false);
strictEqual(methodParam.onClient, false);
strictEqual(methodParam.isApiVersionParam, false);
strictEqual(methodParam.type.kind, "string");

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, 1);
//eslint-disable-next-line deprecation/deprecation
strictEqual(bodyParameter.type.properties[0].nameInClient, "name");
strictEqual(bodyParameter.type.properties[0].name, "name");

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);
});

it("spread with discriminate type with implicit property", 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({})
Expand Down Expand Up @@ -2859,8 +2695,172 @@ describe("typespec-client-generator-core: package", () => {
ok(clientRequestIdProperty);
strictEqual(clientRequestIdProperty.kind, "header");
});
});
describe("spread", () => {
it("plain model with no decorators", async () => {
await runner.compile(`@server("http://localhost:3000", "endpoint")
@service({})
namespace My.Service;
model Input {
key: string;
}
op myOp(...Input): void;
`);
const sdkPackage = runner.context.experimental_sdkPackage;
const method = getServiceMethodOfClient(sdkPackage);
strictEqual(method.name, "myOp");
strictEqual(method.kind, "basic");
strictEqual(method.parameters.length, 2);

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, "model");

const contentTypeParam = method.parameters.find((x) => x.name === "contentType");
ok(contentTypeParam);
strictEqual(contentTypeParam.clientDefaultValue, undefined);
strictEqual(contentTypeParam.type.kind, "constant");
strictEqual(contentTypeParam.onClient, false);

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, sdkPackage.models[0]);

const correspondingMethodParams = bodyParameter.correspondingMethodParams;
strictEqual(correspondingMethodParams.length, 1);
strictEqual(bodyParameter.type, correspondingMethodParams[0].type);
});

it("alias with no decorators", async () => {
await runner.compile(`@server("http://localhost:3000", "endpoint")
@service({})
namespace My.Service;
it("multiple spread", async () => {
alias BodyParameter = {
name: string;
};
op myOp(...BodyParameter): void;
`);
const sdkPackage = runner.context.experimental_sdkPackage;
const method = getServiceMethodOfClient(sdkPackage);
strictEqual(sdkPackage.models.length, 1);
strictEqual(method.name, "myOp");
strictEqual(method.kind, "basic");
strictEqual(method.parameters.length, 2);

const methodParam = method.parameters.find((x) => x.name === "name");
ok(methodParam);
strictEqual(methodParam.kind, "method");
strictEqual(methodParam.optional, false);
strictEqual(methodParam.onClient, false);
strictEqual(methodParam.isApiVersionParam, false);
strictEqual(methodParam.type.kind, "string");

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, 1);
//eslint-disable-next-line deprecation/deprecation
strictEqual(bodyParameter.type.properties[0].nameInClient, "name");
strictEqual(bodyParameter.type.properties[0].name, "name");

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);
});

it("spread with discriminate type with implicit property", 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("rest template spreading of multiple models", async () => {
await runner.compile(`
@service({
title: "Pet Store Service",
Expand Down Expand Up @@ -2928,7 +2928,7 @@ describe("typespec-client-generator-core: package", () => {
ok(response201.type);
deepStrictEqual(response200.type, response201?.type);
});
it("spread with @body in model", async () => {
it("model with @body decorator", async () => {
await runner.compileWithBuiltInService(`
model Shelf {
name: string;
Expand Down Expand Up @@ -2987,6 +2987,26 @@ describe("typespec-client-generator-core: package", () => {
deepStrictEqual(bodyParam.type, shelfModel);
deepStrictEqual(bodyParam.correspondingMethodParams, createShelfRequest.type.properties);
});
it("formdata model without body decorator in spread model", async () => {
await runner.compileWithBuiltInService(`
model DocumentTranslateContent {
@header contentType: "multipart/form-data";
document: bytes;
}
alias Intersected = DocumentTranslateContent & {};
op test(...Intersected): void;
`);
const method = getServiceMethodOfClient(runner.context.experimental_sdkPackage);
const documentMethodParam = method.parameters.find((x) => x.name === "document");
ok(documentMethodParam);
strictEqual(documentMethodParam.kind, "method");
const op = method.operation;
ok(op.bodyParam);
strictEqual(op.bodyParam.kind, "body");
strictEqual(op.bodyParam.name, "documentTranslateContent");
deepStrictEqual(op.bodyParam.correspondingMethodParams, [documentMethodParam]);
});
});
describe("versioning", () => {
it("define own api version param", async () => {
Expand Down
Loading

0 comments on commit 583076d

Please sign in to comment.