diff --git a/packages/openapi-zod-ts/src/__tests__/client.test.ts b/packages/openapi-zod-ts/src/__tests__/client.test.ts index 2ac609f..c4e0164 100644 --- a/packages/openapi-zod-ts/src/__tests__/client.test.ts +++ b/packages/openapi-zod-ts/src/__tests__/client.test.ts @@ -989,6 +989,67 @@ describe('coverage: queryParamType edge cases (lines 70, 81)', () => { const out = generateClient(spec as OpenAPIV3_1.Document).content expect(out).toContain('filter?: unknown') }) + + it('nullable array query param (type: ["array","null"]) with integer items → number[] type (#303)', () => { + // Regression: type: ['array', 'null'] previously fell through to primitiveToTs('array') + // which returned 'unknown'. The fix treats it identically to type: 'array'. + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'T', version: '1' }, + paths: { + '/reports': { + get: { + operationId: 'listReports', + parameters: [ + { + name: 'ids[]', + in: 'query', + schema: { type: ['array', 'null'], items: { type: 'integer' } } as any, + }, + ], + responses: { + '200': { content: { 'application/json': { schema: { type: 'string' } } } }, + }, + }, + }, + }, + } + const out = generateClient(spec as OpenAPIV3_1.Document).content + // Type must be number[], not unknown (toPropertyKey wraps the bracket name in single quotes) + expect(out).toContain("'ids[]'?: number[]") + // Serialization must use append loop, not set + expect(out).toContain("searchParams.append('ids[]'") + expect(out).not.toContain("searchParams.set('ids[]'") + }) + + it('nullable array query param (type: ["array","null"]) with string items → string[] type (#303)', () => { + // Non-integer items: nullable array query param should be string[], not unknown. + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'T', version: '1' }, + paths: { + '/search': { + get: { + operationId: 'searchItems', + parameters: [ + { + name: 'tags', + in: 'query', + schema: { type: ['array', 'null'], items: { type: 'string' } } as any, + }, + ], + responses: { + '200': { content: { 'application/json': { schema: { type: 'string' } } } }, + }, + }, + }, + }, + } + const out = generateClient(spec as OpenAPIV3_1.Document).content + expect(out).toContain('tags?: string[]') + expect(out).toContain("searchParams.append('tags'") + expect(out).not.toContain("searchParams.set('tags'") + }) }) describe('coverage: deriveOperationName via no-operationId spec (lines 101-123, 540)', () => { diff --git a/packages/openapi-zod-ts/src/plugins/client.ts b/packages/openapi-zod-ts/src/plugins/client.ts index 055e602..85e388a 100644 --- a/packages/openapi-zod-ts/src/plugins/client.ts +++ b/packages/openapi-zod-ts/src/plugins/client.ts @@ -247,7 +247,17 @@ function queryParamType(schema: SchemaObject | ReferenceObject | undefined): str // non-null primitive and drop the null member. if (Array.isArray(s.type)) { const nonNull = (s.type as string[]).filter((t) => t !== 'null') - if (nonNull.length === 1) return primitiveToTs(nonNull[0]) + if (nonNull.length === 1) { + // type: ['array', 'null'] — nullable array: treat identically to type: 'array' + if (nonNull[0] === 'array') { + const items = (s as OpenAPIV3_1.ArraySchemaObject).items as SchemaObject | undefined + if (items !== undefined && (items.type === 'integer' || items.type === 'number')) { + return 'number[]' + } + return 'string[]' + } + return primitiveToTs(nonNull[0]) + } return 'string' } if (s.type === 'array') {