diff --git a/.changeset/mighty-paws-design.md b/.changeset/mighty-paws-design.md new file mode 100644 index 000000000..84d22f643 --- /dev/null +++ b/.changeset/mighty-paws-design.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix(zod): allOf in array items being generated as union instead of intersection diff --git a/packages/openapi-ts-tests/specs/2.0.x/array-items-all-of.yaml b/packages/openapi-ts-tests/specs/2.0.x/array-items-all-of.yaml new file mode 100644 index 000000000..ae2086355 --- /dev/null +++ b/packages/openapi-ts-tests/specs/2.0.x/array-items-all-of.yaml @@ -0,0 +1,40 @@ +swagger: '2.0' +info: + title: OpenAPI 2.0 array items allOf example + version: '1' +definitions: + ArrayWithAllOfObjects: + type: array + items: + allOf: + - type: object + properties: + id: + type: integer + - type: object + properties: + name: + type: string + ArrayWithAllOfPrimitives: + type: array + items: + allOf: + - type: number + - type: string + ArrayWithAllOfRefs: + type: array + items: + allOf: + - $ref: '#/definitions/BaseModel' + - type: object + properties: + extra: + type: string + BaseModel: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: date-time diff --git a/packages/openapi-ts-tests/specs/3.0.x/array-items-all-of.yaml b/packages/openapi-ts-tests/specs/3.0.x/array-items-all-of.yaml new file mode 100644 index 000000000..ce970fd19 --- /dev/null +++ b/packages/openapi-ts-tests/specs/3.0.x/array-items-all-of.yaml @@ -0,0 +1,44 @@ +openapi: 3.0.2 +info: + title: OpenAPI 3.0.2 array items allOf example + version: '1' +components: + schemas: + # Test case 1: Array with allOf of object schemas + ArrayWithAllOfObjects: + type: array + items: + allOf: + - type: object + properties: + id: + type: integer + - type: object + properties: + name: + type: string + # Test case 2: Array with allOf of primitives + ArrayWithAllOfPrimitives: + type: array + items: + allOf: + - type: number + - type: string + # Test case 3: Array with allOf including refs + ArrayWithAllOfRefs: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseModel' + - type: object + properties: + extra: + type: string + BaseModel: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: date-time diff --git a/packages/openapi-ts-tests/specs/3.1.x/array-items-all-of.yaml b/packages/openapi-ts-tests/specs/3.1.x/array-items-all-of.yaml new file mode 100644 index 000000000..fc70640fd --- /dev/null +++ b/packages/openapi-ts-tests/specs/3.1.x/array-items-all-of.yaml @@ -0,0 +1,44 @@ +openapi: 3.1.0 +info: + title: OpenAPI 3.1.0 array items allOf example + version: '1' +components: + schemas: + # Test case 1: Array with allOf of object schemas + ArrayWithAllOfObjects: + type: array + items: + allOf: + - type: object + properties: + id: + type: integer + - type: object + properties: + name: + type: string + # Test case 2: Array with allOf of primitives + ArrayWithAllOfPrimitives: + type: array + items: + allOf: + - type: number + - type: string + # Test case 3: Array with allOf including refs + ArrayWithAllOfRefs: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseModel' + - type: object + properties: + extra: + type: string + BaseModel: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: date-time diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..ef475d167 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..c4af44ea7 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..87e0c90b2 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v4'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..ef475d167 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..c4af44ea7 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..87e0c90b2 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v4'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..ef475d167 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..c4af44ea7 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..87e0c90b2 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v4'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v3/test/openapi.test.ts b/packages/openapi-ts-tests/zod/v3/test/openapi.test.ts index 5605ecc63..e15c35ae4 100644 --- a/packages/openapi-ts-tests/zod/v3/test/openapi.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/openapi.test.ts @@ -35,6 +35,14 @@ for (const version of versions) { }); const scenarios = [ + { + config: createConfig({ + input: 'array-items-all-of.yaml', + output: 'array-items-all-of', + }), + description: + 'generates correct array when items use allOf (intersection)', + }, { config: createConfig({ input: 'full.yaml', diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..95f43fa31 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..b5f786712 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..a17f5ae50 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..95f43fa31 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..b5f786712 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..a17f5ae50 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..95f43fa31 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zArrayWithAllOfObjects = z.array(z.intersection(z.object({ + id: z.optional(z.int()) +}), z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(z.intersection(zBaseModel, z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..b5f786712 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.number().int().optional() +}).and(z.object({ + name: z.string().optional() +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.number().int().optional(), + createdAt: z.string().datetime().optional() +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.string().optional() +}))); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts new file mode 100644 index 000000000..a17f5ae50 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/array-items-all-of/zod.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zArrayWithAllOfObjects = z.array(z.object({ + id: z.optional(z.int()) +}).and(z.object({ + name: z.optional(z.string()) +}))); + +export const zArrayWithAllOfPrimitives = z.array(z.intersection(z.number(), z.string())); + +export const zBaseModel = z.object({ + id: z.optional(z.int()), + createdAt: z.optional(z.iso.datetime()) +}); + +export const zArrayWithAllOfRefs = z.array(zBaseModel.and(z.object({ + extra: z.optional(z.string()) +}))); diff --git a/packages/openapi-ts-tests/zod/v4/test/openapi.test.ts b/packages/openapi-ts-tests/zod/v4/test/openapi.test.ts index 5605ecc63..e15c35ae4 100644 --- a/packages/openapi-ts-tests/zod/v4/test/openapi.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/openapi.test.ts @@ -35,6 +35,14 @@ for (const version of versions) { }); const scenarios = [ + { + config: createConfig({ + input: 'array-items-all-of.yaml', + output: 'array-items-all-of', + }), + description: + 'generates correct array when items use allOf (intersection)', + }, { config: createConfig({ input: 'full.yaml', diff --git a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts index d6f6478b1..4c03374eb 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts @@ -69,31 +69,60 @@ const arrayTypeToZodSchema = ({ }); } else { if (schema.logicalOperator === 'and') { - // TODO: parser - handle intersection - // return tsc.typeArrayNode( - // tsc.typeIntersectionNode({ types: itemExpressions }), - // ); - } - - result.expression = tsc.callExpression({ - functionName: tsc.propertyAccessExpression({ - expression: zSymbol.placeholder, - name: identifiers.array, - }), - parameters: [ - tsc.callExpression({ + const firstSchema = schema.items![0]!; + // we want to add an intersection, but not every schema can use the same API. + // if the first item contains another array or not an object, we cannot use + // `.intersection()` as that does not exist on `.union()` and non-object schemas. + let intersectionExpression: ts.Expression; + if ( + firstSchema.logicalOperator === 'or' || + (firstSchema.type && firstSchema.type !== 'object') + ) { + intersectionExpression = tsc.callExpression({ functionName: tsc.propertyAccessExpression({ expression: zSymbol.placeholder, - name: identifiers.union, + name: identifiers.intersection, }), - parameters: [ - tsc.arrayLiteralExpression({ - elements: itemExpressions, + parameters: itemExpressions, + }); + } else { + intersectionExpression = itemExpressions[0]!; + for (let i = 1; i < itemExpressions.length; i++) { + intersectionExpression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.intersection, }), - ], + parameters: [intersectionExpression, itemExpressions[i]!], + }); + } + } + + result.expression = tsc.callExpression({ + functionName, + parameters: [intersectionExpression], + }); + } else { + result.expression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.array, }), - ], - }); + parameters: [ + tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.union, + }), + parameters: [ + tsc.arrayLiteralExpression({ + elements: itemExpressions, + }), + ], + }), + ], + }); + } } } diff --git a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts index feeb1fb5d..ab7fc5535 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts @@ -72,31 +72,60 @@ const arrayTypeToZodSchema = ({ }); } else { if (schema.logicalOperator === 'and') { - // TODO: parser - handle intersection - // return tsc.typeArrayNode( - // tsc.typeIntersectionNode({ types: itemExpressions }), - // ); - } - - arrayExpression = tsc.callExpression({ - functionName: tsc.propertyAccessExpression({ - expression: zSymbol.placeholder, - name: identifiers.array, - }), - parameters: [ - tsc.callExpression({ + const firstSchema = schema.items![0]!; + // we want to add an intersection, but not every schema can use the same API. + // if the first item contains another array or not an object, we cannot use + // `.and()` as that does not exist on `.union()` and non-object schemas. + let intersectionExpression: ts.Expression; + if ( + firstSchema.logicalOperator === 'or' || + (firstSchema.type && firstSchema.type !== 'object') + ) { + intersectionExpression = tsc.callExpression({ functionName: tsc.propertyAccessExpression({ expression: zSymbol.placeholder, - name: identifiers.union, + name: identifiers.intersection, }), - parameters: [ - tsc.arrayLiteralExpression({ - elements: itemExpressions, + parameters: itemExpressions, + }); + } else { + intersectionExpression = itemExpressions[0]!; + for (let i = 1; i < itemExpressions.length; i++) { + intersectionExpression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: intersectionExpression, + name: identifiers.and, }), - ], + parameters: [itemExpressions[i]!], + }); + } + } + + arrayExpression = tsc.callExpression({ + functionName, + parameters: [intersectionExpression], + }); + } else { + arrayExpression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.array, }), - ], - }); + parameters: [ + tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.union, + }), + parameters: [ + tsc.arrayLiteralExpression({ + elements: itemExpressions, + }), + ], + }), + ], + }); + } } } diff --git a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts index dba3157c1..27ce9fe2b 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts @@ -69,31 +69,60 @@ const arrayTypeToZodSchema = ({ }); } else { if (schema.logicalOperator === 'and') { - // TODO: parser - handle intersection - // return tsc.typeArrayNode( - // tsc.typeIntersectionNode({ types: itemExpressions }), - // ); - } - - result.expression = tsc.callExpression({ - functionName: tsc.propertyAccessExpression({ - expression: zSymbol.placeholder, - name: identifiers.array, - }), - parameters: [ - tsc.callExpression({ + const firstSchema = schema.items![0]!; + // we want to add an intersection, but not every schema can use the same API. + // if the first item contains another array or not an object, we cannot use + // `.and()` as that does not exist on `.union()` and non-object schemas. + let intersectionExpression: ts.Expression; + if ( + firstSchema.logicalOperator === 'or' || + (firstSchema.type && firstSchema.type !== 'object') + ) { + intersectionExpression = tsc.callExpression({ functionName: tsc.propertyAccessExpression({ expression: zSymbol.placeholder, - name: identifiers.union, + name: identifiers.intersection, }), - parameters: [ - tsc.arrayLiteralExpression({ - elements: itemExpressions, + parameters: itemExpressions, + }); + } else { + intersectionExpression = itemExpressions[0]!; + for (let i = 1; i < itemExpressions.length; i++) { + intersectionExpression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: intersectionExpression, + name: identifiers.and, }), - ], + parameters: [itemExpressions[i]!], + }); + } + } + + result.expression = tsc.callExpression({ + functionName, + parameters: [intersectionExpression], + }); + } else { + result.expression = tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.array, }), - ], - }); + parameters: [ + tsc.callExpression({ + functionName: tsc.propertyAccessExpression({ + expression: zSymbol.placeholder, + name: identifiers.union, + }), + parameters: [ + tsc.arrayLiteralExpression({ + elements: itemExpressions, + }), + ], + }), + ], + }); + } } }