Skip to content

Commit ebc2cf8

Browse files
committed
Add formatter
1 parent 2e1bd64 commit ebc2cf8

11 files changed

+195
-99
lines changed

README.md

+34-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ View examples:
2121

2222
## Usage
2323

24-
### CLI
24+
### 🖥️ CLI
2525

2626
#### 🗄️ Reading specs from file system
2727

@@ -114,18 +114,18 @@ For anything more complicated, or for generating specs dynamically, you can also
114114
| `--prettier-config [location]` | | | (optional) Path to your custom Prettier configuration for output |
115115
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
116116

117-
### Node
117+
### 🐢 Node
118118

119119
```bash
120120
npm i --save-dev openapi-typescript
121121
```
122122

123123
```js
124124
const { readFileSync } = require("fs");
125-
const swaggerToTS = require("openapi-typescript").default;
125+
const openapiTS = require("openapi-typescript").default;
126126

127127
const input = JSON.parse(readFileSync("spec.json", "utf8")); // Input can be any JS object (OpenAPI format)
128-
const output = swaggerToTS(input); // Outputs TypeScript defs as a string (to be parsed, or written to a file)
128+
const output = openapiTS(input); // Outputs TypeScript defs as a string (to be parsed, or written to a file)
129129
```
130130

131131
The Node API is a bit more flexible: it will only take a JS object as input (OpenAPI format), and return a string of TS
@@ -135,19 +135,44 @@ post-process, and save the output anywhere.
135135
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
136136
you’re batching large folders of specs, [glob][glob] may also come in handy.
137137

138-
## Migrating from v1 to v2
138+
#### Custom Formatter
139139

140-
[Migrating from v1 to v2](./docs/migrating-from-v1.md)
140+
If using the Node.js API, you can optionally pass a **formatter** to openapi-typescript. This is useful if you want to override the default types and substitute your own.
141141

142-
## Project Goals
142+
For example, say your schema has the following property:
143+
144+
```yaml
145+
properties:
146+
updated_at:
147+
type: string
148+
format: date-time
149+
```
150+
151+
By default, this will generate a type `updated_at?: string;`. But we can override this by passing a formatter to the Node API, like so:
152+
153+
```js
154+
const types = openapiTS(mySchema, {
155+
formatter(node: SchemaObject) {
156+
if (node.format === 'date-time') {
157+
return "Date"; // return the TypeScript “Date” type, as a string
158+
}
159+
// for all other schema objects, let openapi-typescript decide (return undefined)
160+
});
161+
```
162+
163+
This will generate `updated_at?: Date` instead. Note that you will still have to do the parsing of your data yourself. But this will save you from having to also update all your types.
164+
165+
_Note: you don’t have to use `.format`—this is just an example! You can use any property on a schema object to overwrite its generated type if desired._
166+
167+
## 🏅 Project Goals
143168

144169
1. Support converting any OpenAPI 3.0 or 2.0 (Swagger) schema to TypeScript types, no matter how complicated
145170
1. The generated TypeScript types **must** match your schema as closely as possible (i.e. don’t convert names to
146171
`PascalCase` or follow any TypeScript-isms; faithfully reproduce your schema as closely as possible, capitalization
147172
and all)
148173
1. This library is a TypeScript generator, not a schema validator.
149174

150-
## Contributing
175+
## 🤝 Contributing
151176

152177
PRs are welcome! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide. Opening an issue beforehand to discuss is
153178
encouraged but not required.
@@ -156,6 +181,7 @@ encouraged but not required.
156181
[js-yaml]: https://www.npmjs.com/package/js-yaml
157182
[namespace]: https://www.typescriptlang.org/docs/handbook/namespaces.html
158183
[npm-run-all]: https://www.npmjs.com/package/npm-run-all
184+
[openapi-format]: https://swagger.io/specification/#data-types
159185
[openapi-operationid]: https://swagger.io/specification/#operation-object
160186
[openapi2]: https://swagger.io/specification/v2/
161187
[openapi3]: https://swagger.io/specification

bin/cli.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const fs = require("fs");
44
const { bold, green, red } = require("kleur");
55
const path = require("path");
66
const meow = require("meow");
7-
const { default: swaggerToTS } = require("../dist/cjs/index.js");
7+
const { default: openapiTS } = require("../dist/cjs/index.js");
88
const { loadSpec } = require("./loaders");
99

1010
const cli = meow(
@@ -72,7 +72,7 @@ async function main() {
7272
}
7373

7474
// 2. generate schema (the main part!)
75-
const result = swaggerToTS(spec, {
75+
const result = openapiTS(spec, {
7676
immutableTypes: cli.flags.immutableTypes,
7777
prettierConfig: cli.flags.prettierConfig,
7878
rawSchema: cli.flags.rawSchema,

src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const WARNING_MESSAGE = `/**
1414
1515
`;
1616

17-
export default function swaggerToTS(
17+
export default function openapiTS(
1818
schema: OpenAPI2 | OpenAPI3 | Record<string, SchemaObject>,
1919
options?: SwaggerToTSOptions
2020
): string {
@@ -24,6 +24,7 @@ export default function swaggerToTS(
2424
// 2. generate output
2525
let output = `${WARNING_MESSAGE}
2626
${transformAll(schema, {
27+
formatter: options && typeof options.formatter === "function" ? options.formatter : undefined,
2728
immutableTypes: (options && options.immutableTypes) || false,
2829
rawSchema: options && options.rawSchema,
2930
version,

src/transform/headers.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { HeaderObject } from "../types";
1+
import { HeaderObject, SchemaFormatter } from "../types";
22
import { comment, tsReadonly } from "../utils";
33
import { transformSchemaObj } from "./schema";
44

55
export function transformHeaderObjMap(
66
headerMap: Record<string, HeaderObject>,
7-
{ immutableTypes }: { immutableTypes: boolean }
7+
options: { formatter?: SchemaFormatter; immutableTypes: boolean }
88
): string {
99
let output = "";
1010

@@ -13,12 +13,10 @@ export function transformHeaderObjMap(
1313

1414
if (v.description) output += comment(v.description);
1515

16-
const readonly = tsReadonly(immutableTypes);
16+
const readonly = tsReadonly(options.immutableTypes);
1717
const required = v.required ? "" : "?";
1818

19-
output += ` ${readonly}"${k}"${required}: ${transformSchemaObj(v.schema, {
20-
immutableTypes,
21-
})}\n`;
19+
output += ` ${readonly}"${k}"${required}: ${transformSchemaObj(v.schema, options)}\n`;
2220
});
2321

2422
return output;

src/transform/index.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { OperationObject, PathItemObject } from "../types";
1+
import { OperationObject, PathItemObject, SchemaFormatter } from "../types";
22
import { comment, tsReadonly } from "../utils";
33
import { transformHeaderObjMap } from "./headers";
44
import { transformOperationObj } from "./operation";
@@ -7,12 +7,13 @@ import { transformResponsesObj, transformRequestBodies } from "./responses";
77
import { transformSchemaObjMap } from "./schema";
88

99
interface TransformOptions {
10+
formatter?: SchemaFormatter;
1011
immutableTypes: boolean;
1112
rawSchema?: boolean;
1213
version: number;
1314
}
1415

15-
export function transformAll(schema: any, { immutableTypes, rawSchema, version }: TransformOptions): string {
16+
export function transformAll(schema: any, { formatter, immutableTypes, rawSchema, version }: TransformOptions): string {
1617
const readonly = tsReadonly(immutableTypes);
1718

1819
let output = "";
@@ -24,12 +25,14 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
2425
switch (version) {
2526
case 2: {
2627
return `export interface definitions {\n ${transformSchemaObjMap(schema, {
28+
formatter,
2729
immutableTypes,
2830
required: Object.keys(schema),
2931
})}\n}`;
3032
}
3133
case 3: {
3234
return `export interface schemas {\n ${transformSchemaObjMap(schema, {
35+
formatter,
3336
immutableTypes,
3437
required: Object.keys(schema),
3538
})}\n }\n\n`;
@@ -54,6 +57,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
5457
// #/definitions
5558
if (schema.definitions) {
5659
output += `export interface definitions {\n ${transformSchemaObjMap(schema.definitions, {
60+
formatter,
5761
immutableTypes,
5862
required: Object.keys(schema.definitions),
5963
})}\n}\n\n`;
@@ -63,6 +67,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
6367
if (schema.parameters) {
6468
const required = Object.keys(schema.parameters);
6569
output += `export interface parameters {\n ${transformSchemaObjMap(schema.parameters, {
70+
formatter,
6671
immutableTypes,
6772
required,
6873
})}\n }\n\n`;
@@ -71,6 +76,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
7176
// #/parameters
7277
if (schema.responses) {
7378
output += `export interface responses {\n ${transformResponsesObj(schema.responses, {
79+
formatter,
7480
immutableTypes,
7581
})}\n }\n\n`;
7682
}
@@ -85,6 +91,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
8591
if (schema.components.schemas) {
8692
const required = Object.keys(schema.components.schemas);
8793
output += ` ${readonly}schemas: {\n ${transformSchemaObjMap(schema.components.schemas, {
94+
formatter,
8895
immutableTypes,
8996
required,
9097
})}\n }\n`;
@@ -93,6 +100,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
93100
// #/components/responses
94101
if (schema.components.responses) {
95102
output += ` ${readonly}responses: {\n ${transformResponsesObj(schema.components.responses, {
103+
formatter,
96104
immutableTypes,
97105
})}\n }\n`;
98106
}
@@ -101,6 +109,7 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
101109
if (schema.components.parameters) {
102110
const required = Object.keys(schema.components.parameters);
103111
output += ` ${readonly}parameters: {\n ${transformSchemaObjMap(schema.components.parameters, {
112+
formatter,
104113
immutableTypes,
105114
required,
106115
})}\n }\n`;
@@ -109,13 +118,15 @@ export function transformAll(schema: any, { immutableTypes, rawSchema, version }
109118
// #/components/requestBodies
110119
if (schema.components.requestBodies) {
111120
output += ` ${readonly}requestBodies: {\n ${transformRequestBodies(schema.components.requestBodies, {
121+
formatter,
112122
immutableTypes,
113123
})}\n }\n`;
114124
}
115125

116126
// #/components/headers
117127
if (schema.components.headers) {
118128
output += ` ${readonly}headers: {\n ${transformHeaderObjMap(schema.components.headers, {
129+
formatter,
119130
immutableTypes,
120131
})} }\n`;
121132
}

src/transform/responses.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RequestBody } from "../types";
1+
import { RequestBody, SchemaFormatter } from "../types";
22
import { comment, transformRef, tsReadonly } from "../utils";
33
import { transformHeaderObjMap } from "./headers";
44
import { transformSchemaObj } from "./schema";
@@ -7,6 +7,7 @@ import { transformRequestBodyObj } from "./operation";
77
const resType = (res: string | number) => (res === 204 || (res >= 300 && res < 400) ? "never" : "unknown");
88

99
interface Options {
10+
formatter?: SchemaFormatter;
1011
immutableTypes: boolean;
1112
}
1213

0 commit comments

Comments
 (0)