Skip to content

Commit 4ddb0d4

Browse files
fix(openapi-typescript): handle nullable schemas
1 parent d2de5c7 commit 4ddb0d4

File tree

3 files changed

+128
-14
lines changed

3 files changed

+128
-14
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": minor
3+
---
4+
5+
Allow all types except UNKNOWN to be nullable

packages/openapi-typescript/src/transform/schema-object.ts

+10-14
Original file line numberDiff line numberDiff line change
@@ -257,24 +257,20 @@ export function transformSchemaObjectWithComposition(
257257
}
258258
}
259259

260-
// if final type could be generated, return intersection of all members
261-
if (finalType) {
262-
// deprecated nullable
263-
if (schemaObject.nullable && !schemaObject.default) {
264-
return tsNullable([finalType]);
260+
// When no final type can be generated, fall back to unknown type (or related variants)
261+
if (!finalType) {
262+
if ("type" in schemaObject) {
263+
finalType = tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER);
264+
} else {
265+
finalType = UNKNOWN;
265266
}
266-
return finalType;
267267
}
268-
// otherwise fall back to unknown type (or related variants)
269-
else {
270-
// fallback: unknown
271-
if (!("type" in schemaObject)) {
272-
return UNKNOWN;
273-
}
274268

275-
// if no type could be generated, fall back to “empty object” type
276-
return tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER);
269+
if (finalType !== UNKNOWN && schemaObject.nullable && !schemaObject.default) {
270+
finalType = tsNullable([finalType]);
277271
}
272+
273+
return finalType;
278274
}
279275

280276
/**

packages/openapi-typescript/test/index.test.ts

+113
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,119 @@ export enum ApiPaths {
759759
},
760760
},
761761
],
762+
[
763+
"nullable > 3.0 syntax",
764+
{
765+
given: {
766+
openapi: "3.0.3",
767+
info: {
768+
title: "Test",
769+
version: "0",
770+
},
771+
paths: {},
772+
components: {
773+
schemas: {
774+
obj1: {
775+
title: "Nullable object",
776+
type: "object",
777+
properties: {
778+
id: {
779+
type: "string",
780+
},
781+
},
782+
nullable: true,
783+
},
784+
obj2: {
785+
title: "Nullable empty object",
786+
type: "object",
787+
nullable: true,
788+
},
789+
str: {
790+
title: "Nullable string",
791+
type: "string",
792+
nullable: true,
793+
},
794+
},
795+
},
796+
},
797+
want: `export type paths = Record<string, never>;
798+
export type webhooks = Record<string, never>;
799+
export interface components {
800+
schemas: {
801+
/** Nullable object */
802+
obj1: {
803+
id?: string;
804+
} | null;
805+
/** Nullable empty object */
806+
obj2: Record<string, never> | null;
807+
/** Nullable string */
808+
str: string | null;
809+
};
810+
responses: never;
811+
parameters: never;
812+
requestBodies: never;
813+
headers: never;
814+
pathItems: never;
815+
}
816+
export type $defs = Record<string, never>;
817+
export type operations = Record<string, never>;`,
818+
},
819+
],
820+
[
821+
"nullable > 3.1 syntax",
822+
{
823+
given: {
824+
openapi: "3.1.0",
825+
info: {
826+
title: "Test",
827+
version: "0",
828+
},
829+
paths: {},
830+
components: {
831+
schemas: {
832+
obj1: {
833+
title: "Nullable object",
834+
type: ["object", "null"],
835+
properties: {
836+
id: {
837+
type: "string",
838+
},
839+
},
840+
},
841+
obj2: {
842+
title: "Nullable empty object",
843+
type: ["object", "null"],
844+
},
845+
str: {
846+
title: "Nullable string",
847+
type: ["string", "null"],
848+
},
849+
},
850+
},
851+
},
852+
want: `export type paths = Record<string, never>;
853+
export type webhooks = Record<string, never>;
854+
export interface components {
855+
schemas: {
856+
/** Nullable object */
857+
obj1: {
858+
id?: string;
859+
} | null;
860+
/** Nullable empty object */
861+
obj2: Record<string, never> | null;
862+
/** Nullable string */
863+
str: string | null;
864+
};
865+
responses: never;
866+
parameters: never;
867+
requestBodies: never;
868+
headers: never;
869+
pathItems: never;
870+
}
871+
export type $defs = Record<string, never>;
872+
export type operations = Record<string, never>;`,
873+
},
874+
],
762875
];
763876

764877
for (const [testName, { given, want, options, ci }] of tests) {

0 commit comments

Comments
 (0)