diff --git a/.changeset/tasty-knives-matter.md b/.changeset/tasty-knives-matter.md new file mode 100644 index 000000000..4efe9c01c --- /dev/null +++ b/.changeset/tasty-knives-matter.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": patch +--- + +Fall back to TypeScript unions for long oneOf lists diff --git a/packages/openapi-typescript/src/utils.ts b/packages/openapi-typescript/src/utils.ts index 9061d7b7c..e0dc0455b 100644 --- a/packages/openapi-typescript/src/utils.ts +++ b/packages/openapi-typescript/src/utils.ts @@ -180,9 +180,17 @@ export function tsNonNullable(type: string): string { return `NonNullable<${type}>`; } -/** OneOf (custom) */ +/** + * OneOf + * TypeScript unions are not exclusive @see https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types + * However, at a certain size, the helper type becomes too complex for inference to work. Hence the > check. + */ export function tsOneOf(...types: string[]): string { - if (types.length === 1) return types[0]; + if (types.length === 1) { + return types[0]; + } else if (types.length > 5) { + return tsUnionOf(...types); + } return `OneOf<[${types.join(", ")}]>`; } diff --git a/packages/openapi-typescript/test/schema-object.test.ts b/packages/openapi-typescript/test/schema-object.test.ts index cd6b2cdf1..7f292d561 100644 --- a/packages/openapi-typescript/test/schema-object.test.ts +++ b/packages/openapi-typescript/test/schema-object.test.ts @@ -335,6 +335,36 @@ describe("Schema Object", () => { }]>`); }); + test("falls back to union at high complexity", () => { + const schema: SchemaObject = { + oneOf: [ + { type: "object", properties: { string: { type: "string" } }, required: ["string"] }, + { type: "object", properties: { boolean: { type: "boolean" } }, required: ["boolean"] }, + { type: "object", properties: { number: { type: "number" } }, required: ["number"] }, + { type: "object", properties: { array: { type: "array", items: { type: "string" } } }, required: ["array"] }, + { type: "object", properties: { object: { type: "object", properties: { string: { type: "string" } }, required: ["string"] } }, required: ["object"] }, + { type: "object", properties: { enum: { type: "string", enum: ["foo", "bar", "baz"] } }, required: ["enum"] }, + ], + }; + const generated = transformSchemaObject(schema, options); + expect(generated).toBe(`{ + string: string; +} | { + boolean: boolean; +} | { + number: number; +} | { + array: (string)[]; +} | { + object: { + string: string; + }; +} | ({ + /** @enum {string} */ + enum: "foo" | "bar" | "baz"; +})`); + }); + test("discriminator", () => { const schema: SchemaObject = { type: "object",