diff --git a/packages/openapi-typescript/src/transform/schema-object.ts b/packages/openapi-typescript/src/transform/schema-object.ts index 80ec56b51..a41ce0e69 100644 --- a/packages/openapi-typescript/src/transform/schema-object.ts +++ b/packages/openapi-typescript/src/transform/schema-object.ts @@ -257,24 +257,20 @@ export function transformSchemaObjectWithComposition( } } - // if final type could be generated, return intersection of all members - if (finalType) { - // deprecated nullable - if (schemaObject.nullable && !schemaObject.default) { - return tsNullable([finalType]); + // When no final type can be generated, fall back to unknown type (or related variants) + if (!finalType) { + if ("type" in schemaObject) { + finalType = tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER); + } else { + finalType = UNKNOWN; } - return finalType; } - // otherwise fall back to unknown type (or related variants) - else { - // fallback: unknown - if (!("type" in schemaObject)) { - return UNKNOWN; - } - // if no type could be generated, fall back to “empty object” type - return tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER); + if (finalType !== UNKNOWN && schemaObject.nullable && !schemaObject.default) { + finalType = tsNullable([finalType]); } + + return finalType; } /** diff --git a/packages/openapi-typescript/test/index.test.ts b/packages/openapi-typescript/test/index.test.ts index 46b01c8b8..372c69fbc 100644 --- a/packages/openapi-typescript/test/index.test.ts +++ b/packages/openapi-typescript/test/index.test.ts @@ -694,6 +694,184 @@ export type operations = Record;`, }, }, ], + [ + "nullable > old syntax", + { + given: { + openapi: "3.1", + info: { title: "Test", version: "1.0" }, + components: { + schemas: { + NullableEmptyObject: { + nullable: true, + properties: {}, + title: "NullableEmptyObject", + type: "object", + }, + NullableObject: { + nullable: true, + properties: { + name: { + type: "string", + }, + }, + title: "NullableObject", + type: "object", + }, + NullableString: { + nullable: true, + title: "NullableString", + type: "string", + }, + }, + }, + }, + want: `export type paths = Record; +export type webhooks = Record; +export interface components { + schemas: { + /** NullableEmptyObject */ + NullableEmptyObject: Record | null; + /** NullableObject */ + NullableObject: { + name?: string; + } | null; + /** NullableString */ + NullableString: string | null; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record;`, + }, + ], + [ + "nullable > new syntax", + { + given: { + openapi: "3.1", + info: { title: "Test", version: "0" }, + components: { + schemas: { + obj1: { + oneOf: [ + { + type: "object", + properties: { + id: { type: "string" }, + }, + }, + { type: "null" }, + ], + }, + }, + }, + }, + want: `export type paths = Record; +export type webhooks = Record; +export interface components { + schemas: { + obj1: { + id?: string; + } | null; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record;`, + }, + ], + [ + "nullable > old syntax and object with ref", + { + given: { + openapi: "3.1", + info: { title: "Test", version: "0" }, + components: { + schemas: { + obj1Ref: { + properties: { + id: { type: "string" }, + }, + }, + obj1: { + type: "object", + nullable: true, + $ref: "#/components/schemas/obj1Ref", + }, + }, + }, + }, + want: `export type paths = Record; +export type webhooks = Record; +export interface components { + schemas: { + obj1Ref: { + id?: string; + }; + obj1: components["schemas"]["obj1Ref"] | null; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record;`, + }, + ], + [ + "nullable > new syntax and object with ref", + { + given: { + openapi: "3.1", + info: { title: "Test", version: "0" }, + components: { + schemas: { + obj1Ref: { + properties: { + id: { type: "string" }, + }, + }, + obj1: { + oneOf: [ + { + $ref: "#/components/schemas/obj1Ref", + }, + { type: "null" }, + ], + }, + }, + }, + }, + want: `export type paths = Record; +export type webhooks = Record; +export interface components { + schemas: { + obj1Ref: { + id?: string; + }; + obj1: components["schemas"]["obj1Ref"] | null; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record;`, + }, + ], ]; for (const [testName, { given, want, options, ci }] of tests) {