diff --git a/lib/src/openApiToTypescript.test.ts b/lib/src/openApiToTypescript.test.ts index 8e36a07c..f143f3d7 100644 --- a/lib/src/openApiToTypescript.test.ts +++ b/lib/src/openApiToTypescript.test.ts @@ -583,7 +583,7 @@ describe("getSchemaAsTsString with context", () => { expect(printTs(result)).toMatchInlineSnapshot(` "export type Root = Partial<{ user: User | Member; - users: Array<(User | Member) | Array>; + users: Array; basic: number; }>;" `); diff --git a/lib/src/openApiToTypescript.ts b/lib/src/openApiToTypescript.ts index cb7a09b0..9ece1421 100644 --- a/lib/src/openApiToTypescript.ts +++ b/lib/src/openApiToTypescript.ts @@ -202,27 +202,26 @@ TsConversionArgs): ts.Node | TypeDefinitionObject | string => { return schema.nullable ? t.union([t.number(), t.reference("null")]) : t.number(); } - if (schemaType === "array") { - if (schema.items) { - let arrayOfType = getTypescriptFromOpenApi({ - schema: schema.items, - ctx, - meta, - options, - }) as TypeDefinition; - if (typeof arrayOfType === "string") { - if (!ctx) throw new Error("Context is required for circular $ref (recursive schemas)"); - arrayOfType = t.reference(arrayOfType); - } + if (schema.type === "array") { + const itemSchema = schema.items; - return schema.nullable - ? t.union([doWrapReadOnly(t.array(arrayOfType)), t.reference("null")]) - : doWrapReadOnly(t.array(arrayOfType)); + if (!itemSchema) { + return doWrapReadOnly(t.reference("Array", [t.any()])); } - return schema.nullable - ? t.union([doWrapReadOnly(t.array(t.any())), t.reference("null")]) - : doWrapReadOnly(t.array(t.any())); + // Check if items have anyOf + if (itemSchema && "anyOf" in itemSchema) { + const types = itemSchema.anyOf.map( + (subSchema) => + getTypescriptFromOpenApi({ schema: subSchema, ctx, meta, options }) as ts.TypeNode + ); + const unionType = t.union(types); + return doWrapReadOnly(t.reference("Array", [unionType])); + } + + // Existing item type handling + const itemType = getTypescriptFromOpenApi({ schema: itemSchema, ctx, meta, options }) as ts.TypeNode; + return doWrapReadOnly(t.reference("Array", [itemType])); } if (schemaType === "object" || schema.properties || schema.additionalProperties) { @@ -305,7 +304,7 @@ TsConversionArgs): ts.Node | TypeDefinitionObject | string => { } if (!schemaType) return t.unknown(); - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`Unsupported schema type: ${schemaType}`); }; diff --git a/lib/tests/union-array.test.ts b/lib/tests/union-array.test.ts new file mode 100644 index 00000000..ce3052d0 --- /dev/null +++ b/lib/tests/union-array.test.ts @@ -0,0 +1,71 @@ +import type { OpenAPIObject } from "openapi3-ts"; +import { expect, test } from "vitest"; +import { generateZodClientFromOpenAPI } from "../src"; + +// https://github.com/astahmer/openapi-zod-client/issues/49 +test("union-array", async () => { + const openApiDoc: OpenAPIObject = { + openapi: "3.0.0", + info: { + version: "1.0.0", + title: "Union array", + }, + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + properties: { + foo: { type: "integer", enum: [1, 2] }, + }, + }, + Bar: { + type: "object", + properties: { + bar: { type: "string", enum: ["a", "b"] }, + }, + }, + Union: { + type: "object", + properties: { + unionArray: { + items: { + anyOf: [{ $ref: "#/components/schemas/Foo" }, { $ref: "#/components/schemas/Bar" }], + }, + type: "array", + }, + }, + }, + }, + }, + }; + + const output = await generateZodClientFromOpenAPI({ + disableWriteToFile: true, + openApiDoc, + options: { shouldExportAllTypes: true }, + }); + expect(output).toMatchInlineSnapshot(` + "import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; + import { z } from "zod"; + + type Union = Partial<{ + unionArray: Array; + }>; + type Foo = Partial<{ + foo: 1 | 2; + }>; + type Bar = Partial<{ + bar: "a" | "b"; + }>; + + const endpoints = makeApi([]); + + export const api = new Zodios(endpoints); + + export function createApiClient(baseUrl: string, options?: ZodiosOptions) { + return new Zodios(baseUrl, endpoints, options); + } + " + `); +});