Skip to content

Commit 4339d72

Browse files
fix(openapi-typescript): handle nullable schemas
1 parent ca68475 commit 4339d72

File tree

2 files changed

+188
-14
lines changed

2 files changed

+188
-14
lines changed

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

+178
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,184 @@ export type operations = Record<string, never>;`,
694694
},
695695
},
696696
],
697+
[
698+
"nullable > old syntax",
699+
{
700+
given: {
701+
openapi: "3.1",
702+
info: { title: "Test", version: "1.0" },
703+
components: {
704+
schemas: {
705+
NullableEmptyObject: {
706+
nullable: true,
707+
properties: {},
708+
title: "NullableEmptyObject",
709+
type: "object",
710+
},
711+
NullableObject: {
712+
nullable: true,
713+
properties: {
714+
name: {
715+
type: "string",
716+
},
717+
},
718+
title: "NullableObject",
719+
type: "object",
720+
},
721+
NullableString: {
722+
nullable: true,
723+
title: "NullableString",
724+
type: "string",
725+
},
726+
},
727+
},
728+
},
729+
want: `export type paths = Record<string, never>;
730+
export type webhooks = Record<string, never>;
731+
export interface components {
732+
schemas: {
733+
/** NullableEmptyObject */
734+
NullableEmptyObject: Record<string, never> | null;
735+
/** NullableObject */
736+
NullableObject: {
737+
name?: string;
738+
} | null;
739+
/** NullableString */
740+
NullableString: string | null;
741+
};
742+
responses: never;
743+
parameters: never;
744+
requestBodies: never;
745+
headers: never;
746+
pathItems: never;
747+
}
748+
export type $defs = Record<string, never>;
749+
export type operations = Record<string, never>;`,
750+
},
751+
],
752+
[
753+
"nullable > new syntax",
754+
{
755+
given: {
756+
openapi: "3.1",
757+
info: { title: "Test", version: "0" },
758+
components: {
759+
schemas: {
760+
obj1: {
761+
oneOf: [
762+
{
763+
type: "object",
764+
properties: {
765+
id: { type: "string" },
766+
},
767+
},
768+
{ type: "null" },
769+
],
770+
},
771+
},
772+
},
773+
},
774+
want: `export type paths = Record<string, never>;
775+
export type webhooks = Record<string, never>;
776+
export interface components {
777+
schemas: {
778+
obj1: {
779+
id?: string;
780+
} | null;
781+
};
782+
responses: never;
783+
parameters: never;
784+
requestBodies: never;
785+
headers: never;
786+
pathItems: never;
787+
}
788+
export type $defs = Record<string, never>;
789+
export type operations = Record<string, never>;`,
790+
},
791+
],
792+
[
793+
"nullable > old syntax and object with ref",
794+
{
795+
given: {
796+
openapi: "3.1",
797+
info: { title: "Test", version: "0" },
798+
components: {
799+
schemas: {
800+
obj1Ref: {
801+
properties: {
802+
id: { type: "string" },
803+
},
804+
},
805+
obj1: {
806+
type: "object",
807+
nullable: true,
808+
$ref: "#/components/schemas/obj1Ref",
809+
},
810+
},
811+
},
812+
},
813+
want: `export type paths = Record<string, never>;
814+
export type webhooks = Record<string, never>;
815+
export interface components {
816+
schemas: {
817+
obj1Ref: {
818+
id?: string;
819+
};
820+
obj1: components["schemas"]["obj1Ref"] | null;
821+
};
822+
responses: never;
823+
parameters: never;
824+
requestBodies: never;
825+
headers: never;
826+
pathItems: never;
827+
}
828+
export type $defs = Record<string, never>;
829+
export type operations = Record<string, never>;`,
830+
},
831+
],
832+
[
833+
"nullable > new syntax and object with ref",
834+
{
835+
given: {
836+
openapi: "3.1",
837+
info: { title: "Test", version: "0" },
838+
components: {
839+
schemas: {
840+
obj1Ref: {
841+
properties: {
842+
id: { type: "string" },
843+
},
844+
},
845+
obj1: {
846+
oneOf: [
847+
{
848+
$ref: "#/components/schemas/obj1Ref",
849+
},
850+
{ type: "null" },
851+
],
852+
},
853+
},
854+
},
855+
},
856+
want: `export type paths = Record<string, never>;
857+
export type webhooks = Record<string, never>;
858+
export interface components {
859+
schemas: {
860+
obj1Ref: {
861+
id?: string;
862+
};
863+
obj1: components["schemas"]["obj1Ref"] | 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+
],
697875
];
698876

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

0 commit comments

Comments
 (0)