Skip to content

Commit 0f10589

Browse files
committed
- Working on babel support (union types)
1 parent 12c46cf commit 0f10589

File tree

7 files changed

+159
-108
lines changed

7 files changed

+159
-108
lines changed

README.md

Lines changed: 113 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,86 @@
66
[![Codecov](https://codecov.io/gh/ferdikoomen/openapi-typescript-codegen/branch/master/graph/badge.svg)](https://codecov.io/gh/ferdikoomen/openapi-typescript-codegen)
77
[![Quality](https://badgen.net/lgtm/grade/javascript/g/ferdikoomen/openapi-typescript-codegen)](https://lgtm.com/projects/g/ferdikoomen/openapi-typescript-codegen)
88

9-
> NodeJS library that generates Typescript clients based on the OpenAPI specification.
9+
> Node.js library that generates Typescript clients based on the OpenAPI specification.
1010
11-
#### Why?
12-
- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds.
13-
- Quick, lightweight, robust and framework agnostic.
14-
- Supports generation of Typescript clients.
15-
- Supports generations of fetch and XHR http clients.
16-
- Supports OpenAPI specification v2.0 and v3.0.
17-
- Supports JSON and YAML files for input.
11+
## Why?
12+
- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds
13+
- Quick, lightweight, robust and framework agnostic
14+
- Supports generation of TypeScript clients
15+
- Supports generations of fetch and XHR http clients
16+
- Supports OpenAPI specification v2.0 and v3.0
17+
- Supports JSON and YAML files for input
18+
- Supports generation through CLI, Node.js and NPX
19+
- Supports tsc and @babel/plugin-transform-typescript
1820

1921

20-
## Known issues:
21-
- If you use enums inside your models / definitions then those enums are now
22+
## Babel support:
23+
- If you use enums inside your models / definitions then those enums are by default
2224
inside a namespace with the same name as your model. This is called declaration
23-
merging. However, Babel 7 now support compiling of Typescript and right now they
24-
do not support namespaces.
25+
merging. However, the @babel/plugin-transform-typescript does not support these
26+
namespaces, so if you are using babel in your project please use the `--useUnionTypes`
27+
flag to generate union types instead of traditional enums. More info can be found
28+
here: [Enums vs. Union Types](#enums-vs-union-types).
2529

2630

27-
## Installation
31+
## Install
2832

2933
```
3034
npm install openapi-typescript-codegen --save-dev
3135
```
3236

37+
38+
## Usage
39+
40+
```
41+
$ openapi --help
42+
43+
Usage: openapi [options]
44+
45+
Options:
46+
-V, --version output the version number
47+
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
48+
-o, --output <value> Output directory (required)
49+
-c, --client <value> HTTP client to generate [fetch, xhr] (default: "fetch")
50+
--useOptions Use options instead of arguments
51+
--useUnionTypes Use union types instead of enums
52+
--exportCore <value> Write core files to disk (default: true)
53+
--exportServices <value> Write services to disk (default: true)
54+
--exportModels <value> Write models to disk (default: true)
55+
--exportSchemas <value> Write schemas to disk (default: false)
56+
57+
Examples
58+
$ openapi --input ./spec.json
59+
$ openapi --input ./spec.json --output ./dist
60+
$ openapi --input ./spec.json --output ./dist --client xhr
61+
```
62+
63+
3364
## Example
3465

3566
**package.json**
3667
```json
3768
{
3869
"scripts": {
39-
"generate": "openapi --input ./api/openapi.json --output ./dist"
70+
"generate": "openapi --input ./spec.json --output ./dist"
4071
}
4172
}
4273
```
4374

4475
**Command line**
4576

4677
```
47-
npm install openapi-typescript-codegen -g
48-
49-
openapi --input ./api/openapi.json --output ./dist
78+
npx openapi-typescript-codegen --input ./spec.json --output ./dist
5079
```
5180

52-
**NodeJS API**
81+
**Node.js API**
5382

5483
```javascript
5584
const OpenAPI = require('openapi-typescript-codegen');
5685

5786
OpenAPI.generate({
58-
input: './api/openapi.json',
59-
output: './generated'
87+
input: './spec.json',
88+
output: './dist'
6089
});
6190
```
6291

@@ -65,21 +94,22 @@ Or by providing the JSON directly:
6594
```javascript
6695
const OpenAPI = require('openapi-typescript-codegen');
6796

68-
const spec = require('./api/openapi.json');
97+
const spec = require('./spec.json');
6998

7099
OpenAPI.generate({
71100
input: spec,
72-
output: './generated'
101+
output: './dist'
73102
});
74103
```
75104

105+
76106
## Features
77107

78-
### Argument-style vs. Object-style
79-
There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in Javascript or Typescript, because of
108+
### Argument style vs. Object style `--useOptions`
109+
There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in JavaScript or TypeScript, because of
80110
that, we offer the flag `--useOptions` to generate code in two different styles.
81111

82-
Argument-style:
112+
**Argument-style:**
83113
```typescript
84114
function createUser(name: string, password: string, type?: string, address?: string) {
85115
// ...
@@ -89,7 +119,7 @@ function createUser(name: string, password: string, type?: string, address?: str
89119
createUser('Jack', '123456', undefined, 'NY US');
90120
```
91121

92-
Object-style:
122+
**Object-style:**
93123
```typescript
94124
function createUser({ name, password, type, address }: {
95125
name: string,
@@ -108,10 +138,60 @@ createUser({
108138
});
109139
```
110140

141+
### Enums vs. Union Types `--useUnionTypes`
142+
The OpenAPI spec allows you to define [enums](https://swagger.io/docs/specification/data-models/enums/) inside the
143+
data model. By default, we convert these enums definitions to [TypeScript enums](https://www.typescriptlang.org/docs/handbook/enums.html).
144+
However, these enums are merged inside the namespace of the model, this is unsupported by Babel, [see docs](https://babeljs.io/docs/en/babel-plugin-transform-typescript#impartial-namespace-support).
145+
146+
Because we also want to support projects that use Babel [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript), we offer the flag `--useOptions` to generate
147+
[union types](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types) instead of
148+
the traditional enums. The difference can be seen below:
149+
150+
**Enums:**
151+
```typescript
152+
// Model
153+
export interface Order {
154+
id?: number;
155+
quantity?: number;
156+
status?: Order.status;
157+
}
158+
159+
export namespace Order {
160+
export enum status {
161+
PLACED = 'placed',
162+
APPROVED = 'approved',
163+
DELIVERED = 'delivered',
164+
}
165+
}
166+
167+
// Usage
168+
const order: Order = {
169+
id: 1,
170+
quantity: 40,
171+
status: Order.status.PLACED
172+
}
173+
```
174+
175+
**Union Types:**
176+
```typescript
177+
// Model
178+
export interface Order {
179+
id?: number;
180+
quantity?: number;
181+
status?: 'placed' | 'approved' | 'delivered';
182+
}
183+
184+
// Usage
185+
const order: Order = {
186+
id: 1,
187+
quantity: 40,
188+
status: 'placed'
189+
}
190+
```
111191

112-
### Runtime schemas
113-
By default the OpenAPI generator only exports interfaces for your models. These interfaces will help you during
114-
development, but will not be available in javascript during runtime. However, Swagger allows you to define properties
192+
### Runtime schemas `--exportSchemas`
193+
By default, the OpenAPI generator only exports interfaces for your models. These interfaces will help you during
194+
development, but will not be available in JavaScript during runtime. However, Swagger allows you to define properties
115195
that can be useful during runtime, for instance: `maxLength` of a string or a `pattern` to match, etc. Let's say
116196
we have the following model:
117197

@@ -192,7 +272,7 @@ export const $MyModel = {
192272
```
193273

194274
These runtime object are prefixed with a `$` character and expose all the interesting attributes of a model
195-
and it's properties. We can now use this object to generate the form:
275+
and its properties. We can now use this object to generate the form:
196276

197277
```typescript jsx
198278
import { $MyModel } from './generated';
@@ -235,12 +315,12 @@ that can help developers use more meaningful enumerators.
235315
],
236316
"x-enum-varnames": [
237317
"Success",
238-
"Warning"
318+
"Warning",
239319
"Error"
240320
],
241321
"x-enum-descriptions": [
242322
"Used when the status of something is successful",
243-
"Used when the status of something has a warning"
323+
"Used when the status of something has a warning",
244324
"Used when the status of something has an error"
245325
]
246326
}
@@ -265,6 +345,7 @@ enum EnumWithStrings {
265345
}
266346
```
267347

348+
268349
### Authorization
269350
The OpenAPI generator supports Bearer Token authorization. In order to enable the sending
270351
of tokens in each request you can set the token using the global OpenAPI configuration:
@@ -277,7 +358,6 @@ OpenAPI.TOKEN = 'some-bearer-token';
277358

278359

279360
### Compare to other generators
280-
281361
Depending on which swagger generator you use, you will see different output.
282362
For instance: Different ways of generating models, services, level of quality,
283363
HTTP client, etc. I've compiled a list with the results per area and how they

bin/index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ const program = require('commander');
77
const pkg = require('../package.json');
88

99
program
10+
.name('openapi')
11+
.usage('[options]')
1012
.version(pkg.version)
11-
.option('-i, --input <value>', 'Path to swagger specification', './spec.json')
12-
.option('-o, --output <value>', 'Output directory', './generated')
13+
.requiredOption('-i, --input <value>', 'OpenAPI specification, can be a path, url or string content (required)')
14+
.requiredOption('-o, --output <value>', 'Output directory (required)')
1315
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr]', 'fetch')
14-
.option('--useOptions', 'Use options vs arguments style functions')
16+
.option('--useOptions', 'Use options instead of arguments')
1517
.option('--useUnionTypes', 'Use union types instead of enums')
16-
.option('--exportCore <value>', 'Generate core', true)
17-
.option('--exportServices <value>', 'Generate services', true)
18-
.option('--exportModels <value>', 'Generate models', true)
19-
.option('--exportSchemas <value>', 'Generate schemas', false)
18+
.option('--exportCore <value>', 'Write core files to disk', true)
19+
.option('--exportServices <value>', 'Write services to disk', true)
20+
.option('--exportModels <value>', 'Write models to disk', true)
21+
.option('--exportSchemas <value>', 'Write schemas to disk', false)
2022
.parse(process.argv);
2123

2224
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));

src/openApi/v3/parser/getModel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
114114
}
115115
}
116116

117-
// TODO: Add correct support for oneOf, anyOf, allOf
118-
// TODO: https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
117+
// TODO:
118+
// Add correct support for oneOf, anyOf, allOf
119+
// https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
119120

120121
if (definition.anyOf && definition.anyOf.length && !definition.properties) {
121122
model.export = 'generic';

src/openApi/v3/parser/getOperation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function getOperation(openApi: OpenApi, url: string, method: string, op:
5858
operation.parametersBody = parameters.parametersBody;
5959
}
6060

61+
// TODO: form data goes wrong here: https://github.com/ferdikoomen/openapi-typescript-codegen/issues/257§
6162
if (op.requestBody) {
6263
const requestBodyDef = getRef<OpenApiRequestBody>(openApi, op.requestBody);
6364
const requestBody = getOperationRequestBody(openApi, requestBodyDef);

src/templates/core/ApiError.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,22 @@ export class ApiError extends Error {
2222
}
2323
}
2424

25-
export namespace ApiError {
26-
export enum Message {
27-
BAD_REQUEST = 'Bad Request',
28-
UNAUTHORIZED = 'Unauthorized',
29-
FORBIDDEN = 'Forbidden',
30-
NOT_FOUND = 'Not Found',
31-
INTERNAL_SERVER_ERROR = 'Internal Server Error',
32-
BAD_GATEWAY = 'Bad Gateway',
33-
SERVICE_UNAVAILABLE = 'Service Unavailable',
34-
GENERIC_ERROR = 'Generic Error',
35-
}
36-
}
37-
3825
/**
3926
* Catch common errors (based on status code).
4027
* @param result
4128
*/
4229
export function catchGenericError(result: Result): void {
4330
switch (result.status) {
44-
case 400: throw new ApiError(result, ApiError.Message.BAD_REQUEST);
45-
case 401: throw new ApiError(result, ApiError.Message.UNAUTHORIZED);
46-
case 403: throw new ApiError(result, ApiError.Message.FORBIDDEN);
47-
case 404: throw new ApiError(result, ApiError.Message.NOT_FOUND);
48-
case 500: throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR);
49-
case 502: throw new ApiError(result, ApiError.Message.BAD_GATEWAY);
50-
case 503: throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE);
31+
case 400: throw new ApiError(result, 'Bad Request');
32+
case 401: throw new ApiError(result, 'Unauthorized');
33+
case 403: throw new ApiError(result, 'Forbidden');
34+
case 404: throw new ApiError(result, 'Not Found');
35+
case 500: throw new ApiError(result, 'Internal Server Error');
36+
case 502: throw new ApiError(result, 'Bad Gateway');
37+
case 503: throw new ApiError(result, 'Service Unavailable');
5138
}
5239

5340
if (!isSuccess(result.status)) {
54-
throw new ApiError(result, ApiError.Message.GENERIC_ERROR);
41+
throw new ApiError(result, 'Generic Error');
5542
}
5643
}

src/templates/core/request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ export async function request(options: Readonly<RequestOptions>): Promise<Result
5050
url += getQueryString(options.query);
5151
}
5252

53-
// Append formData as body
53+
// Append formData as body, this needs to be parsed to key=value pairs
54+
// so the backend can parse this just like a regular HTML form.
5455
if (options.formData) {
5556
request.body = getFormData(options.formData);
57+
headers.append('Content-Type', 'application/x-www-form-urlencoded');
5658
} else if (options.body) {
5759

5860
// If this is blob data, then pass it directly to the body and set content type.

0 commit comments

Comments
 (0)