Skip to content

Commit 99bcf0e

Browse files
authored
Support raw schemas in addition to Swagger/OpenAPI documents (#263)
* Support raw schemas in addition to Swagger/OpenAPI documents This adds support for supplying raw schemas instead of Swagger/OpenAPI documents, using an option --raw-schema in combination with --version. This enables producing types for stand-alone schemas, which is useful for large APIs where schemas have been extracted to separate files, and where there may be no components object in the document. For V3, the output differs from the output produced for OpenAPI3 documents in that `schemas: { ... }` is the exported interface instead of `components: { schemas: { ... } }`. For V2, `definitions: { ... }` is still exported. * Remove optional chaining use due to Jest not supporting it
1 parent cdf3546 commit 99bcf0e

File tree

8 files changed

+103
-35
lines changed

8 files changed

+103
-35
lines changed

bin/cli.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Options
1515
--help display this
1616
--output, -o specify output file
1717
--prettier-config (optional) specify path to Prettier config file
18+
--raw-schema (optional) Read from raw schema instead of document
19+
--version (optional) Schema version (must be present for raw schemas)
1820
`,
1921
{
2022
flags: {
@@ -25,6 +27,12 @@ Options
2527
prettierConfig: {
2628
type: "string",
2729
},
30+
rawSchema: {
31+
type: "boolean"
32+
},
33+
version: {
34+
type: "number"
35+
}
2836
},
2937
}
3038
);
@@ -48,6 +56,8 @@ const timeStart = process.hrtime();
4856

4957
const result = swaggerToTS(spec, {
5058
prettierConfig: cli.flags.prettierConfig,
59+
rawSchema: cli.flags.rawSchema,
60+
version: cli.flags.version
5161
});
5262

5363
// Write to file if specifying output

src/index.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import path from "path";
22
import prettier from "prettier";
33
import { swaggerVersion } from "./utils";
4-
import { OpenAPI2, OpenAPI3, SwaggerToTSOptions } from "./types";
4+
import {
5+
OpenAPI2,
6+
OpenAPI2Schemas,
7+
OpenAPI3,
8+
OpenAPI3Schemas,
9+
SwaggerToTSOptions,
10+
} from "./types";
511
import v2 from "./v2";
612
import v3 from "./v3";
713

@@ -14,19 +20,21 @@ export const WARNING_MESSAGE = `/**
1420
`;
1521

1622
export default function swaggerToTS(
17-
schema: OpenAPI2 | OpenAPI3,
23+
schema: OpenAPI2 | OpenAPI2Schemas | OpenAPI3 | OpenAPI3Schemas,
1824
options?: SwaggerToTSOptions
1925
): string {
2026
// generate types for V2 and V3
21-
const version = swaggerVersion(schema);
27+
const version =
28+
(options && options.version) ||
29+
swaggerVersion(schema as OpenAPI2 | OpenAPI3);
2230
let output = `${WARNING_MESSAGE}`;
2331
switch (version) {
2432
case 2: {
25-
output = output.concat(v2(schema as OpenAPI2, options));
33+
output = output.concat(v2(schema as OpenAPI2 | OpenAPI2Schemas, options));
2634
break;
2735
}
2836
case 3: {
29-
output = output.concat(v3(schema as OpenAPI3, options));
37+
output = output.concat(v3(schema as OpenAPI3 | OpenAPI3Schemas, options));
3038
break;
3139
}
3240
}

src/types/OpenAPI2.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
* the parts that swagger-to-ts needs to know about.
55
*/
66

7+
export interface OpenAPI2Schemas {
8+
[key: string]: OpenAPI2SchemaObject;
9+
}
10+
711
export interface OpenAPI2 {
8-
definitions?: { [key: string]: OpenAPI2SchemaObject };
12+
definitions?: OpenAPI2Schemas;
913
swagger: string;
1014
[key: string]: any; // handle other properties beyond swagger-to-ts’ concern
1115
}

src/types/OpenAPI3.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
* the parts that swagger-to-ts needs to know about.
55
*/
66

7+
export interface OpenAPI3Schemas {
8+
[key: string]: OpenAPI3SchemaObject | OpenAPI3Reference;
9+
}
10+
11+
export interface OpenAPI3Components {
12+
schemas: OpenAPI3Schemas;
13+
responses?: OpenAPI3Schemas;
14+
}
15+
716
export interface OpenAPI3 {
817
openapi: string;
9-
components: {
10-
schemas: { [key: string]: OpenAPI3SchemaObject | OpenAPI3Reference };
11-
responses?: { [key: string]: OpenAPI3SchemaObject | OpenAPI3Reference };
12-
};
18+
components: OpenAPI3Components;
1319
[key: string]: any; // handle other properties beyond swagger-to-ts’ concern
1420
}
1521

src/types/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ export interface SwaggerToTSOptions {
1818
schemaObject: OpenAPI2SchemaObject | OpenAPI3SchemaObject,
1919
property: Property
2020
) => Property;
21+
/** (optional) Parsing input document as raw schema rather than OpenAPI document */
22+
rawSchema?: boolean;
23+
/** (optional) OpenAPI version. Must be present if parsing raw schema */
24+
version?: number;
2125
}

src/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ export function swaggerVersion(definition: OpenAPI2 | OpenAPI3): 2 | 3 {
9595
}
9696

9797
/** Convert $ref to TS ref */
98-
export function transformRef(ref: string): string {
99-
const parts = ref.replace(/^#\//, "").split("/");
98+
export function transformRef(ref: string, root = ""): string {
99+
const parts = ref.replace(/^#\//, root).split("/");
100100
return `${parts[0]}["${parts.slice(1).join('"]["')}"]`;
101101
}
102102

src/v2.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import propertyMapper from "./property-mapper";
2-
import { OpenAPI2, OpenAPI2SchemaObject, SwaggerToTSOptions } from "./types";
2+
import {
3+
OpenAPI2,
4+
OpenAPI2SchemaObject,
5+
OpenAPI2Schemas,
6+
SwaggerToTSOptions,
7+
} from "./types";
38
import {
49
comment,
510
nodeType,
@@ -29,25 +34,36 @@ export const PRIMITIVES: { [key: string]: "boolean" | "string" | "number" } = {
2934
};
3035

3136
export default function generateTypesV2(
32-
schema: OpenAPI2,
37+
input: OpenAPI2 | OpenAPI2Schemas,
3338
options?: SwaggerToTSOptions
3439
): string {
35-
if (!schema.definitions) {
36-
throw new Error(
37-
`⛔️ 'definitions' missing from schema https://swagger.io/specification/v2/#definitions-object`
38-
);
40+
const rawSchema = options && options.rawSchema;
41+
42+
let definitions: OpenAPI2Schemas;
43+
44+
if (rawSchema) {
45+
definitions = input as OpenAPI2Schemas;
46+
} else {
47+
const document = input as OpenAPI2;
48+
49+
if (!document.definitions) {
50+
throw new Error(
51+
`⛔️ 'definitions' missing from schema https://swagger.io/specification/v2/#definitions-object`
52+
);
53+
}
54+
definitions = document.definitions;
3955
}
4056

4157
// propertyMapper
4258
const propertyMapped = options
43-
? propertyMapper(schema.definitions, options.propertyMapper)
44-
: schema.definitions;
59+
? propertyMapper(definitions, options.propertyMapper)
60+
: definitions;
4561

4662
// type conversions
4763
function transform(node: OpenAPI2SchemaObject): string {
4864
switch (nodeType(node)) {
4965
case "ref": {
50-
return transformRef(node.$ref);
66+
return transformRef(node.$ref, rawSchema ? "definitions/" : "");
5167
}
5268
case "string":
5369
case "number":

src/v3.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import propertyMapper from "./property-mapper";
2-
import { OpenAPI3, OpenAPI3SchemaObject, SwaggerToTSOptions } from "./types";
2+
import {
3+
OpenAPI3,
4+
OpenAPI3Components,
5+
OpenAPI3SchemaObject,
6+
OpenAPI3Schemas,
7+
SwaggerToTSOptions,
8+
} from "./types";
39
import {
410
comment,
511
nodeType,
@@ -24,25 +30,34 @@ export const PRIMITIVES: { [key: string]: "boolean" | "string" | "number" } = {
2430
};
2531

2632
export default function generateTypesV3(
27-
schema: OpenAPI3,
33+
input: OpenAPI3 | OpenAPI3Schemas,
2834
options?: SwaggerToTSOptions
2935
): string {
30-
if (!schema.components || !schema.components.schemas) {
31-
throw new Error(
32-
`⛔️ 'components' missing from schema https://swagger.io/specification`
33-
);
36+
const { rawSchema = false } = options || {};
37+
let components: OpenAPI3Components;
38+
39+
if (rawSchema) {
40+
components = { schemas: input };
41+
} else {
42+
components = (input as OpenAPI3).components;
43+
44+
if (!components || !components.schemas) {
45+
throw new Error(
46+
`⛔️ 'components' missing from schema https://swagger.io/specification`
47+
);
48+
}
3449
}
3550

3651
// propertyMapper
3752
const propertyMapped = options
38-
? propertyMapper(schema.components.schemas, options.propertyMapper)
39-
: schema.components.schemas;
53+
? propertyMapper(components.schemas, options.propertyMapper)
54+
: components.schemas;
4055

4156
// type converter
4257
function transform(node: OpenAPI3SchemaObject): string {
4358
switch (nodeType(node)) {
4459
case "ref": {
45-
return transformRef(node.$ref);
60+
return transformRef(node.$ref, rawSchema ? "schemas/" : "");
4661
}
4762
case "string":
4863
case "number":
@@ -139,17 +154,22 @@ export default function generateTypesV3(
139154
return output;
140155
}
141156

157+
if (rawSchema) {
158+
const schemas = createKeys(propertyMapped, Object.keys(propertyMapped));
159+
160+
return `export interface schemas {
161+
${schemas}
162+
}`;
163+
}
164+
142165
const schemas = `schemas: {
143166
${createKeys(propertyMapped, Object.keys(propertyMapped))}
144167
}`;
145168

146-
const responses = !schema.components.responses
169+
const responses = !components.responses
147170
? ``
148171
: `responses: {
149-
${createKeys(
150-
schema.components.responses,
151-
Object.keys(schema.components.responses)
152-
)}
172+
${createKeys(components.responses, Object.keys(components.responses))}
153173
}`;
154174

155175
// note: make sure that base-level schemas are required

0 commit comments

Comments
 (0)