diff --git a/README.md b/README.md index 5d85df1a..53ebf09c 100644 --- a/README.md +++ b/README.md @@ -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' +``` diff --git a/bin/index.js b/bin/index.js index c38cce7a..72024735 100755 --- a/bin/index.js +++ b/bin/index.js @@ -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 ); } diff --git a/src/index.spec.ts b/src/index.spec.ts index e3e52483..dd34eac2 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -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); }); }); diff --git a/src/index.ts b/src/index.ts index b288b887..68d0b740 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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; } diff --git a/src/templates/partials/parameters.hbs b/src/templates/partials/parameters.hbs new file mode 100644 index 00000000..c4f5241b --- /dev/null +++ b/src/templates/partials/parameters.hbs @@ -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}} diff --git a/src/templates/service.hbs b/src/templates/service.hbs index b95f0b73..0db0a6f9 100644 --- a/src/templates/service.hbs +++ b/src/templates/service.hbs @@ -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}}}', diff --git a/src/utils/readHandlebarsTemplates.ts b/src/utils/readHandlebarsTemplates.ts index c7792374..a5fc2af3 100644 --- a/src/utils/readHandlebarsTemplates.ts +++ b/src/utils/readHandlebarsTemplates.ts @@ -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))); diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index 1d742401..7da82915 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -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); } diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index 7c47042a..b911a4ab 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -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 { diff --git a/src/utils/writeClientModels.ts b/src/utils/writeClientModels.ts index a0820bf4..ddf0004e 100644 --- a/src/utils/writeClientModels.ts +++ b/src/utils/writeClientModels.ts @@ -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 => { diff --git a/src/utils/writeClientSchemas.ts b/src/utils/writeClientSchemas.ts index 2132f39a..7ce3a8b7 100644 --- a/src/utils/writeClientSchemas.ts +++ b/src/utils/writeClientSchemas.ts @@ -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 => { diff --git a/src/utils/writeClientServices.ts b/src/utils/writeClientServices.ts index 1189ee05..486d59ae 100644 --- a/src/utils/writeClientServices.ts +++ b/src/utils/writeClientServices.ts @@ -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)); }); } diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 43ce009f..3c061e72 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -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> { + }): Promise> { 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 { + }): Promise { 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 { + }: { + parameterString: string | null, + parameterNumber: number | null, + parameterBoolean: boolean | null, + parameterEnum: ('Success' | 'Warning' | 'Error'), + parameterModel: ModelWithString | null, + }): Promise { 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 { + }): Promise { 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 { + }): Promise { 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 { + }): Promise { 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 { + }): Promise { 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 | null, parameterDictionary: any, parameterEnum: ('Success' | 'Warning' | 'Error') | null, id?: number, - ): Promise { + }): Promise { 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 { + + const result = await __request({ + method: 'post', + path: \`/api/v\${OpenAPI.VERSION}/upload\`, + formData: { + 'file': file, + }, + }); + + catchGenericError(result); + + return result.body; + } + +}" +`; diff --git a/test/index.js b/test/index.js index 72910efd..4211b93d 100644 --- a/test/index.js +++ b/test/index.js @@ -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/'); diff --git a/test/index.spec.js b/test/index.spec.js index 910bdf0e..4a35fe1e 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -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 diff --git a/test/mock/v3/spec.json b/test/mock/v3/spec.json index 6d617588..41cca719 100644 --- a/test/mock/v3/spec.json +++ b/test/mock/v3/spec.json @@ -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": [