From e5b3a1e8ab05712acfb1adb5e663c2f35c86dc6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:30:03 +0000 Subject: [PATCH 1/4] Initial plan From 29d24794ccb16e8a070608ec3698d919813267ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:49:35 +0000 Subject: [PATCH 2/4] Fix Zod schema union deduplication by including validation constraints in type ID Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com> --- .../validators-string-constraints-union.json | 27 +++++++++++++++++++ .../zod.gen.ts | 8 ++++++ .../zod.gen.ts | 8 ++++++ .../zod.gen.ts | 8 ++++++ .../zod/v3/test/3.1.x.test.ts | 7 +++++ .../zod.gen.ts | 8 ++++++ .../zod.gen.ts | 8 ++++++ .../zod.gen.ts | 8 ++++++ .../zod/v4/test/3.1.x.test.ts | 7 +++++ packages/openapi-ts/src/ir/schema.ts | 20 +++++++++++++- 10 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 packages/openapi-ts-tests/specs/3.1.x/validators-string-constraints-union.json create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts diff --git a/packages/openapi-ts-tests/specs/3.1.x/validators-string-constraints-union.json b/packages/openapi-ts-tests/specs/3.1.x/validators-string-constraints-union.json new file mode 100644 index 0000000000..1f09f37833 --- /dev/null +++ b/packages/openapi-ts-tests/specs/3.1.x/validators-string-constraints-union.json @@ -0,0 +1,27 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "String Constraints Union Test", + "version": "1.0.0" + }, + "components": { + "schemas": { + "LocaleOrLanguage": { + "anyOf": [ + { + "type": "string", + "minLength": 5, + "maxLength": 5, + "description": "Combination of ISO 639-1 and ISO 3166-1 alpha-2 separated by a hyphen." + }, + { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 639-1 language code." + } + ] + } + } + } +} diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..1e8242e45d --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zLocaleOrLanguage = z.union([ + z.string().check(z.length(5)), + z.string().check(z.length(2)) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..5027df72f8 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zLocaleOrLanguage = z.union([ + z.string().length(5), + z.string().length(2) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..531f15719b --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v4'; + +export const zLocaleOrLanguage = z.union([ + z.string().length(5), + z.string().length(2) +]); diff --git a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts index 8ec15f08d0..4fedd7c557 100644 --- a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts @@ -138,6 +138,13 @@ for (const zodVersion of zodVersions) { description: "validator schemas with merged unions (can't use .merge())", }, + { + config: createConfig({ + input: 'validators-string-constraints-union.json', + output: 'validators-string-constraints-union', + }), + description: 'validator schemas with string constraints union', + }, ]; it.each(scenarios)('$description', async ({ config }) => { diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..99addfb0b9 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zLocaleOrLanguage = z.union([ + z.string().check(z.length(5)), + z.string().check(z.length(2)) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..6a9afba9d6 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zLocaleOrLanguage = z.union([ + z.string().length(5), + z.string().length(2) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts new file mode 100644 index 0000000000..5027df72f8 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-string-constraints-union/zod.gen.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zLocaleOrLanguage = z.union([ + z.string().length(5), + z.string().length(2) +]); diff --git a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts index 8ec15f08d0..4fedd7c557 100644 --- a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts @@ -138,6 +138,13 @@ for (const zodVersion of zodVersions) { description: "validator schemas with merged unions (can't use .merge())", }, + { + config: createConfig({ + input: 'validators-string-constraints-union.json', + output: 'validators-string-constraints-union', + }), + description: 'validator schemas with string constraints union', + }, ]; it.each(scenarios)('$description', async ({ config }) => { diff --git a/packages/openapi-ts/src/ir/schema.ts b/packages/openapi-ts/src/ir/schema.ts index b9e42c669e..b257a3a450 100644 --- a/packages/openapi-ts/src/ir/schema.ts +++ b/packages/openapi-ts/src/ir/schema.ts @@ -42,7 +42,25 @@ export const deduplicateSchema = ({ item.format !== undefined && detectFormat ? `format-${item.format}` : ''; - const typeId = `${item.$ref ?? ''}${item.type ?? ''}${constant}${format}`; + + // Include validation constraints in the type ID to avoid incorrect deduplication + const constraints = [ + item.minLength !== undefined ? `minLength-${item.minLength}` : '', + item.maxLength !== undefined ? `maxLength-${item.maxLength}` : '', + item.minimum !== undefined ? `minimum-${item.minimum}` : '', + item.maximum !== undefined ? `maximum-${item.maximum}` : '', + item.exclusiveMinimum !== undefined + ? `exclusiveMinimum-${item.exclusiveMinimum}` + : '', + item.exclusiveMaximum !== undefined + ? `exclusiveMaximum-${item.exclusiveMaximum}` + : '', + item.minItems !== undefined ? `minItems-${item.minItems}` : '', + item.maxItems !== undefined ? `maxItems-${item.maxItems}` : '', + item.pattern !== undefined ? `pattern-${item.pattern}` : '', + ].join(''); + + const typeId = `${item.$ref ?? ''}${item.type ?? ''}${constant}${format}${constraints}`; if (!typeIds.includes(typeId)) { typeIds.push(typeId); uniqueItems.push(item); From 31b3933b2caa1085dbead1ef94695bca4b83d6ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:35:59 +0000 Subject: [PATCH 3/4] Add changeset for Zod union deduplication fix Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com> --- .changeset/fix-zod-union-deduplication.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/fix-zod-union-deduplication.md diff --git a/.changeset/fix-zod-union-deduplication.md b/.changeset/fix-zod-union-deduplication.md new file mode 100644 index 0000000000..64126f8d3f --- /dev/null +++ b/.changeset/fix-zod-union-deduplication.md @@ -0,0 +1,13 @@ +--- +"@hey-api/openapi-ts": patch +--- + +Fix Zod schema union deduplication by including validation constraints in type ID + +The Zod plugin was incorrectly deduplicating schemas with different validation constraints when using `anyOf`, resulting in incomplete union schemas. This fix ensures that schemas with different validation constraints (like `minLength`, `maxLength`, `minimum`, `maximum`, `pattern`, etc.) are properly preserved in unions. + +For example, with an `anyOf` containing strings with different length constraints: +- Before: `z.string().length(5)` (incorrect - only one constraint) +- After: `z.union([z.string().length(5), z.string().length(2)])` (correct - proper union) + +Fixes issue where the `deduplicateSchema` function was only considering `$ref`, `type`, `const`, and `format` when creating type identifiers, but ignored validation constraints. \ No newline at end of file From 8bd26d53e09ac55afe8a1e1540a3043ffaccfd44 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 17 Sep 2025 22:51:54 +0800 Subject: [PATCH 4/4] Update fix-zod-union-deduplication.md --- .changeset/fix-zod-union-deduplication.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.changeset/fix-zod-union-deduplication.md b/.changeset/fix-zod-union-deduplication.md index 64126f8d3f..a0c4ee6e1c 100644 --- a/.changeset/fix-zod-union-deduplication.md +++ b/.changeset/fix-zod-union-deduplication.md @@ -2,12 +2,4 @@ "@hey-api/openapi-ts": patch --- -Fix Zod schema union deduplication by including validation constraints in type ID - -The Zod plugin was incorrectly deduplicating schemas with different validation constraints when using `anyOf`, resulting in incomplete union schemas. This fix ensures that schemas with different validation constraints (like `minLength`, `maxLength`, `minimum`, `maximum`, `pattern`, etc.) are properly preserved in unions. - -For example, with an `anyOf` containing strings with different length constraints: -- Before: `z.string().length(5)` (incorrect - only one constraint) -- After: `z.union([z.string().length(5), z.string().length(2)])` (correct - proper union) - -Fixes issue where the `deduplicateSchema` function was only considering `$ref`, `type`, `const`, and `format` when creating type identifiers, but ignored validation constraints. \ No newline at end of file +fix(parser): expand schema deduplication by including validation constraints in type ID