- Added option "---useOptions" in the API

This commit is contained in:
Ferdi Koomen 2020-02-22 22:27:23 +01:00
parent 6b373978b5
commit e3b848c0a6
16 changed files with 291 additions and 54 deletions

102
README.md
View File

@ -36,23 +36,22 @@ npm install openapi-typescript-codegen --save-dev
```json
{
"scripts": {
"generate": "openapi ./api/openapi.json ./dist"
"generate": "openapi --input ./api/openapi.json --output ./dist"
}
...
}
```
Command line
**Command line**
```
npm install openapi-typescript-codegen -g
openapi ./api/openapi.json ./dist
openapi --input ./api/openapi.json --output ./dist
```
NodeJS API:
**NodeJS API**
```
```javascript
const OpenAPI = require('openapi-typescript-codegen');
OpenAPI.generate(
@ -60,3 +59,94 @@ OpenAPI.generate(
'./dist'
);
```
## Features
### Argument-style vs. Object-style
There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in JS/TS, because of that,
we offer an option `--useOptions` to generate code in two different styles.
Argument-style:
```typescript
function createUser(name: string, password: string, type?: string, address?: string) {
// ...
}
// usage
createUser('Jack', '123456', undefined, 'NY US');
```
Object-style:
```typescript
interface CreateUserOptions {
name: string,
password: string,
type?: string
address?: string
}
function createUser({ name, password, type, address }: CreateUserOptions) {
// ...
}
// usage
createUser({
name: 'Jack',
password: '123456',
address: 'NY US'
});
```
### Enum with custom names and descriptions
You can use `x-enum-varnames` and `x-enum-descriptions` in your spec to generate enum with custom names and descriptions.
It's not in official [spec](https://github.com/OAI/OpenAPI-Specification/issues/681) yet. But its a supported extension
that can help developers use more meaningful enumerators.
```json
{
"EnumWithStrings": {
"description": "This is a simple enum with strings",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"Success",
"Warning"
"Error"
],
"x-enum-descriptions": [
"Used when the status of something is successful",
"Used when the status of something has a warning"
"Used when the status of something has an error"
]
}
}
```
Generated code:
```typescript
enum EnumWithStrings {
/*
* Used when the status of something is successful
*/
Success = 0,
/*
* Used when the status of something has a warning
*/
Waring = 1,
/*
* Used when the status of something has an error
*/
Error = 2,
}
```
### Authorization
The OpenAPI generator supports Bearer Token authorization. In order to enable the sending
of tokens in each request you can set the token using the global OpenAPI configuration:
```typescript
import { OpenAPI } from './'
OpenAPI.TOKEN = 'some-bearer-token'
```

View File

@ -11,6 +11,7 @@ program
.option('--input [value]', 'Path to swagger specification', './spec.json')
.option('--output [value]', 'Output directory', './generated')
.option('--client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
.option('--useOptions', 'Use options vs arguments style functions', false)
.parse(process.argv);
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));
@ -19,6 +20,7 @@ if (OpenAPI) {
OpenAPI.generate(
program.input,
program.output,
program.client
program.client,
program.useOptions
);
}

View File

@ -2,10 +2,10 @@ import * as OpenAPI from '.';
describe('index', () => {
it('parses v2 without issues', () => {
OpenAPI.generate('./test/mock/v2/spec.json', './test/result/v2/', OpenAPI.HttpClient.FETCH, false);
OpenAPI.generate('./test/mock/v2/spec.json', './test/result/v2/', OpenAPI.HttpClient.FETCH, false, false);
});
it('parses v3 without issues', () => {
OpenAPI.generate('./test/mock/v3/spec.json', './test/result/v3/', OpenAPI.HttpClient.FETCH, false);
OpenAPI.generate('./test/mock/v3/spec.json', './test/result/v3/', OpenAPI.HttpClient.FETCH, false, false);
});
});

View File

@ -19,9 +19,10 @@ export enum HttpClient {
* @param input The relative location of the OpenAPI spec.
* @param output The relative location of the output directory.
* @param httpClient The selected httpClient (fetch or XHR).
* @param useOptions Use options or arguments functions.
* @param write Write the files to disk (true or false)
*/
export function generate(input: string, output: string, httpClient: HttpClient = HttpClient.FETCH, write: boolean = true): void {
export function generate(input: string, output: string, httpClient: HttpClient = HttpClient.FETCH, useOptions: boolean = false, write: boolean = true): void {
const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);
@ -36,14 +37,14 @@ export function generate(input: string, output: string, httpClient: HttpClient =
case OpenApiVersion.V2:
const clientV2 = parseV2(openApi);
if (write) {
writeClient(clientV2, httpClient, templates, outputPath);
writeClient(clientV2, httpClient, templates, outputPath, useOptions);
}
break;
case OpenApiVersion.V3:
const clientV3 = parseV3(openApi);
if (write) {
writeClient(clientV3, httpClient, templates, outputPath);
writeClient(clientV3, httpClient, templates, outputPath, useOptions);
}
break;
}

View File

@ -0,0 +1,18 @@
{{#if parameters}}
{{#if @root.useOptions}}
{
{{#each parameters}}
{{{name}}}{{#if default}} = {{{default}}}{{/if}},
{{/each}}
}: {
{{#each parameters}}
{{{name}}}{{>isRequired}}: {{>type}},
{{/each}}
}
{{~else}}
{{#each parameters}}
{{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}},
{{/each}}
{{/if}}
{{/if}}

View File

@ -35,11 +35,7 @@ export class {{{name}}} {
{{/each}}
* @throws ApiError
*/
public static async {{{name}}}({{#if parameters}}
{{#each parameters}}
{{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}},
{{/each}}
{{/if}}): Promise<{{>result}}> {
public static async {{{name}}}({{>parameters}}): Promise<{{>result}}> {
const result = await __request({
method: '{{{method}}}',

View File

@ -28,7 +28,7 @@ export function readHandlebarsTemplates(): Templates {
settings: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/core/OpenAPI.hbs`)),
};
const partials = path.resolve(__dirname, `../../src/templates//partials`);
const partials = path.resolve(__dirname, `../../src/templates/partials`);
const partialsFiles = glob.sync('*.hbs', { cwd: partials });
partialsFiles.forEach(partial => {
Handlebars.registerPartial(path.basename(partial, '.hbs'), readHandlebarsTemplate(path.resolve(partials, partial)));

View File

@ -17,9 +17,10 @@ import { writeClientSettings } from './writeClientSettings';
* @param client Client object with all the models, services, etc.
* @param httpClient The selected httpClient (fetch or XHR).
* @param templates Templates wrapper with all loaded Handlebars templates.
* @param outputPath
* @param outputPath Directory to write the generated files to.
* @param useOptions Use options or arguments functions.
*/
export function writeClient(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string): void {
export function writeClient(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string, useOptions: boolean): void {
const outputPathCore = path.resolve(outputPath, 'core');
const outputPathModels = path.resolve(outputPath, 'models');
const outputPathSchemas = path.resolve(outputPath, 'schemas');
@ -56,7 +57,7 @@ export function writeClient(client: Client, httpClient: HttpClient, templates: T
// Write the client files
writeClientModels(client.models, templates, outputPathModels);
writeClientSchemas(client.models, templates, outputPathSchemas);
writeClientServices(client.services, templates, outputPathServices);
writeClientServices(client.services, templates, outputPathServices, useOptions);
writeClientSettings(client, httpClient, templates, outputPathCore);
writeClientIndex(client, templates, outputPath);
}

View File

@ -11,7 +11,7 @@ import { getServiceNames } from './getServiceNames';
* library. But yuo can also import individual models and services directly.
* @param client Client object, containing, models, schemas and services.
* @param templates The loaded handlebar templates.
* @param outputPath
* @param outputPath Directory to write the generated files to.
*/
export function writeClientIndex(client: Client, templates: Templates, outputPath: string): void {
try {

View File

@ -9,7 +9,7 @@ import { format } from './format';
* Generate Models using the Handlebar template and write to disk.
* @param models Array of Models to write.
* @param templates The loaded handlebar templates.
* @param outputPath
* @param outputPath Directory to write the generated files to.
*/
export function writeClientModels(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {

View File

@ -9,7 +9,7 @@ import { format } from './format';
* Generate Schemas using the Handlebar template and write to disk.
* @param models Array of Models to write.
* @param templates The loaded handlebar templates.
* @param outputPath
* @param outputPath Directory to write the generated files to.
*/
export function writeClientSchemas(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {

View File

@ -9,13 +9,17 @@ import { format } from './format';
* Generate Services using the Handlebar template and write to disk.
* @param services Array of Services to write.
* @param templates The loaded handlebar templates.
* @param outputPath
* @param outputPath Directory to write the generated files to.
* @param useOptions Use options or arguments functions.
*/
export function writeClientServices(services: Service[], templates: Templates, outputPath: string): void {
export function writeClientServices(services: Service[], templates: Templates, outputPath: string, useOptions: boolean): void {
services.forEach(service => {
const file = path.resolve(outputPath, `${service.name}.ts`);
const templateData = exportService(service);
const templateResult = templates.service(templateData);
const templateResult = templates.service({
...templateData,
useOptions,
});
fs.writeFileSync(file, format(templateResult));
});
}

View File

@ -2786,6 +2786,7 @@ export { ParametersService } from './services/ParametersService';
export { ResponseService } from './services/ResponseService';
export { SimpleService } from './services/SimpleService';
export { TypesService } from './services/TypesService';
export { UploadService } from './services/UploadService';
"
`;
@ -4187,7 +4188,10 @@ export class ComplexService {
* @result ModelWithString Successful response
* @throws ApiError
*/
public static async complexTypes(
public static async complexTypes({
parameterObject,
parameterReference,
}: {
parameterObject: {
first?: {
second?: {
@ -4196,7 +4200,7 @@ export class ComplexService {
},
},
parameterReference: ModelWithString,
): Promise<Array<ModelWithString>> {
}): Promise<Array<ModelWithString>> {
const result = await __request({
method: 'get',
@ -4225,7 +4229,10 @@ export class ComplexService {
* @result ModelWithString Success
* @throws ApiError
*/
public static async complexParams(
public static async complexParams({
id,
requestBody,
}: {
id: number,
requestBody?: {
readonly key: string | null,
@ -4240,7 +4247,7 @@ export class ComplexService {
readonly name?: string | null,
},
},
): Promise<ModelWithString> {
}): Promise<ModelWithString> {
const result = await __request({
method: 'put',
@ -4277,15 +4284,21 @@ export class DefaultsService {
* @param parameterModel This is a simple model
* @throws ApiError
*/
public static async callWithDefaultParameters(
parameterString: string | null = 'Hello World!',
parameterNumber: number | null = 123,
parameterBoolean: boolean | null = true,
parameterEnum: ('Success' | 'Warning' | 'Error') = 'Success',
parameterModel: ModelWithString | null = {
public static async callWithDefaultParameters({
parameterString = 'Hello World!',
parameterNumber = 123,
parameterBoolean = true,
parameterEnum = 'Success',
parameterModel = {
\\"prop\\": \\"Hello World\\"
},
): Promise<void> {
}: {
parameterString: string | null,
parameterNumber: number | null,
parameterBoolean: boolean | null,
parameterEnum: ('Success' | 'Warning' | 'Error'),
parameterModel: ModelWithString | null,
}): Promise<void> {
const result = await __request({
method: 'get',
@ -4365,13 +4378,19 @@ export class ParametersService {
* @param requestBody This is the parameter that goes into the body
* @throws ApiError
*/
public static async callWithParameters(
public static async callWithParameters({
parameterHeader,
parameterQuery,
parameterForm,
parameterCookie,
requestBody,
}: {
parameterHeader: string | null,
parameterQuery: string | null,
parameterForm: string | null,
parameterCookie: string | null,
requestBody: ModelWithString | null,
): Promise<void> {
}): Promise<void> {
const result = await __request({
method: 'get',
@ -4407,7 +4426,16 @@ export class ParametersService {
* @param parameterPath3 This is the parameter that goes into the path
* @throws ApiError
*/
public static async callWithWeirdParameterNames(
public static async callWithWeirdParameterNames({
parameterHeader,
parameterQuery,
parameterForm,
parameterCookie,
requestBody,
parameterPath1,
parameterPath2,
parameterPath3,
}: {
parameterHeader: string | null,
parameterQuery: string | null,
parameterForm: string | null,
@ -4416,7 +4444,7 @@ export class ParametersService {
parameterPath1?: string,
parameterPath2?: string,
parameterPath3?: string,
): Promise<void> {
}): Promise<void> {
const result = await __request({
method: 'get',
@ -4446,10 +4474,13 @@ export class ParametersService {
* @param parameter This is an optional parameter
* @throws ApiError
*/
public static async getCallWithOptionalParam(
public static async getCallWithOptionalParam({
requestBody,
parameter,
}: {
requestBody: ModelWithString,
parameter?: string,
): Promise<void> {
}): Promise<void> {
const result = await __request({
method: 'get',
@ -4470,10 +4501,13 @@ export class ParametersService {
* @param requestBody This is an optional parameter
* @throws ApiError
*/
public static async postCallWithOptionalParam(
public static async postCallWithOptionalParam({
parameter,
requestBody,
}: {
parameter: string,
requestBody?: ModelWithString,
): Promise<void> {
}): Promise<void> {
const result = await __request({
method: 'post',
@ -4723,16 +4757,25 @@ export class TypesService {
* @result any Response is a simple object
* @throws ApiError
*/
public static async types(
parameterNumber: number = 123,
parameterString: string | null = 'default',
parameterBoolean: boolean | null = true,
parameterObject: any = null,
public static async types({
parameterNumber = 123,
parameterString = 'default',
parameterBoolean = true,
parameterObject = null,
parameterArray,
parameterDictionary,
parameterEnum,
id,
}: {
parameterNumber: number,
parameterString: string | null,
parameterBoolean: boolean | null,
parameterObject: any,
parameterArray: Array<string> | null,
parameterDictionary: any,
parameterEnum: ('Success' | 'Warning' | 'Error') | null,
id?: number,
): Promise<number | string | boolean | any> {
}): Promise<number | string | boolean | any> {
const result = await __request({
method: 'get',
@ -4755,3 +4798,42 @@ export class TypesService {
}"
`;
exports[`generation v3 file(./test/result/v3/services/UploadService.ts): ./test/result/v3/services/UploadService.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class UploadService {
/**
* @param file Supply a file reference for upload
* @result boolean
* @throws ApiError
*/
public static async uploadFile({
file,
}: {
file: File,
}): Promise<boolean> {
const result = await __request({
method: 'post',
path: \`/api/v\${OpenAPI.VERSION}/upload\`,
formData: {
'file': file,
},
});
catchGenericError(result);
return result.body;
}
}"
`;

View File

@ -4,12 +4,14 @@ OpenAPI.generate(
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
false,
);
OpenAPI.generate(
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
true,
);
OpenAPI.compile('./test/result/v2/');

View File

@ -10,6 +10,7 @@ describe('generation', () => {
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
false,
);
test.each(glob
@ -27,6 +28,7 @@ describe('generation', () => {
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
true,
);
test.each(glob

View File

@ -233,7 +233,7 @@
"tags": [
"Parameters"
],
"operationId": "getCallWithOptionalParam",
"operationId": "GetCallWithOptionalParam",
"parameters": [
{
"description": "This is an optional parameter",
@ -262,7 +262,7 @@
"tags": [
"Parameters"
],
"operationId": "postCallWithOptionalParam",
"operationId": "PostCallWithOptionalParam",
"parameters": [
{
"description": "This is a required parameter",
@ -653,6 +653,45 @@
}
}
},
"/api/v{api-version}/upload": {
"post": {
"tags": [
"Upload"
],
"operationId": "UploadFile",
"parameters": [
{
"description": "Supply a file reference for upload",
"name": "file",
"in": "formData",
"required": true,
"schema": {
"type": "File"
}
},
{
"name": "api-version",
"in": "path",
"required": true,
"nullable": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
}
}
},
"/api/v{api-version}/complex": {
"get": {
"tags": [