From 768364411f2416dad3a0d403a28fd4879e13ed16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:07:55 +0000 Subject: [PATCH 1/3] Initial plan From 8cc1d72bfd82d1eec749f5a581330db83ac51757 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:41:08 +0000 Subject: [PATCH 2/3] fix(openapi3): fix 'Duplicate type name' error for union with bytes in multipart body Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- ...penapi3-multipart-union-bytes-2026-3-16.md | 7 +++++ packages/openapi3/src/visibility-usage.ts | 10 +++++++ packages/openapi3/test/multipart.test.ts | 27 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 .chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md diff --git a/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md b/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md new file mode 100644 index 00000000000..264194d9ed4 --- /dev/null +++ b/.chronus/changes/fix-openapi3-multipart-union-bytes-2026-3-16.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/openapi3" +--- + +Fix OpenAPI emitter failing with "Duplicate type name" error when using a named union with a `bytes` variant in a multipart body (e.g. `HttpPart` where `MyUnion` includes `bytes`). diff --git a/packages/openapi3/src/visibility-usage.ts b/packages/openapi3/src/visibility-usage.ts index b01ed33dedb..2e30d48499b 100644 --- a/packages/openapi3/src/visibility-usage.ts +++ b/packages/openapi3/src/visibility-usage.ts @@ -152,6 +152,16 @@ function addUsagesInOperation( navigateReferencedTypes(httpOperation.parameters.body.type, visibility, (type, vis) => trackUsage(metadataInfo, usages, type, vis), ); + // For multipart bodies, also navigate part types directly. HttpPart wrappers are + // empty models with no properties, so navigateReferencedTypes won't reach T through + // normal property traversal, causing T to be incorrectly treated as unreachable. + if (httpOperation.parameters.body.bodyKind === "multipart") { + for (const part of httpOperation.parameters.body.parts) { + navigateReferencedTypes(part.body.type, visibility, (type, vis) => + trackUsage(metadataInfo, usages, type, vis), + ); + } + } } for (const param of httpOperation.parameters.parameters) { navigateReferencedTypes(param.param, visibility, (type, vis) => diff --git a/packages/openapi3/test/multipart.test.ts b/packages/openapi3/test/multipart.test.ts index 1858f48e0f7..bdb81e55af3 100644 --- a/packages/openapi3/test/multipart.test.ts +++ b/packages/openapi3/test/multipart.test.ts @@ -91,6 +91,33 @@ worksFor(supportedVersions, ({ openApiFor }) => { description: "My doc", }); }); + + it("named union with bytes variant does not cause 'Duplicate type name' error", async () => { + const res = await openApiFor( + ` + union BinaryOrJson { + bytes, + { file_id: string }, + } + op upload(@header contentType: "multipart/form-data", @multipartBody body: { attachment: HttpPart }): void; + `, + ); + const op = res.paths["/"].post; + // The union should be referenced as a $ref (not inlined), and should be available in components + expect( + op.requestBody.content["multipart/form-data"].schema.properties.attachment.$ref, + ).toEqual("#/components/schemas/BinaryOrJson"); + const schema = res.components.schemas.BinaryOrJson; + expect(schema).toBeDefined(); + // The schema should use anyOf with 2 variants + expect(schema.anyOf).toHaveLength(2); + // The object variant ({file_id: string}) should appear in the schema regardless of version + expect(schema.anyOf).toContainEqual({ + type: "object", + properties: { file_id: { type: "string" } }, + required: ["file_id"], + }); + }); }); worksFor(["3.0.0"], ({ openApiFor }) => { From 7019adddb7584252f918e3d1be8bf46810bb78fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 20:57:49 +0000 Subject: [PATCH 3/3] style: apply prettier formatting Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- packages/openapi3/test/multipart.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/openapi3/test/multipart.test.ts b/packages/openapi3/test/multipart.test.ts index bdb81e55af3..b5a53caa25d 100644 --- a/packages/openapi3/test/multipart.test.ts +++ b/packages/openapi3/test/multipart.test.ts @@ -104,9 +104,9 @@ worksFor(supportedVersions, ({ openApiFor }) => { ); const op = res.paths["/"].post; // The union should be referenced as a $ref (not inlined), and should be available in components - expect( - op.requestBody.content["multipart/form-data"].schema.properties.attachment.$ref, - ).toEqual("#/components/schemas/BinaryOrJson"); + expect(op.requestBody.content["multipart/form-data"].schema.properties.attachment.$ref).toEqual( + "#/components/schemas/BinaryOrJson", + ); const schema = res.components.schemas.BinaryOrJson; expect(schema).toBeDefined(); // The schema should use anyOf with 2 variants