diff --git a/.changeset/fix-zod-union-deduplication.md b/.changeset/fix-zod-union-deduplication.md new file mode 100644 index 0000000000..a0c4ee6e1c --- /dev/null +++ b/.changeset/fix-zod-union-deduplication.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix(parser): expand schema deduplication by including validation constraints in type ID 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);