diff --git a/.chronus/changes/prop_not_set_multipart-2024-3-1-16-13-0.md b/.chronus/changes/prop_not_set_multipart-2024-3-1-16-13-0.md new file mode 100644 index 0000000000..8bf97087e4 --- /dev/null +++ b/.chronus/changes/prop_not_set_multipart-2024-3-1-16-13-0.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +don't recursively set `MultipartFormData` usage for models that are properties on a `MultipartFormData` model \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index fa88ebdfcd..1cb205d98f 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -1119,33 +1119,40 @@ function checkAndGetClientType( return diagnostics.wrap([clientType]); } +interface ModelUsageOptions { + seenModelNames?: Set; + recurseThroughProperties?: boolean; +} + function updateUsageOfModel( context: TCGCContext, usage: UsageFlags, type?: SdkType, - seenModelNames?: Set + options?: ModelUsageOptions ): void { + options = options ?? {}; + options.recurseThroughProperties = options?.recurseThroughProperties ?? true; if (!type || !["model", "enum", "array", "dict", "union", "enumvalue"].includes(type.kind)) return; - if (seenModelNames === undefined) { - seenModelNames = new Set(); + if (options?.seenModelNames === undefined) { + options.seenModelNames = new Set(); } - if (type.kind === "model" && seenModelNames.has(type)) return; // avoid circular references + if (type.kind === "model" && options.seenModelNames.has(type)) return; // avoid circular references if (type.kind === "array" || type.kind === "dict") { - return updateUsageOfModel(context, usage, type.valueType, seenModelNames); + return updateUsageOfModel(context, usage, type.valueType, options); } if (type.kind === "union") { for (const unionType of type.values) { - updateUsageOfModel(context, usage, unionType, seenModelNames); + updateUsageOfModel(context, usage, unionType, options); } return; } if (type.kind === "enumvalue") { - updateUsageOfModel(context, usage, type.enumType, seenModelNames); + updateUsageOfModel(context, usage, type.enumType, options); return; } if (type.kind !== "model" && type.kind !== "enum") return; - seenModelNames.add(type); + options.seenModelNames.add(type); const usageOverride = getUsageOverride(context, type.__raw as any); if (usageOverride) { @@ -1160,18 +1167,20 @@ function updateUsageOfModel( type.baseModel.usage |= usage; } if (type.baseModel) { - updateUsageOfModel(context, usage, type.baseModel, seenModelNames); + updateUsageOfModel(context, usage, type.baseModel, options); } if (type.discriminatedSubtypes) { for (const discriminatedSubtype of Object.values(type.discriminatedSubtypes)) { - updateUsageOfModel(context, usage, discriminatedSubtype, seenModelNames); + updateUsageOfModel(context, usage, discriminatedSubtype, options); } } - if (type.additionalProperties) { - updateUsageOfModel(context, usage, type.additionalProperties, seenModelNames); + if (type.additionalProperties && options.recurseThroughProperties) { + updateUsageOfModel(context, usage, type.additionalProperties, options); } - for (const property of type.properties) { - updateUsageOfModel(context, usage, property.type, seenModelNames); + if (options.recurseThroughProperties) { + for (const property of type.properties) { + updateUsageOfModel(context, usage, property.type, options); + } } } @@ -1216,7 +1225,9 @@ function updateTypesFromOperation( } if (isMultipartFormData(context, httpBody.type, operation)) { bodies.forEach((body) => { - updateUsageOfModel(context, UsageFlags.MultipartFormData, body); + updateUsageOfModel(context, UsageFlags.MultipartFormData, body, { + recurseThroughProperties: false, + }); }); } } diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index d280d22ab9..3e2384cf13 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -3000,6 +3000,32 @@ describe("typespec-client-generator-core: types", () => { ["color", "description", "displayName", "name"].sort() ); }); + + it("usage doesn't apply to properties of a form data", async function () { + await runner.compileWithBuiltInService(` + model MultiPartRequest { + id: string; + profileImage: bytes; + address: Address; + } + + model Address { + city: string; + } + + @post + op upload(@header contentType: "multipart/form-data", @body body: MultiPartRequest): void; + `); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 2); + const multiPartRequest = models.find((x) => x.name === "MultiPartRequest"); + ok(multiPartRequest); + ok(multiPartRequest.usage & UsageFlags.MultipartFormData); + + const address = models.find((x) => x.name === "Address"); + ok(address); + strictEqual(address.usage & UsageFlags.MultipartFormData, 0); + }); }); describe("SdkTupleType", () => { it("model with tupled properties", async function () {