From b2e27a9ab49dd092f98de88e320077790f4ffead Mon Sep 17 00:00:00 2001 From: msyyc <70930885+msyyc@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:32:23 +0800 Subject: [PATCH 1/7] add model for multipart file --- packages/typespec-azure-core/lib/models.tsp | 6 +++ .../test/types/multipart-types.test.ts | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp index 308bf98d00..e5fc8ef20e 100644 --- a/packages/typespec-azure-core/lib/models.tsp +++ b/packages/typespec-azure-core/lib/models.tsp @@ -400,3 +400,9 @@ model ArmResourceIdentifierAllowedResource { */ scopes?: ArmResourceDeploymentScope[]; } + +@doc("Used in file part of multipart request body") +model MultiPartFile extends File { + filename: string; + contentType: string; +} diff --git a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts index e85ab84033..f5aa637a0e 100644 --- a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts @@ -1,3 +1,4 @@ +import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; @@ -454,6 +455,50 @@ describe("typespec-client-generator-core: multipart types", () => { strictEqual(fileRequiredFileName.multipartOptions.contentType.optional, false); }); + it("with MultiPartFile of Azure.Core", async function () { + const runnerCore = await createSdkTestRunner({ + librariesToAdd: [AzureCoreTestLibrary], + emitterName: "@azure-tools/typespec-java" , autoUsings: ["Azure.Core"]}); + await runnerCore.compileWithBuiltInService(` + model MultiPartRequest{ + fileOptionalFileName: HttpPart; + fileRequiredFileName: HttpPart; + } + @post + op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void; + `); + const models = runnerCore.context.sdkPackage.models; + strictEqual(models.length, 2); + const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest"); + ok(MultiPartRequest); + ok(MultiPartRequest.usage & UsageFlags.MultipartFormData); + const fileOptionalFileName = MultiPartRequest.properties.find( + (x) => x.name === "fileOptionalFileName" + ) as SdkBodyModelPropertyType; + ok(fileOptionalFileName); + strictEqual(fileOptionalFileName.optional, false); + ok(fileOptionalFileName.multipartOptions); + strictEqual(fileOptionalFileName.name, "fileOptionalFileName"); + strictEqual(fileOptionalFileName.multipartOptions.isFilePart, true); + ok(fileOptionalFileName.multipartOptions.filename); + strictEqual(fileOptionalFileName.multipartOptions.filename.optional, true); + ok(fileOptionalFileName.multipartOptions.contentType); + strictEqual(fileOptionalFileName.multipartOptions.contentType.optional, true); + + const fileRequiredFileName = MultiPartRequest.properties.find( + (x) => x.name === "fileRequiredFileName" + ) as SdkBodyModelPropertyType; + ok(fileRequiredFileName); + strictEqual(fileRequiredFileName.optional, false); + ok(fileRequiredFileName.multipartOptions); + strictEqual(fileRequiredFileName.name, "fileRequiredFileName"); + strictEqual(fileRequiredFileName.multipartOptions.isFilePart, true); + ok(fileRequiredFileName.multipartOptions.filename); + strictEqual(fileRequiredFileName.multipartOptions.filename.optional, false); + ok(fileRequiredFileName.multipartOptions.contentType); + strictEqual(fileRequiredFileName.multipartOptions.contentType.optional, false); + }); + it("check 'multi' of multipart with @multipartBody for model", async function () { await runner.compileWithBuiltInService(` model Address { From 83f9978e83fbd02d48d54370186f0207c7c1d91a Mon Sep 17 00:00:00 2001 From: msyyc <70930885+msyyc@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:33:49 +0800 Subject: [PATCH 2/7] changelog --- .../changes/add-multipartfile-model-2024-8-2-16-33-33.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/add-multipartfile-model-2024-8-2-16-33-33.md diff --git a/.chronus/changes/add-multipartfile-model-2024-8-2-16-33-33.md b/.chronus/changes/add-multipartfile-model-2024-8-2-16-33-33.md new file mode 100644 index 0000000000..45496564ec --- /dev/null +++ b/.chronus/changes/add-multipartfile-model-2024-8-2-16-33-33.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-azure-core" +--- + +Add model MultiPartFile with required `filename` and `contentType` \ No newline at end of file From 5ff3ab0bb91d76c326ea75a4af2ab0ed65fb18d4 Mon Sep 17 00:00:00 2001 From: msyyc <70930885+msyyc@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:36:18 +0800 Subject: [PATCH 3/7] format --- .../test/types/multipart-types.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts index f5aa637a0e..641d648c94 100644 --- a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts @@ -456,9 +456,11 @@ describe("typespec-client-generator-core: multipart types", () => { }); it("with MultiPartFile of Azure.Core", async function () { - const runnerCore = await createSdkTestRunner({ + const runnerCore = await createSdkTestRunner({ librariesToAdd: [AzureCoreTestLibrary], - emitterName: "@azure-tools/typespec-java" , autoUsings: ["Azure.Core"]}); + emitterName: "@azure-tools/typespec-java", + autoUsings: ["Azure.Core"], + }); await runnerCore.compileWithBuiltInService(` model MultiPartRequest{ fileOptionalFileName: HttpPart; From 8f242584a4f15bd0276fdd53118825b91fdbcab5 Mon Sep 17 00:00:00 2001 From: msyyc <70930885+msyyc@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:05:40 +0800 Subject: [PATCH 4/7] for ci --- docs/libraries/azure-core/reference/data-types.md | 15 +++++++++++++++ docs/libraries/azure-core/reference/index.mdx | 1 + 2 files changed, 16 insertions(+) diff --git a/docs/libraries/azure-core/reference/data-types.md b/docs/libraries/azure-core/reference/data-types.md index 12d9ca1719..feb2b62ae2 100644 --- a/docs/libraries/azure-core/reference/data-types.md +++ b/docs/libraries/azure-core/reference/data-types.md @@ -220,6 +220,21 @@ model Azure.Core.MaxPageSizeQueryParameter | ------------ | ------- | -------------------------------------------- | | maxpagesize? | `int32` | The maximum number of result items per page. | +### `MultiPartFile` {#Azure.Core.MultiPartFile} + +Used in file part of multipart request body + +```typespec +model Azure.Core.MultiPartFile +``` + +#### Properties + +| Name | Type | Description | +| ----------- | -------- | ----------- | +| filename | `string` | | +| contentType | `string` | | + ### `OrderByQueryParameter` {#Azure.Core.OrderByQueryParameter} Provides the standard 'orderby' query parameter for list operations. diff --git a/docs/libraries/azure-core/reference/index.mdx b/docs/libraries/azure-core/reference/index.mdx index 5ea11012a4..f6d7a70346 100644 --- a/docs/libraries/azure-core/reference/index.mdx +++ b/docs/libraries/azure-core/reference/index.mdx @@ -96,6 +96,7 @@ npm install --save-peer @azure-tools/typespec-azure-core - [`FilterParameter`](./data-types.md#Azure.Core.FilterParameter) - [`FilterQueryParameter`](./data-types.md#Azure.Core.FilterQueryParameter) - [`MaxPageSizeQueryParameter`](./data-types.md#Azure.Core.MaxPageSizeQueryParameter) +- [`MultiPartFile`](./data-types.md#Azure.Core.MultiPartFile) - [`OrderByQueryParameter`](./data-types.md#Azure.Core.OrderByQueryParameter) - [`Page`](./data-types.md#Azure.Core.Page) - [`PollingOptions`](./data-types.md#Azure.Core.PollingOptions) From 4e313dc3ce77c5e5dc0ab94344056326c48a13fb Mon Sep 17 00:00:00 2001 From: msyyc <70930885+msyyc@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:25:01 +0800 Subject: [PATCH 5/7] review --- .../azure-core/reference/data-types.md | 8 ++--- packages/typespec-azure-core/lib/models.tsp | 3 ++ .../typespec-azure-core/test/models.test.ts | 30 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/typespec-azure-core/test/models.test.ts diff --git a/docs/libraries/azure-core/reference/data-types.md b/docs/libraries/azure-core/reference/data-types.md index feb2b62ae2..17fe40d713 100644 --- a/docs/libraries/azure-core/reference/data-types.md +++ b/docs/libraries/azure-core/reference/data-types.md @@ -230,10 +230,10 @@ model Azure.Core.MultiPartFile #### Properties -| Name | Type | Description | -| ----------- | -------- | ----------- | -| filename | `string` | | -| contentType | `string` | | +| Name | Type | Description | +| ----------- | -------- | ------------------------------------------------------- | +| filename | `string` | The file name in file part of multipart request body | +| contentType | `string` | The content type in file part of multipart request body | ### `OrderByQueryParameter` {#Azure.Core.OrderByQueryParameter} diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp index e5fc8ef20e..a5e2e37151 100644 --- a/packages/typespec-azure-core/lib/models.tsp +++ b/packages/typespec-azure-core/lib/models.tsp @@ -403,6 +403,9 @@ model ArmResourceIdentifierAllowedResource { @doc("Used in file part of multipart request body") model MultiPartFile extends File { + @doc("The file name in file part of multipart request body") filename: string; + + @doc("The content type in file part of multipart request body") contentType: string; } diff --git a/packages/typespec-azure-core/test/models.test.ts b/packages/typespec-azure-core/test/models.test.ts new file mode 100644 index 0000000000..de030e0e07 --- /dev/null +++ b/packages/typespec-azure-core/test/models.test.ts @@ -0,0 +1,30 @@ +import { expectDiagnosticEmpty } from "@typespec/compiler/testing"; +import { getHttpPart, isOrExtendsHttpFile } from "@typespec/http"; +import { ok, strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { getOperations } from "./test-host.js"; + +describe("typespec-azure-core: models", () => { + it("MultiPartFile could be recognized by @typespec/http", async () => { + const [operations, diagnostics, runner] = await getOperations( + ` + model TestModel { + file: HttpPart; + }; + + @post op TestOperation(@header contentType: "multipart/form-date", @multipartBody body: TestModel): void; + ` + ); + + ok(operations.length === 1); + ok(operations[0].parameters.body); + strictEqual(operations[0].parameters.body.bodyKind, "multipart"); + strictEqual(operations[0].parameters.body.type.kind, "Model"); + const fileHttpPart = operations[0].parameters.body.type.properties.get("file"); + ok(fileHttpPart); + const file = getHttpPart(runner.program, fileHttpPart.type); + ok(file !== undefined); + ok(isOrExtendsHttpFile(runner.program, file.type)); + expectDiagnosticEmpty(diagnostics); + }); +}); From fee2a898b04a0c9e23a3ca047d7b89528917f167 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Thu, 5 Sep 2024 10:30:55 +0800 Subject: [PATCH 6/7] review --- packages/typespec-azure-core/lib/models.tsp | 4 ++-- .../typespec-azure-core/test/{ => types}/models.test.ts | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) rename packages/typespec-azure-core/test/{ => types}/models.test.ts (81%) diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp index a5e2e37151..472f598a85 100644 --- a/packages/typespec-azure-core/lib/models.tsp +++ b/packages/typespec-azure-core/lib/models.tsp @@ -403,9 +403,9 @@ model ArmResourceIdentifierAllowedResource { @doc("Used in file part of multipart request body") model MultiPartFile extends File { - @doc("The file name in file part of multipart request body") + /** The file name in file part of multipart request body */ filename: string; - @doc("The content type in file part of multipart request body") + /** The content type in file part of multipart request body */ contentType: string; } diff --git a/packages/typespec-azure-core/test/models.test.ts b/packages/typespec-azure-core/test/types/models.test.ts similarity index 81% rename from packages/typespec-azure-core/test/models.test.ts rename to packages/typespec-azure-core/test/types/models.test.ts index de030e0e07..0b02495363 100644 --- a/packages/typespec-azure-core/test/models.test.ts +++ b/packages/typespec-azure-core/test/types/models.test.ts @@ -1,12 +1,11 @@ -import { expectDiagnosticEmpty } from "@typespec/compiler/testing"; import { getHttpPart, isOrExtendsHttpFile } from "@typespec/http"; import { ok, strictEqual } from "assert"; import { describe, it } from "vitest"; -import { getOperations } from "./test-host.js"; +import { getOperations } from "../test-host.js"; describe("typespec-azure-core: models", () => { it("MultiPartFile could be recognized by @typespec/http", async () => { - const [operations, diagnostics, runner] = await getOperations( + const [operations, _, runner] = await getOperations( ` model TestModel { file: HttpPart; @@ -25,6 +24,5 @@ describe("typespec-azure-core: models", () => { const file = getHttpPart(runner.program, fileHttpPart.type); ok(file !== undefined); ok(isOrExtendsHttpFile(runner.program, file.type)); - expectDiagnosticEmpty(diagnostics); }); }); From 860df87afecc9eb0a2a2541167ac08841b456d0e Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Fri, 6 Sep 2024 10:15:25 +0800 Subject: [PATCH 7/7] review --- .../azure-core/reference/data-types.md | 30 ++++++++--------- docs/libraries/azure-core/reference/index.mdx | 2 +- packages/typespec-azure-core/lib/models.tsp | 2 +- .../test/types/models.test.ts | 32 +++++++++---------- .../test/types/multipart-types.test.ts | 4 +-- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/docs/libraries/azure-core/reference/data-types.md b/docs/libraries/azure-core/reference/data-types.md index 17fe40d713..2241742137 100644 --- a/docs/libraries/azure-core/reference/data-types.md +++ b/docs/libraries/azure-core/reference/data-types.md @@ -178,6 +178,21 @@ model Azure.Core.ExpandQueryParameter | ------- | ---------- | ------------------------------------------------- | | expand? | `string[]` | Expand the indicated resources into the response. | +### `FileWithRequiredMetadata` {#Azure.Core.FileWithRequiredMetadata} + +Used in file part of multipart request body + +```typespec +model Azure.Core.FileWithRequiredMetadata +``` + +#### Properties + +| Name | Type | Description | +| ----------- | -------- | ------------------------------------------------------- | +| filename | `string` | The file name in file part of multipart request body | +| contentType | `string` | The content type in file part of multipart request body | + ### `FilterParameter` {#Azure.Core.FilterParameter} Provides the standard 'filter' query parameter for list operations @@ -220,21 +235,6 @@ model Azure.Core.MaxPageSizeQueryParameter | ------------ | ------- | -------------------------------------------- | | maxpagesize? | `int32` | The maximum number of result items per page. | -### `MultiPartFile` {#Azure.Core.MultiPartFile} - -Used in file part of multipart request body - -```typespec -model Azure.Core.MultiPartFile -``` - -#### Properties - -| Name | Type | Description | -| ----------- | -------- | ------------------------------------------------------- | -| filename | `string` | The file name in file part of multipart request body | -| contentType | `string` | The content type in file part of multipart request body | - ### `OrderByQueryParameter` {#Azure.Core.OrderByQueryParameter} Provides the standard 'orderby' query parameter for list operations. diff --git a/docs/libraries/azure-core/reference/index.mdx b/docs/libraries/azure-core/reference/index.mdx index f6d7a70346..4e29e82c1a 100644 --- a/docs/libraries/azure-core/reference/index.mdx +++ b/docs/libraries/azure-core/reference/index.mdx @@ -93,10 +93,10 @@ npm install --save-peer @azure-tools/typespec-azure-core - [`EtagProperty`](./data-types.md#Azure.Core.EtagProperty) - [`EtagResponseEnvelope`](./data-types.md#Azure.Core.EtagResponseEnvelope) - [`ExpandQueryParameter`](./data-types.md#Azure.Core.ExpandQueryParameter) +- [`FileWithRequiredMetadata`](./data-types.md#Azure.Core.FileWithRequiredMetadata) - [`FilterParameter`](./data-types.md#Azure.Core.FilterParameter) - [`FilterQueryParameter`](./data-types.md#Azure.Core.FilterQueryParameter) - [`MaxPageSizeQueryParameter`](./data-types.md#Azure.Core.MaxPageSizeQueryParameter) -- [`MultiPartFile`](./data-types.md#Azure.Core.MultiPartFile) - [`OrderByQueryParameter`](./data-types.md#Azure.Core.OrderByQueryParameter) - [`Page`](./data-types.md#Azure.Core.Page) - [`PollingOptions`](./data-types.md#Azure.Core.PollingOptions) diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp index 472f598a85..a1ec834f8c 100644 --- a/packages/typespec-azure-core/lib/models.tsp +++ b/packages/typespec-azure-core/lib/models.tsp @@ -402,7 +402,7 @@ model ArmResourceIdentifierAllowedResource { } @doc("Used in file part of multipart request body") -model MultiPartFile extends File { +model FileWithRequiredMetadata extends File { /** The file name in file part of multipart request body */ filename: string; diff --git a/packages/typespec-azure-core/test/types/models.test.ts b/packages/typespec-azure-core/test/types/models.test.ts index 0b02495363..68cbc66972 100644 --- a/packages/typespec-azure-core/test/types/models.test.ts +++ b/packages/typespec-azure-core/test/types/models.test.ts @@ -1,28 +1,26 @@ import { getHttpPart, isOrExtendsHttpFile } from "@typespec/http"; import { ok, strictEqual } from "assert"; -import { describe, it } from "vitest"; +import { it } from "vitest"; import { getOperations } from "../test-host.js"; -describe("typespec-azure-core: models", () => { - it("MultiPartFile could be recognized by @typespec/http", async () => { - const [operations, _, runner] = await getOperations( - ` +it("FileWithRequiredMetadata could be recognized by @typespec/http", async () => { + const [operations, _, runner] = await getOperations( + ` model TestModel { - file: HttpPart; + file: HttpPart; }; @post op TestOperation(@header contentType: "multipart/form-date", @multipartBody body: TestModel): void; ` - ); + ); - ok(operations.length === 1); - ok(operations[0].parameters.body); - strictEqual(operations[0].parameters.body.bodyKind, "multipart"); - strictEqual(operations[0].parameters.body.type.kind, "Model"); - const fileHttpPart = operations[0].parameters.body.type.properties.get("file"); - ok(fileHttpPart); - const file = getHttpPart(runner.program, fileHttpPart.type); - ok(file !== undefined); - ok(isOrExtendsHttpFile(runner.program, file.type)); - }); + ok(operations.length === 1); + ok(operations[0].parameters.body); + strictEqual(operations[0].parameters.body.bodyKind, "multipart"); + strictEqual(operations[0].parameters.body.type.kind, "Model"); + const fileHttpPart = operations[0].parameters.body.type.properties.get("file"); + ok(fileHttpPart); + const file = getHttpPart(runner.program, fileHttpPart.type); + ok(file !== undefined); + ok(isOrExtendsHttpFile(runner.program, file.type)); }); diff --git a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts index 641d648c94..908a130d83 100644 --- a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts @@ -455,7 +455,7 @@ describe("typespec-client-generator-core: multipart types", () => { strictEqual(fileRequiredFileName.multipartOptions.contentType.optional, false); }); - it("with MultiPartFile of Azure.Core", async function () { + it("with FileWithRequiredMetadata of Azure.Core", async function () { const runnerCore = await createSdkTestRunner({ librariesToAdd: [AzureCoreTestLibrary], emitterName: "@azure-tools/typespec-java", @@ -464,7 +464,7 @@ describe("typespec-client-generator-core: multipart types", () => { await runnerCore.compileWithBuiltInService(` model MultiPartRequest{ fileOptionalFileName: HttpPart; - fileRequiredFileName: HttpPart; + fileRequiredFileName: HttpPart; } @post op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;