From 82f259464839a7d440ccdb235d41a2032f7bbdd4 Mon Sep 17 00:00:00 2001 From: Morgan <256248948+morgan-coded@users.noreply.github.com> Date: Sun, 28 Jun 2026 18:49:18 -0500 Subject: [PATCH 1/2] fix: handle non-array complex schema keywords --- .../base-schema-parsers/complex.ts | 11 +-- .../base-schema-parsers/object.ts | 19 +++-- tests/spec/issue-1798/basic.test.ts | 34 ++++++++ tests/spec/issue-1798/schema.json | 81 +++++++++++++++++++ 4 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 tests/spec/issue-1798/basic.test.ts create mode 100644 tests/spec/issue-1798/schema.json diff --git a/src/schema-parser/base-schema-parsers/complex.ts b/src/schema-parser/base-schema-parsers/complex.ts index fb0facc20..8767d084f 100644 --- a/src/schema-parser/base-schema-parsers/complex.ts +++ b/src/schema-parser/base-schema-parsers/complex.ts @@ -5,6 +5,11 @@ import { MonoSchemaParser } from "../mono-schema-parser.js"; export class ComplexSchemaParser extends MonoSchemaParser { override parse() { const complexType = this.schemaUtils.getComplexType(this.schema); + const complexSchemaItems: Array> = Array.isArray( + this.schema[complexType], + ) + ? this.schema[complexType] + : []; const simpleSchema = omit( this.schema, Object.keys(this.schemaParser._complexSchemaParsers), @@ -23,11 +28,7 @@ export class ComplexSchemaParser extends MonoSchemaParser { name: this.typeName, description: this.schemaFormatters.formatDescription( this.schema.description || - compact( - (this.schema[complexType] || []).map( - (item: any) => item?.description, - ), - )[0] || + compact(complexSchemaItems.map((item) => item.description))[0] || "", ), content: diff --git a/src/schema-parser/base-schema-parsers/object.ts b/src/schema-parser/base-schema-parsers/object.ts index d65e77d65..ce4267cc6 100644 --- a/src/schema-parser/base-schema-parsers/object.ts +++ b/src/schema-parser/base-schema-parsers/object.ts @@ -58,6 +58,16 @@ export class ObjectSchemaParser extends MonoSchemaParser { const complexType = this.schemaUtils.getComplexType(property); const rawDataComplexType = this.schemaUtils.getComplexType(rawTypeData); + const propertyComplexSchemaItems: Array> = + Array.isArray((property as Record)[complexType]) + ? ((property as Record)[complexType] as Array< + Record + >) + : []; + const rawDataComplexSchemaItems: Array> = + Array.isArray(rawTypeData[rawDataComplexType]) + ? (rawTypeData[rawDataComplexType] as Array>) + : []; propertiesContent.push({ ...(property as object), @@ -66,16 +76,11 @@ export class ObjectSchemaParser extends MonoSchemaParser { description: (property as Record).description || compact( - ( - ((property as Record)[complexType] as any[]) || - [] - ).map((item: any) => item?.description), + propertyComplexSchemaItems.map((item) => item?.description), )[0] || rawTypeData.description || compact( - ((rawTypeData[rawDataComplexType] as any[]) || []).map( - (item: any) => item?.description, - ), + rawDataComplexSchemaItems.map((item) => item?.description), )[0] || "", isRequired: required, diff --git a/tests/spec/issue-1798/basic.test.ts b/tests/spec/issue-1798/basic.test.ts new file mode 100644 index 000000000..1f09dcf7c --- /dev/null +++ b/tests/spec/issue-1798/basic.test.ts @@ -0,0 +1,34 @@ +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import { generateApi } from "../../../src/index.js"; + +describe("issue-1798", async () => { + let tmpdir = ""; + + beforeAll(async () => { + tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "swagger-typescript-api")); + }); + + afterAll(async () => { + await fs.rm(tmpdir, { recursive: true }); + }); + + test("generates schemas with object-shaped not clauses", async () => { + await generateApi({ + fileName: "client", + input: path.resolve(import.meta.dirname, "schema.json"), + output: tmpdir, + silent: true, + sortTypes: true, + cleanOutput: true, + }); + + const content = await fs.readFile(path.join(tmpdir, "client.ts"), { + encoding: "utf8", + }); + + expect(content).toContain("export interface ProductVariant"); + }); +}); diff --git a/tests/spec/issue-1798/schema.json b/tests/spec/issue-1798/schema.json new file mode 100644 index 000000000..2c885a7ac --- /dev/null +++ b/tests/spec/issue-1798/schema.json @@ -0,0 +1,81 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "issue 1798", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "schemas": { + "RelatedProductVariant": { + "type": "object", + "properties": { + "variantId": { + "type": "integer" + }, + "variantReferenceKey": { + "type": "string" + }, + "isMainVariant": { + "type": "boolean" + } + } + }, + "ProductVariant": { + "type": "object", + "properties": { + "relatedVariants": { + "oneOf": [ + { + "type": "array", + "minItems": 1, + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/RelatedProductVariant" + }, + { + "type": "object", + "required": ["variantId", "isMainVariant"], + "properties": { + "variantId": { + "type": "integer" + }, + "variantReferenceKey": { + "not": {} + } + } + } + ] + } + }, + { + "type": "array", + "minItems": 1, + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/RelatedProductVariant" + }, + { + "type": "object", + "required": ["variantReferenceKey", "isMainVariant"], + "properties": { + "variantReferenceKey": { + "type": "string" + }, + "variantId": { + "not": {} + } + } + } + ] + } + } + ] + } + } + } + } + } +} From 21995af5b8d2f48b1f02ca663a04534ccece4cc6 Mon Sep 17 00:00:00 2001 From: morgan-coded <256248948+morgan-coded@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:53:12 -0500 Subject: [PATCH 2/2] chore: add changeset --- .changeset/silly-waves-learn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silly-waves-learn.md diff --git a/.changeset/silly-waves-learn.md b/.changeset/silly-waves-learn.md new file mode 100644 index 000000000..9a7b45512 --- /dev/null +++ b/.changeset/silly-waves-learn.md @@ -0,0 +1,5 @@ +--- +"swagger-typescript-api": patch +--- + +Handle object-shaped not schemas without crashing