Skip to content

Commit 3925246

Browse files
authored
Refactor, add parameters for V2 (#448)
* Combine schemas * Add more items to components object * Fix comment linebreak bug * Make content more consistent
1 parent 97c6006 commit 3925246

38 files changed

+129109
-78994
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
"@typescript-eslint/explicit-module-boundary-types": "off",
88
"@typescript-eslint/no-explicit-any": "off", // in a foreign schema, sometimes we need “any”
99
"@typescript-eslint/no-use-before-define": "off",
10+
"@typescript-eslint/no-var-requires": "off",
1011
"prettier/prettier": "error",
1112
"prefer-const": "off",
1213
},

README.md

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,31 @@ npx openapi-typescript https://petstore.swagger.io/v2/swagger.json --output pets
4141
# 🚀 https://petstore.swagger.io/v2/swagger.json -> petstore.ts [650ms]
4242
```
4343

44-
_Thanks to @psmyrdek for this feature!_
44+
_Thanks to @psmyrdek for the remote spec feature!_
45+
46+
#### Using in TypeScript
47+
48+
Import any top-level item from the generated spec to use it. It works best if you also alias types to save on typing:
49+
50+
```ts
51+
import { components } from './generated-schema.ts';
52+
53+
type APIResponse = components["schemas"]["APIResponse"];
54+
```
55+
56+
The reason for all the `["…"]` everywhere is because OpenAPI lets you use more characters than are valid TypeScript identifiers. The goal of this project is to generate _all_ of your schema, not merely the parts that are “TypeScript-safe.”
57+
58+
Also note that there’s a special `operations` interface that you can import `OperationObjects` by their [operationId][openapi-operationid]:
59+
60+
```ts
61+
import { operations } from './generated-schema.ts';
62+
63+
type getUsersById = operations["getUsersById"];
64+
```
65+
66+
This is the only place where our generation differs from your schema as-written, but it’s done so as a convenience and shouldn’t cause any issues (you can still use deep references as-needed).
67+
68+
_Thanks to @gr2m for the operations feature!_
4569

4670
#### Outputting to `stdout`
4771

@@ -86,6 +110,7 @@ For anything more complicated, or for generating specs dynamically, you can also
86110
| :----------------------------- | :---- | :------: | :--------------------------------------------------------------- |
87111
| `--output [location]` | `-o` | (stdout) | Where should the output file be saved? |
88112
| `--prettier-config [location]` | | | (optional) Path to your custom Prettier configuration for output |
113+
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
89114

90115
### Node
91116

@@ -108,35 +133,6 @@ post-process, and save the output anywhere.
108133
If your specs are in YAML, you’ll have to convert them to JS objects using a library such as [js-yaml][js-yaml]. If
109134
you’re batching large folders of specs, [glob][glob] may also come in handy.
110135

111-
#### PropertyMapper
112-
113-
In order to allow more control over how properties are parsed, and to specifically handle `x-something`-properties, the
114-
`propertyMapper` option may be specified as the optional 2nd parameter.
115-
116-
This is a function that, if specified, is called for each property and allows you to change how openapi-typescript
117-
handles parsing of Swagger files.
118-
119-
An example on how to use the `x-nullable` property to control if a property is optional:
120-
121-
```js
122-
const getNullable = (d: { [key: string]: any }): boolean => {
123-
const nullable = d["x-nullable"];
124-
if (typeof nullable === "boolean") {
125-
return nullable;
126-
}
127-
return true;
128-
};
129-
130-
const output = swaggerToTS(swagger, {
131-
propertyMapper: (swaggerDefinition, property): Property => ({
132-
...property,
133-
optional: getNullable(swaggerDefinition),
134-
}),
135-
});
136-
```
137-
138-
_Thanks to @atlefren for this feature!_
139-
140136
## Migrating from v1 to v2
141137

142138
[Migrating from v1 to v2](./docs/migrating-from-v1.md)
@@ -156,9 +152,10 @@ encouraged but not required.
156152

157153
[glob]: https://www.npmjs.com/package/glob
158154
[js-yaml]: https://www.npmjs.com/package/js-yaml
159-
[openapi2]: https://swagger.io/specification/v2/
160155
[namespace]: https://www.typescriptlang.org/docs/handbook/namespaces.html
161156
[npm-run-all]: https://www.npmjs.com/package/npm-run-all
157+
[openapi-operationid]: https://swagger.io/specification/#operation-object
158+
[openapi2]: https://swagger.io/specification/v2/
162159
[openapi3]: https://swagger.io/specification
163160
[prettier]: https://npmjs.com/prettier
164161

examples/stripe-openapi2.ts

Lines changed: 25116 additions & 5083 deletions
Large diffs are not rendered by default.

examples/stripe-openapi3.ts

Lines changed: 25232 additions & 24528 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import path from "path";
22
import prettier from "prettier";
33
import { swaggerVersion } from "./utils";
4-
import { OpenAPI2, OpenAPI2Schemas, OpenAPI3, OpenAPI3Schemas, SwaggerToTSOptions } from "./types";
5-
import v2 from "./v2";
6-
import v3 from "./v3";
4+
import { transformAll } from "./transform/index";
5+
import { OpenAPI2, OpenAPI3, SchemaObject, SwaggerToTSOptions } from "./types/index";
6+
export * from "./types/index";
77

88
export const WARNING_MESSAGE = `/**
99
* This file was auto-generated by openapi-typescript.
@@ -14,24 +14,18 @@ export const WARNING_MESSAGE = `/**
1414
`;
1515

1616
export default function swaggerToTS(
17-
schema: OpenAPI2 | OpenAPI2Schemas | OpenAPI3 | OpenAPI3Schemas,
17+
schema: OpenAPI2 | OpenAPI3 | Record<string, SchemaObject>,
1818
options?: SwaggerToTSOptions
1919
): string {
20-
// generate types for V2 and V3
20+
// 1. determine version
2121
const version = (options && options.version) || swaggerVersion(schema as OpenAPI2 | OpenAPI3);
22-
let output = `${WARNING_MESSAGE}`;
23-
switch (version) {
24-
case 2: {
25-
output = output.concat(v2(schema as OpenAPI2 | OpenAPI2Schemas, options));
26-
break;
27-
}
28-
case 3: {
29-
output = output.concat(v3(schema as OpenAPI3 | OpenAPI3Schemas, options));
30-
break;
31-
}
32-
}
3322

34-
// Prettify output
23+
// 2. generate output
24+
let output = `${WARNING_MESSAGE}
25+
${transformAll(schema, { version, rawSchema: options && options.rawSchema })}
26+
`;
27+
28+
// 3. Prettify output
3529
let prettierOptions: prettier.Options = { parser: "typescript" };
3630
if (options && options.prettierConfig) {
3731
try {

src/property-mapper.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/transform/headers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { comment } from "../utils";
2+
import { HeaderObject } from "../types";
3+
import { transformSchemaObj } from "./schema";
4+
5+
export function transformHeaderObjMap(headerMap: Record<string, HeaderObject>): string {
6+
let output = "";
7+
8+
Object.entries(headerMap).forEach(([k, v]) => {
9+
if (!v.schema) return;
10+
11+
if (v.description) output += comment(v.description);
12+
const required = v.required ? "" : "?";
13+
output += ` "${k}"${required}: ${transformSchemaObj(v.schema)}\n`;
14+
});
15+
16+
return output;
17+
}

src/transform/index.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { OperationObject } from "../types";
2+
import { comment } from "../utils";
3+
import { transformHeaderObjMap } from "./headers";
4+
import { transformOperationObj } from "./operation";
5+
import { transformPathsObj } from "./paths";
6+
import { transformResponsesObj } from "./responses";
7+
import { transformSchemaObjMap } from "./schema";
8+
9+
interface TransformOptions {
10+
rawSchema?: boolean;
11+
version: number;
12+
}
13+
14+
export function transformAll(schema: any, { version, rawSchema }: TransformOptions): string {
15+
let output = "";
16+
17+
let operations: Record<string, OperationObject> = {};
18+
19+
// --raw-schema mode
20+
if (rawSchema) {
21+
switch (version) {
22+
case 2: {
23+
return `export interface definitions {\n ${transformSchemaObjMap(schema, {
24+
required: Object.keys(schema),
25+
})}\n}`;
26+
}
27+
case 3: {
28+
return `export interface schemas {\n ${transformSchemaObjMap(schema, {
29+
required: Object.keys(schema),
30+
})}\n }\n\n`;
31+
}
32+
}
33+
}
34+
35+
// #/paths (V2 & V3)
36+
output += `export interface paths {\n`; // open paths
37+
if (schema.paths) {
38+
output += transformPathsObj(schema.paths, {
39+
operations,
40+
parameters: (schema.components && schema.components.parameters) || schema.parameters,
41+
});
42+
}
43+
output += `}\n\n`; // close paths
44+
45+
switch (version) {
46+
case 2: {
47+
// #/definitions
48+
output += `export interface definitions {\n ${transformSchemaObjMap(schema.definitions || {}, {
49+
required: Object.keys(schema.definitions),
50+
})}\n}\n\n`;
51+
52+
// #/parameters
53+
if (schema.parameters) {
54+
const required = Object.keys(schema.parameters);
55+
output += `export interface parameters {\n ${transformSchemaObjMap(schema.parameters, {
56+
required,
57+
})}\n }\n\n`;
58+
}
59+
60+
// #/parameters
61+
if (schema.responses) {
62+
output += `export interface responses {\n ${transformResponsesObj(schema.responses)}\n }\n\n`;
63+
}
64+
break;
65+
}
66+
case 3: {
67+
// #/components
68+
output += `export interface components {\n`; // open components
69+
70+
if (schema.components) {
71+
// #/components/schemas
72+
if (schema.components.schemas) {
73+
const required = Object.keys(schema.components.schemas);
74+
output += ` schemas: {\n ${transformSchemaObjMap(schema.components.schemas, { required })}\n }\n`;
75+
}
76+
77+
// #/components/responses
78+
if (schema.components.responses) {
79+
output += ` responses: {\n ${transformResponsesObj(schema.components.responses)}\n }\n`;
80+
}
81+
82+
// #/components/parameters
83+
if (schema.components.parameters) {
84+
const required = Object.keys(schema.components.parameters);
85+
output += ` parameters: {\n ${transformSchemaObjMap(schema.components.parameters, {
86+
required,
87+
})}\n }\n`;
88+
}
89+
90+
// #/components/requestBodies
91+
if (schema.components.requestBodies) {
92+
const required = Object.keys(schema.components.requestBodies);
93+
output += ` requestBodies: {\n ${transformSchemaObjMap(schema.components.requestBodies, {
94+
required,
95+
})}\n }\n`;
96+
}
97+
98+
// #/components/headers
99+
if (schema.components.headers) {
100+
output += ` headers: {\n ${transformHeaderObjMap(schema.components.headers)} }\n`;
101+
}
102+
}
103+
104+
output += `}\n\n`; // close components
105+
break;
106+
}
107+
}
108+
109+
output += `export interface operations {\n`; // open operations
110+
if (Object.keys(operations).length) {
111+
Object.entries(operations).forEach(([operationId, operation]) => {
112+
if (operation.description) output += comment(operation.description); // handle comment
113+
output += ` "${operationId}": {\n ${transformOperationObj(
114+
operation,
115+
schema.components && schema.components.parameters
116+
)};\n }\n`;
117+
});
118+
}
119+
output += `}\n`; // close operations
120+
121+
return output.trim();
122+
}

0 commit comments

Comments
 (0)