Skip to content

Commit 06214f5

Browse files
committed
- Added flag to enable the optional generation of schemas and services
1 parent ca2174c commit 06214f5

20 files changed

+423
-129
lines changed

.prettierrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"semi": true,
33
"singleQuote": true,
44
"trailingComma": "es5",
5-
"printWidth": 500,
5+
"printWidth": 200,
66
"tabWidth": 4
77
}

README.md

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,59 +56,170 @@ const OpenAPI = require('openapi-typescript-codegen');
5656

5757
OpenAPI.generate({
5858
input: './api/openapi.json',
59-
output: './dist'
59+
output: './generated'
6060
});
6161
```
6262

6363
Or by providing the JSON directly:
6464

6565
```javascript
6666
const OpenAPI = require('openapi-typescript-codegen');
67+
6768
const spec = require('./api/openapi.json');
6869

6970
OpenAPI.generate({
7071
input: spec,
71-
output: './dist'
72+
output: './generated'
7273
});
7374
```
7475

7576
## Features
7677

7778
### Argument-style vs. Object-style
78-
There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in JS/TS, because of that,
79-
we offer an option `--useOptions` to generate code in two different styles.
79+
There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in Javascript or Typescript, because of
80+
that, we offer the flag `--useOptions` to generate code in two different styles.
8081

8182
Argument-style:
8283
```typescript
8384
function createUser(name: string, password: string, type?: string, address?: string) {
8485
// ...
8586
}
8687

87-
// usage
88+
// Usage
8889
createUser('Jack', '123456', undefined, 'NY US');
8990
```
9091

9192
Object-style:
9293
```typescript
93-
interface CreateUserOptions {
94+
function createUser({ name, password, type, address }: {
9495
name: string,
9596
password: string,
9697
type?: string
9798
address?: string
98-
}
99-
100-
function createUser({ name, password, type, address }: CreateUserOptions) {
99+
}) {
101100
// ...
102101
}
103102

104-
// usage
103+
// Usage
105104
createUser({
106105
name: 'Jack',
107106
password: '123456',
108107
address: 'NY US'
109108
});
110109
```
111110

111+
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
115+
that can be useful during runtime, for instance: `maxLength` of a string or a `pattern` to match, etc. Let's say
116+
we have the following model:
117+
118+
```json
119+
{
120+
"MyModel": {
121+
"required": [
122+
"key",
123+
"name"
124+
],
125+
"type": "object",
126+
"properties": {
127+
"key": {
128+
"maxLength": 64,
129+
"pattern": "^[a-zA-Z0-9_]*$",
130+
"type": "string"
131+
},
132+
"name": {
133+
"maxLength": 255,
134+
"type": "string"
135+
},
136+
"enabled": {
137+
"type": "boolean",
138+
"readOnly": true
139+
},
140+
"modified": {
141+
"type": "string",
142+
"format": "date-time",
143+
"readOnly": true
144+
}
145+
}
146+
}
147+
}
148+
```
149+
150+
This will generate the following interface:
151+
152+
```typescript
153+
export interface ModelWithPattern {
154+
key: string;
155+
name: string;
156+
readonly enabled?: boolean;
157+
readonly modified?: string;
158+
}
159+
```
160+
161+
The interface does not contain any properties like `maxLength` or `pattern`. However they could be useful
162+
if we wanted to create some form where a user could create such a model. In that form you would iterate
163+
over the properties to render form fields based on their type and validate the input based on the `maxLength`
164+
or `pattern` property. This requires us to have this information somewhere... For this we can use the
165+
flag `--exportSchemas` to generate a runtime model next to the normal interface:
166+
167+
```typescript
168+
export const $ModelWithPattern = {
169+
properties: {
170+
key: {
171+
type: 'string',
172+
isRequired: true,
173+
maxLength: 64,
174+
pattern: '^[a-zA-Z0-9_]*$',
175+
},
176+
name: {
177+
type: 'string',
178+
isRequired: true,
179+
maxLength: 255,
180+
},
181+
enabled: {
182+
type: 'boolean',
183+
isReadOnly: true,
184+
},
185+
modified: {
186+
type: 'string',
187+
isReadOnly: true,
188+
format: 'date-time',
189+
},
190+
},
191+
};
192+
```
193+
194+
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:
196+
197+
```typescript jsx
198+
import { $ModelWithPattern } from './generated';
199+
200+
// Some pseudo code to iterate over the properties and return a form field
201+
// the form field could be some abstract component that renders the correct
202+
// field type and validation rules based on the given input.
203+
const formFields = Object.entries($ModelWithPattern.properties).map(([key, value]) => (
204+
<FormField
205+
name={key}
206+
type={value.type}
207+
format={value.format}
208+
maxLength={value.maxLength}
209+
pattern={value.pattern}
210+
isReadOnly={value.isReadOnly}
211+
/>
212+
));
213+
214+
const MyForm = () => (
215+
<form>
216+
{formFields}
217+
</form>
218+
);
219+
220+
```
221+
222+
112223
### Enum with custom names and descriptions
113224
You can use `x-enum-varnames` and `x-enum-descriptions` in your spec to generate enum with custom names and descriptions.
114225
It's not in official [spec](https://github.com/OAI/OpenAPI-Specification/issues/681) yet. But its a supported extension
@@ -159,6 +270,7 @@ The OpenAPI generator supports Bearer Token authorization. In order to enable th
159270
of tokens in each request you can set the token using the global OpenAPI configuration:
160271

161272
```typescript
162-
import { OpenAPI } from './'
163-
OpenAPI.TOKEN = 'some-bearer-token'
273+
import { OpenAPI } from './generated';
274+
275+
OpenAPI.TOKEN = 'some-bearer-token';
164276
```

bin/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ program
1313
.option('--client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
1414
.option('--useOptions', 'Use options vs arguments style functions', false)
1515
.option('--useUnionTypes', 'Use inclusive union types', false)
16+
.option('--exportServices', 'Generate services', true)
17+
.option('--exportSchemas', 'Generate schemas', false)
1618
.parse(process.argv);
1719

1820
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));
@@ -23,6 +25,8 @@ if (OpenAPI) {
2325
output: program.output,
2426
httpClient: program.client,
2527
useOptions: program.useOptions,
26-
useUnionTypes: program.useUnionTypes
28+
useUnionTypes: program.useUnionTypes,
29+
exportServices: program.exportServices,
30+
exportSchemas: program.exportSchemas,
2731
});
2832
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openapi-typescript-codegen",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.",
55
"author": "Ferdi Koomen",
66
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
@@ -63,7 +63,6 @@
6363
"dependencies": {
6464
"camelcase": "5.3.1",
6565
"commander": "4.1.1",
66-
"glob": "7.1.6",
6766
"handlebars": "4.7.3",
6867
"js-yaml": "3.13.1",
6968
"mkdirp": "1.0.3",
@@ -86,6 +85,7 @@
8685
"eslint-config-prettier": "6.10.0",
8786
"eslint-plugin-prettier": "3.1.2",
8887
"eslint-plugin-simple-import-sort": "5.0.1",
88+
"glob": "7.1.6",
8989
"jest": "25.1.0",
9090
"jest-cli": "25.1.0",
9191
"prettier": "1.19.1",

src/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface Options {
1818
httpClient?: HttpClient;
1919
useOptions?: boolean;
2020
useUnionTypes?: boolean;
21+
exportServices?: boolean;
22+
exportSchemas?: boolean;
2123
write?: boolean;
2224
}
2325

@@ -30,9 +32,11 @@ export interface Options {
3032
* @param httpClient The selected httpClient (fetch or XHR).
3133
* @param useOptions Use options or arguments functions.
3234
* @param useUnionTypes Use inclusive union types.
35+
* @param exportServices: Generate services.
36+
* @param exportSchemas: Generate schemas.
3337
* @param write Write the files to disk (true or false).
3438
*/
35-
export function generate({ input, output, httpClient = HttpClient.FETCH, useOptions = false, useUnionTypes = false, write = true }: Options): void {
39+
export function generate({ input, output, httpClient = HttpClient.FETCH, useOptions = false, useUnionTypes = false, exportServices = true, exportSchemas = false, write = true }: Options): void {
3640
try {
3741
// Load the specification, read the OpenAPI version and load the
3842
// handlebar templates for the given language
@@ -45,7 +49,7 @@ export function generate({ input, output, httpClient = HttpClient.FETCH, useOpti
4549
const client = parseV2(openApi);
4650
const clientFinal = postProcessClient(client, useUnionTypes);
4751
if (write) {
48-
writeClient(clientFinal, templates, output, httpClient, useOptions);
52+
writeClient(clientFinal, templates, output, httpClient, useOptions, exportServices, exportSchemas);
4953
}
5054
break;
5155
}
@@ -54,7 +58,7 @@ export function generate({ input, output, httpClient = HttpClient.FETCH, useOpti
5458
const client = parseV3(openApi);
5559
const clientFinal = postProcessClient(client, useUnionTypes);
5660
if (write) {
57-
writeClient(clientFinal, templates, output, httpClient, useOptions);
61+
writeClient(clientFinal, templates, output, httpClient, useOptions, exportServices, exportSchemas);
5862
}
5963
break;
6064
}

src/templates/core/requestUsingFetch.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { Result } from './Result';
1212
* @param response Response object from fetch
1313
*/
1414
async function parseBody(response: Response): Promise<any> {
15-
1615
try {
1716
const contentType = response.headers.get('Content-Type');
1817
if (contentType) {

src/templates/core/requestUsingXHR.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ function parseBody(xhr: XMLHttpRequest): any {
2828
} catch (e) {
2929
console.error(e);
3030
}
31-
3231
return null;
3332
}
3433

src/templates/index.hbs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,31 @@
22
/* tslint:disable */
33
/* eslint-disable */
44
/* prettier-ignore */
5+
{{#if exportServices}}
56

67
export { ApiError } from './core/ApiError';
78
export { isSuccess } from './core/isSuccess';
89
export { OpenAPI } from './core/OpenAPI';
10+
{{/if}}
911
{{#if models}}
1012

1113
{{#each models}}
1214
export { {{{this}}} } from './models/{{{this}}}';
1315
{{/each}}
1416
{{/if}}
17+
{{#if exportSchemas}}
1518
{{#if models}}
1619

1720
{{#each models}}
1821
export { ${{{this}}} } from './schemas/${{{this}}}';
1922
{{/each}}
2023
{{/if}}
24+
{{/if}}
25+
{{#if exportServices}}
2126
{{#if services}}
2227

2328
{{#each services}}
2429
export { {{{this}}} } from './services/{{{this}}}';
2530
{{/each}}
2631
{{/if}}
32+
{{/if}}

src/utils/readHandlebarsTemplate.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,19 @@ import * as Handlebars from 'handlebars';
66
* @param filePath
77
*/
88
export function readHandlebarsTemplate(filePath: string): Handlebars.TemplateDelegate {
9-
if (fs.existsSync(filePath)) {
10-
try {
11-
const template = fs
12-
.readFileSync(filePath, 'utf8')
13-
.toString()
14-
.trim();
9+
const template = fs
10+
.readFileSync(filePath, 'utf8')
11+
.toString()
12+
.trim();
1513

16-
return Handlebars.compile(template, {
17-
strict: true,
18-
noEscape: true,
19-
preventIndent: true,
20-
knownHelpersOnly: true,
21-
knownHelpers: {
22-
equals: true,
23-
notEquals: true,
24-
},
25-
});
26-
} catch (e) {
27-
throw new Error(`Could not compile Handlebar template: "${filePath}"`);
28-
}
29-
}
30-
throw new Error(`Could not find Handlebar template: "${filePath}"`);
14+
return Handlebars.compile(template, {
15+
strict: true,
16+
noEscape: true,
17+
preventIndent: true,
18+
knownHelpersOnly: true,
19+
knownHelpers: {
20+
equals: true,
21+
notEquals: true,
22+
},
23+
});
3124
}

src/utils/readHandlebarsTemplates.spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import * as fs from 'fs';
2-
import * as glob from 'glob';
32

43
import { readHandlebarsTemplates } from './readHandlebarsTemplates';
54

65
jest.mock('fs');
7-
jest.mock('glob');
86

97
const fsExistsSync = fs.existsSync as jest.MockedFunction<typeof fs.existsSync>;
108
const fsReadFileSync = fs.readFileSync as jest.MockedFunction<typeof fs.readFileSync>;
11-
const globSync = glob.sync as jest.MockedFunction<typeof glob.sync>;
129

1310
describe('readHandlebarsTemplates', () => {
1411
it('should read the templates', () => {
1512
fsExistsSync.mockReturnValue(true);
1613
fsReadFileSync.mockReturnValue('{{{message}}}');
17-
globSync.mockReturnValue([]);
1814

1915
const template = readHandlebarsTemplates();
2016

0 commit comments

Comments
 (0)