Skip to content

Commit 93c4409

Browse files
authored
Transform recursively, without JSON.parse() (#223)
1 parent 5dc6c64 commit 93c4409

File tree

14 files changed

+16544
-3215
lines changed

14 files changed

+16544
-3215
lines changed

src/property-mapper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
OpenAPI3SchemaObject,
44
SwaggerToTSOptions,
55
} from "./types";
6-
import { isObjNode, fromEntries } from "./utils";
6+
import { fromEntries } from "./utils";
77

88
export default function propertyMapper<T = any>(
99
schema: T,
@@ -15,7 +15,7 @@ export default function propertyMapper<T = any>(
1515

1616
return JSON.parse(JSON.stringify(schema), (_, node: OpenAPI2SchemaObject) => {
1717
// if no properties, skip
18-
if (!isObjNode(node) || !node.properties) {
18+
if (!node.properties) {
1919
return node;
2020
}
2121

src/utils.ts

Lines changed: 82 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,91 @@
11
import { OpenAPI2, OpenAPI3 } from "./types";
22

3-
// shim for Object.fromEntries() for Node < 13
3+
export function comment(text: string): string {
4+
return `/**
5+
* ${text.trim().replace("\n+$", "").replace(/\n/g, "\n * ")}
6+
*/
7+
`;
8+
}
9+
10+
/** shim for Object.fromEntries() for Node < 13 */
411
export function fromEntries(entries: [string, any][]): object {
512
return entries.reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {});
613
}
714

8-
/**
9-
* Escape/unescape
10-
* JSON handles most of the transformation for us, but it can’t handle refs, so
11-
* they will all get converted to strings. We need a special “this is a ref”
12-
* placeholder to distinguish it from actual strings (also, we can’t assume
13-
* that "string" in JSON is a TypeScript type and not the actual string
14-
* "string")!
15-
*/
16-
export function escape(text: string): string {
17-
return `<@${text}@>`;
18-
}
15+
/** Return type of node (works for v2 or v3, as there are no conflicting types) */
16+
type SchemaObjectType =
17+
| "anyOf"
18+
| "array"
19+
| "boolean"
20+
| "enum"
21+
| "number"
22+
| "object"
23+
| "oneOf"
24+
| "ref"
25+
| "string";
26+
export function nodeType(obj: any): SchemaObjectType | undefined {
27+
if (!obj || typeof obj !== "object") {
28+
return undefined;
29+
}
1930

20-
export function unescape(text: string): string {
21-
// the " replaces surrounding quotes, if any
22-
const REMOVE_OPENER = /["|\\"]?<\@/g;
23-
const REMOVE_CLOSER = /\@>["|\\"]?/g;
24-
const ESCAPE_NEWLINES = /\\n/g;
25-
return text
26-
.replace(REMOVE_OPENER, "")
27-
.replace(REMOVE_CLOSER, "")
28-
.replace(ESCAPE_NEWLINES, "\n");
29-
}
31+
if (obj["$ref"]) {
32+
return "ref";
33+
}
3034

31-
export function isArrayNode(node: any): boolean {
32-
if (!isSchemaObj(node)) {
33-
return false;
35+
// enum
36+
if (Array.isArray(obj.enum)) {
37+
return "enum";
3438
}
35-
return node.type === "array" && node.items;
36-
}
3739

38-
/**
39-
* Return true if object type
40-
*/
41-
export function isObjNode(node: any): boolean {
42-
if (!isSchemaObj(node)) {
43-
return false;
40+
// boolean
41+
if (obj.type === "boolean") {
42+
return "boolean";
4443
}
45-
return (
46-
(node.type && node.type === "object") ||
47-
!!node.properties ||
48-
!!node.allOf ||
49-
!!node.additionalProperties
50-
);
51-
}
5244

53-
/**
54-
* Return true if anyOf type
55-
*/
56-
export function isAnyOfNode(node: any): boolean {
57-
if (!isSchemaObj(node)) {
58-
return false;
45+
// string
46+
if (
47+
["binary", "byte", "date", "dateTime", "password", "string"].includes(
48+
obj.type
49+
)
50+
) {
51+
return "string";
5952
}
60-
return Array.isArray(node.anyOf);
61-
}
6253

63-
/**
64-
* Return true if oneOf type
65-
*/
66-
export function isOneOfNode(node: any): boolean {
67-
if (!isSchemaObj(node)) {
68-
return false;
54+
// number
55+
if (["double", "float", "integer", "number"].includes(obj.type)) {
56+
return "number";
6957
}
70-
return Array.isArray(node.oneOf);
71-
}
7258

73-
/**
74-
* Return true if item is schema object
75-
*/
76-
export function isSchemaObj(obj: any): boolean {
77-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
78-
return false;
59+
// anyOf
60+
if (Array.isArray(obj.anyOf)) {
61+
return "anyOf";
7962
}
80-
return (
81-
!!obj.additionalProperties ||
82-
!!obj.allOf ||
83-
!!obj.anyOf ||
84-
!!obj.oneOf ||
85-
!!obj.properties ||
86-
!!obj.type
87-
);
88-
}
8963

90-
/**
91-
* Rename object with ? keys if optional
92-
*/
93-
export function makeOptional(obj: any, required?: string[]): any {
94-
if (typeof obj !== "object" || !Object.keys(obj).length) {
95-
return obj;
64+
// oneOf
65+
if (Array.isArray(obj.oneOf)) {
66+
return "oneOf";
9667
}
97-
return fromEntries(
98-
Object.entries(obj).map(([key, val]) => {
99-
const sanitized = unescape(key).replace(/\?$/, "");
10068

101-
if (Array.isArray(required) && required.includes(key)) {
102-
return [sanitized, val]; // required: no `?`
103-
}
69+
// object
70+
if (
71+
obj.type === "object" ||
72+
!!obj.properties ||
73+
!!obj.allOf ||
74+
!!obj.additionalProperties
75+
) {
76+
return "object";
77+
}
10478

105-
return [escape(`${sanitized}?`), val]; // optional: `?`
106-
})
107-
);
79+
// array
80+
if (obj.type === "array") {
81+
return "array";
82+
}
83+
84+
// other / unknown
85+
return obj.type;
10886
}
10987

110-
/**
111-
* Return OpenAPI version from definition
112-
*/
88+
/** Return OpenAPI version from definition */
11389
export function swaggerVersion(definition: OpenAPI2 | OpenAPI3): 2 | 3 {
11490
// OpenAPI 3
11591
const { openapi } = definition as OpenAPI3;
@@ -128,23 +104,28 @@ export function swaggerVersion(definition: OpenAPI2 | OpenAPI3): 2 | 3 {
128104
);
129105
}
130106

131-
/**
132-
* Convert T into T[];
133-
*/
107+
/** Convert $ref to TS ref */
108+
export function transformRef(ref: string): string {
109+
const parts = ref.replace(/^#\//, "").split("/");
110+
return `${parts[0]}["${parts.slice(1).join('"]["')}"]`;
111+
}
112+
113+
/** Convert T into T[]; */
134114
export function tsArrayOf(type: string): string {
135-
return type.concat("[]");
115+
return `(${type})[]`;
136116
}
137117

138-
/**
139-
* Convert T, U into T & U;
140-
*/
118+
/** Convert T, U into T & U; */
141119
export function tsIntersectionOf(types: string[]): string {
142-
return types.join(" & ");
120+
return `(${types.join(") & (")})`;
121+
}
122+
123+
/** Convert T into Partial<T> */
124+
export function tsPartial(type: string): string {
125+
return `Partial<${type}>`;
143126
}
144127

145-
/**
146-
* Convert [X, Y, Z] into X | Y | Z
147-
*/
128+
/** Convert [X, Y, Z] into X | Y | Z */
148129
export function tsUnionOf(types: string[]): string {
149-
return types.join(" | ");
130+
return `(${types.join(") | (")})`;
150131
}

0 commit comments

Comments
 (0)