Skip to content

Commit 4a6475e

Browse files
authored
Improve generated types for unknown schema objects (#769)
Fixes #693, #554
1 parent 3db1e4f commit 4a6475e

17 files changed

+3758
-3705
lines changed

examples/stripe-openapi2.ts

+472-472
Large diffs are not rendered by default.

examples/stripe-openapi3.ts

+218-216
Large diffs are not rendered by default.

src/transform/schema.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function transformSchemaObj(node: any, options: TransformSchemaObjOptions
9999
case "number":
100100
case "boolean":
101101
case "unknown": {
102-
output += nodeType(node) || "any";
102+
output += nodeType(node);
103103
break;
104104
}
105105
case "enum": {
@@ -139,7 +139,7 @@ export function transformSchemaObj(node: any, options: TransformSchemaObjOptions
139139
(node.additionalProperties === undefined && options.additionalProperties && options.version === 3)
140140
) {
141141
if ((node.additionalProperties ?? true) === true || Object.keys(node.additionalProperties).length === 0) {
142-
additionalProperties = `{ ${readonly}[key: string]: any }`;
142+
additionalProperties = `{ ${readonly}[key: string]: unknown }`;
143143
} else if (typeof node.additionalProperties === "object") {
144144
const oneOf: any[] | undefined = (node.additionalProperties as any).oneOf || undefined; // TypeScript does a really bad job at inference here, so we enforce a type
145145
const anyOf: any[] | undefined = (node.additionalProperties as any).anyOf || undefined; // "
@@ -149,7 +149,7 @@ export function transformSchemaObj(node: any, options: TransformSchemaObjOptions
149149
additionalProperties = `{ ${readonly}[key: string]: ${transformAnyOf(anyOf, options)}; }`;
150150
} else {
151151
additionalProperties = `{ ${readonly}[key: string]: ${
152-
transformSchemaObj(node.additionalProperties, options) || "any"
152+
transformSchemaObj(node.additionalProperties, options) || "unknown"
153153
}; }`;
154154
}
155155
}
@@ -172,7 +172,7 @@ export function transformSchemaObj(node: any, options: TransformSchemaObjOptions
172172
if (Array.isArray(node.items)) {
173173
output += `${readonly}${tsTupleOf(node.items.map((node: any) => transformSchemaObj(node, options)))}`;
174174
} else {
175-
output += `${readonly}${tsArrayOf(node.items ? transformSchemaObj(node.items as any, options) : "any")}`;
175+
output += `${readonly}${tsArrayOf(node.items ? transformSchemaObj(node.items as any, options) : "unknown")}`;
176176
}
177177
break;
178178
}

src/utils.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ type SchemaObjectType =
4343
| "ref"
4444
| "string"
4545
| "unknown";
46-
export function nodeType(obj: any): SchemaObjectType | undefined {
46+
export function nodeType(obj: any): SchemaObjectType {
4747
if (!obj || typeof obj !== "object") {
48-
return undefined;
48+
return "unknown";
4949
}
5050

5151
if (obj.$ref) {
@@ -77,13 +77,20 @@ export function nodeType(obj: any): SchemaObjectType | undefined {
7777
return "array";
7878
}
7979

80-
// file
81-
if (obj.type === "file") {
82-
return "unknown";
80+
// object
81+
if (
82+
obj.type === "object" ||
83+
obj.hasOwnProperty("allOf") ||
84+
obj.hasOwnProperty("anyOf") ||
85+
obj.hasOwnProperty("oneOf") ||
86+
obj.hasOwnProperty("properties") ||
87+
obj.hasOwnProperty("additionalProperties")
88+
) {
89+
return "object";
8390
}
8491

85-
// return object by default
86-
return "object";
92+
// return unknown by default
93+
return "unknown";
8794
}
8895

8996
/** Return OpenAPI version from definition */

tests/request.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ describe("requestBodies", () => {
8989
content: {
9090
"application/json": {
9191
test?: string;
92-
} & { [key: string]: any };
92+
} & { [key: string]: unknown };
9393
};
9494
};`)
9595
);

tests/schema.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe("SchemaObject", () => {
5858
expect(transform(objUnknown, { ...defaults })).toBe(`{ [key: string]: unknown }`);
5959

6060
// empty
61-
expect(transform({}, { ...defaults })).toBe(`{ [key: string]: unknown }`);
61+
expect(transform({}, { ...defaults })).toBe(`unknown`);
6262

6363
// nullable
6464
expect(transform(objNullable, { ...defaults })).toBe(`({\n"string"?: string;\n\n}) | null`);
@@ -74,7 +74,7 @@ describe("SchemaObject", () => {
7474
`{\nreadonly "object"?: {\nreadonly "string"?: string;\nreadonly "number"?: components["schemas"]["object_ref"];\n\n};\n\n}`
7575
);
7676
expect(transform(objUnknown, opts)).toBe(`{ readonly [key: string]: unknown }`);
77-
expect(transform({}, opts)).toBe(`{ readonly [key: string]: unknown }`);
77+
expect(transform({}, opts)).toBe(`unknown`);
7878
expect(transform(objNullable, opts)).toBe(`({\nreadonly "string"?: string;\n\n}) | null`);
7979
expect(transform(objRequired, opts)).toBe(`{\nreadonly "required": string;\nreadonly "optional"?: boolean;\n\n}`);
8080
});
@@ -218,10 +218,10 @@ describe("SchemaObject", () => {
218218
describe("advanced", () => {
219219
it("additionalProperties", () => {
220220
// boolean
221-
expect(transform({ additionalProperties: true }, { ...defaults })).toBe(`{ [key: string]: any }`);
221+
expect(transform({ additionalProperties: true }, { ...defaults })).toBe(`{ [key: string]: unknown }`);
222222

223223
// empty object
224-
expect(transform({ additionalProperties: {} }, { ...defaults })).toBe(`{ [key: string]: any }`);
224+
expect(transform({ additionalProperties: {} }, { ...defaults })).toBe(`{ [key: string]: unknown }`);
225225

226226
// type
227227
expect(transform({ additionalProperties: { type: "string" } }, { ...defaults })).toBe(

0 commit comments

Comments
 (0)