- Cleanup of templates

- Fixed File type for Node.js and Browser clients
This commit is contained in:
Ferdi Koomen 2020-09-30 23:35:02 +02:00
parent 6b229a260f
commit 923c141fdb
34 changed files with 114 additions and 106 deletions

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.5.0-beta",
"version": "0.5.0-rc",
"description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
@ -46,8 +46,6 @@
"build": "rollup --config --environment NODE_ENV:development",
"build:watch": "rollup --config --environment NODE_ENV:development --watch",
"release": "rollup --config --environment NODE_ENV:production",
"start": "nest start --path ./test/server/tsconfig.json",
"start:watch": "nest start --path ./test/server/tsconfig.json --watch",
"run": "node ./test/index.js",
"test": "jest --selectProjects UNIT",
"test:update": "jest --selectProjects UNIT --updateSnapshot",

View File

@ -1,7 +1,7 @@
export enum PrimaryType {
FILE = 'any', // File or Buffer?
FILE = 'File',
OBJECT = 'any',
ARRAY = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',
NUMBER = 'number',
STRING = 'string',

View File

@ -2,7 +2,7 @@ import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('any');
expect(getMappedType('File')).toEqual('File');
expect(getMappedType('String')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');

View File

@ -1,5 +1,5 @@
export enum PrimaryType {
FILE = 'any', // File or Buffer?
FILE = 'File',
OBJECT = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',

View File

@ -2,7 +2,7 @@ import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('any');
expect(getMappedType('File')).toEqual('File');
expect(getMappedType('String')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');

View File

@ -1,9 +1,3 @@
{{#equals httpClient 'fetch'}}
{{>fetch/request}}
{{/equals}}
{{#equals httpClient 'xhr'}}
{{>xhr/request}}
{{/equals}}
{{#equals httpClient 'node'}}
{{>node/request}}
{{/equals}}
{{#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals}}
{{#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals}}
{{#equals @root.httpClient 'node'}}{{>node/request}}{{/equals}}

View File

@ -2,7 +2,7 @@
{{#if extends}}
{{#each extends}}
import type { ${{{this}}} } from './${{{this}}}';
import { ${{{this}}} } from './${{{this}}}';
{{/each}}
{{/if}}

View File

@ -37,45 +37,45 @@ export class {{{name}}} {
const result = await __request({
method: '{{{method}}}',
path: `{{{path}}}`,
{{#if parametersCookie~}}
{{#if parametersCookie}}
cookies: {
{{#each parametersCookie}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersHeader~}}
{{#if parametersHeader}}
headers: {
{{#each parametersHeader}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersQuery~}}
{{#if parametersQuery}}
query: {
{{#each parametersQuery}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersForm~}}
{{#if parametersForm}}
formData: {
{{#each parametersForm}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersBody~}}
{{#if parametersBody}}
body: {{{parametersBody.name}}},
{{/if}}
{{#if responseHeader~}}
{{#if responseHeader}}
responseHeader: '{{{responseHeader}}}',
{{/if}}
{{#if errors~}}
{{#if errors}}
errors: {
{{#each errors}}
{{#each errors}}
{{{code}}}: `{{{description}}}`,
{{/each}}
{{/each}}
},
{{/if}}
});

View File

@ -0,0 +1,7 @@
{{~#equals this 'File'~}}
{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'node'}}Buffer | ArrayBuffer | ArrayBufferView{{/equals~}}
{{~else~}}
{{this}}
{{~/equals~}}

View File

@ -1,5 +1,5 @@
{{#if @root.useOptions}}
{{~#unless isRequired}}?{{else if default}}?{{/unless~}}
{{else}}
{{~#unless isRequired}}{{#unless default}}?{{/unless}}{{/unless~}}
{{/if}}
{{~#if @root.useOptions~}}
{{#unless isRequired}}?{{else if default}}?{{/unless}}
{{~else~}}
{{#unless isRequired}}{{#unless default}}?{{/unless}}{{/unless}}
{{~/if~}}

View File

@ -9,7 +9,7 @@
{{{name}}}{{>isRequired}}: {{>type}},
{{/each}}
}
{{~else}}
{{else}}
{{#each parameters}}
{{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}},

View File

@ -1,12 +1,12 @@
{
type: 'Array',
{{#if isReadOnly~}}
{{#if isReadOnly}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
{{#if isRequired}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
{{#if isNullable}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,12 +1,12 @@
{
type: 'Dictionary',
{{#if isReadOnly~}}
{{#if isReadOnly}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
{{#if isRequired}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
{{#if isNullable}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,12 +1,12 @@
{
type: 'Enum',
{{#if isReadOnly~}}
{{#if isReadOnly}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
{{#if isRequired}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
{{#if isNullable}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,56 +1,56 @@
{
{{#if type~}}
{{#if type}}
type: '{{{base}}}',
{{/if}}
{{#if isReadOnly~}}
{{#if isReadOnly}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
{{#if isRequired}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
{{#if isNullable}}
isNullable: {{{isNullable}}},
{{/if}}
{{#if format~}}
{{#if format}}
format: '{{{format}}}',
{{/if}}
{{#if maximum~}}
{{#if maximum}}
maximum: {{{maximum}}},
{{/if}}
{{#if exclusiveMaximum~}}
{{#if exclusiveMaximum}}
exclusiveMaximum: {{{exclusiveMaximum}}},
{{/if}}
{{#if minimum~}}
{{#if minimum}}
minimum: {{{minimum}}},
{{/if}}
{{#if exclusiveMinimum~}}
{{#if exclusiveMinimum}}
exclusiveMinimum: {{{exclusiveMinimum}}},
{{/if}}
{{#if multipleOf~}}
{{#if multipleOf}}
multipleOf: {{{multipleOf}}},
{{/if}}
{{#if maxLength~}}
{{#if maxLength}}
maxLength: {{{maxLength}}},
{{/if}}
{{#if minLength~}}
{{#if minLength}}
minLength: {{{minLength}}},
{{/if}}
{{#if pattern~}}
{{#if pattern}}
pattern: '{{{pattern}}}',
{{/if}}
{{#if maxItems~}}
{{#if maxItems}}
maxItems: {{{maxItems}}},
{{/if}}
{{#if minItems~}}
{{#if minItems}}
minItems: {{{minItems}}},
{{/if}}
{{#if uniqueItems~}}
{{#if uniqueItems}}
uniqueItems: {{{uniqueItems}}},
{{/if}}
{{#if maxProperties~}}
{{#if maxProperties}}
maxProperties: {{{maxProperties}}},
{{/if}}
{{#if minProperties~}}
{{#if minProperties}}
minProperties: {{{minProperties}}},
{{/if}}
}

View File

@ -11,13 +11,13 @@
{{/each}}
{{/if}}
},
{{#if isReadOnly~}}
{{#if isReadOnly}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
{{#if isRequired}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
{{#if isNullable}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,5 +1,5 @@
{{~#if link~}}
Array<{{>type link}}>{{>isNullable}}
{{~else~}}
Array<{{{base}}}>{{>isNullable}}
Array<{{>base base}}>{{>isNullable}}
{{~/if~}}

View File

@ -1,5 +1,5 @@
{{~#if link~}}
Record<string, {{>type link}}>{{>isNullable}}
{{~else~}}
Record<string, {{{base}}}>{{>isNullable}}
Record<string, {{>base base}}>{{>isNullable}}
{{~/if~}}

View File

@ -1,13 +1,7 @@
{{~#if @root.useUnionTypes~}}
{{~#each enum~}}
{{{value}}}{{#unless @last}} | {{/unless}}
{{~/each~}}
{{>isNullable}}
{{#each enum}}{{{value}}}{{#unless @last}} | {{/unless}}{{/each}}{{>isNullable}}
{{~else if parent~}}
{{{parent}}}.{{{name}}}{{>isNullable}}
{{~else~}}
{{~#each enum~}}
{{{value}}}{{#unless @last}} | {{/unless}}
{{~/each~}}
{{>isNullable}}
{{#each enum}}{{{value}}}{{#unless @last}} | {{/unless}}{{/each}}{{>isNullable}}
{{~/if~}}

View File

@ -1 +1 @@
{{{base}}}{{>isNullable}}
{{>base base}}{{>isNullable}}

View File

@ -1 +1 @@
{{{base}}}{{>isNullable}}
{{>base base}}{{>isNullable}}

View File

@ -1,12 +1,12 @@
// @ts-nocheck
import * as Handlebars from 'handlebars/runtime';
export function registerHandlebarHelpers(): void {
Handlebars.registerHelper('equals', function (a: string, b: string, options: Handlebars.HelperOptions): string {
// @ts-ignore
return a === b ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper('notEquals', function (a: string, b: string, options: Handlebars.HelperOptions): string {
// @ts-ignore
return a !== b ? options.fn(this) : options.inverse(this);
});
}

View File

@ -36,6 +36,7 @@ import templateExportModel from '../templates/exportModel.hbs';
import templateExportSchema from '../templates/exportSchema.hbs';
import templateExportService from '../templates/exportService.hbs';
import templateIndex from '../templates/index.hbs';
import partialBase from '../templates/partials/base.hbs';
import partialExportEnum from '../templates/partials/exportEnum.hbs';
import partialExportInterface from '../templates/partials/exportInterface.hbs';
import partialExportType from '../templates/partials/exportType.hbs';
@ -125,6 +126,7 @@ export function registerHandlebarTemplates(): Templates {
Handlebars.registerPartial('typeGeneric', Handlebars.template(partialTypeGeneric));
Handlebars.registerPartial('typeInterface', Handlebars.template(partialTypeInterface));
Handlebars.registerPartial('typeReference', Handlebars.template(partialTypeReference));
Handlebars.registerPartial('base', Handlebars.template(partialBase));
// Generic functions used in 'request' file @see src/templates/core/request.hbs for more info
Handlebars.registerPartial('functions/catchErrors', Handlebars.template(functionCatchErrors));

View File

@ -16,7 +16,7 @@ import { writeClientServices } from './writeClientServices';
* @param client Client object with all the models, services, etc.
* @param templates Templates wrapper with all loaded Handlebars templates
* @param output The relative location of the output directory
* @param httpClient The selected httpClient (fetch or XHR)
* @param httpClient The selected httpClient (fetch, xhr or node)
* @param useOptions Use options or arguments functions
* @param useUnionTypes Use union types instead of enums
* @param exportCore: Generate core client classes
@ -56,17 +56,17 @@ export async function writeClient(
if (exportServices) {
await mkdir(outputPathServices);
await writeClientServices(client.services, templates, outputPathServices, useUnionTypes, useOptions);
await writeClientServices(client.services, templates, outputPathServices, httpClient, useUnionTypes, useOptions);
}
if (exportSchemas) {
await mkdir(outputPathSchemas);
await writeClientSchemas(client.models, templates, outputPathSchemas, useUnionTypes);
await writeClientSchemas(client.models, templates, outputPathSchemas, httpClient, useUnionTypes);
}
if (exportModels) {
await mkdir(outputPathModels);
await writeClientModels(client.models, templates, outputPathModels, useUnionTypes);
await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes);
}
await writeClientIndex(client, templates, outputPath, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas);

View File

@ -1,6 +1,7 @@
import * as path from 'path';
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { format } from './format';
import { Templates } from './registerHandlebarTemplates';
@ -10,13 +11,15 @@ import { Templates } from './registerHandlebarTemplates';
* @param models Array of Models to write
* @param templates The loaded handlebar templates
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr or node)
* @param useUnionTypes Use union types instead of enums
*/
export async function writeClientModels(models: Model[], templates: Templates, outputPath: string, useUnionTypes: boolean): Promise<void> {
export async function writeClientModels(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
for (const model of models) {
const file = path.resolve(outputPath, `${model.name}.ts`);
const templateResult = templates.exports.model({
...model,
httpClient,
useUnionTypes,
});
await writeFile(file, format(templateResult));

View File

@ -1,6 +1,7 @@
import * as path from 'path';
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { format } from './format';
import { Templates } from './registerHandlebarTemplates';
@ -10,13 +11,15 @@ import { Templates } from './registerHandlebarTemplates';
* @param models Array of Models to write
* @param templates The loaded handlebar templates
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr or node)
* @param useUnionTypes Use union types instead of enums
*/
export async function writeClientSchemas(models: Model[], templates: Templates, outputPath: string, useUnionTypes: boolean): Promise<void> {
export async function writeClientSchemas(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
for (const model of models) {
const file = path.resolve(outputPath, `$${model.name}.ts`);
const templateResult = templates.exports.schema({
...model,
httpClient,
useUnionTypes,
});
await writeFile(file, format(templateResult));

View File

@ -1,6 +1,7 @@
import * as path from 'path';
import type { Service } from '../client/interfaces/Service';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { format } from './format';
import { Templates } from './registerHandlebarTemplates';
@ -12,15 +13,17 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
* @param services Array of Services to write
* @param templates The loaded handlebar templates
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr or node)
* @param useUnionTypes Use union types instead of enums
* @param useOptions Use options or arguments functions
*/
export async function writeClientServices(services: Service[], templates: Templates, outputPath: string, useUnionTypes: boolean, useOptions: boolean): Promise<void> {
export async function writeClientServices(services: Service[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean, useOptions: boolean): Promise<void> {
for (const service of services) {
const file = path.resolve(outputPath, `${service.name}.ts`);
const useVersion = service.operations.some(operation => operation.path.includes(VERSION_TEMPLATE_STRING));
const templateResult = templates.exports.service({
...service,
httpClient,
useUnionTypes,
useVersion,
useOptions,

View File

@ -251,7 +251,9 @@ export async function request(options: ApiRequestOptions): Promise<ApiResult> {
catchErrors(options, result);
return result;
}"
}
"
`;
exports[`v2 should generate: ./test/generated/v2/index.ts 1`] = `
@ -602,7 +604,7 @@ import type { ModelWithString } from './ModelWithString';
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
propWithFile?: Array<any>;
propWithFile?: Array<Blob>;
propWithNumber?: Array<number>;
}
"
@ -935,7 +937,7 @@ exports[`v2 should generate: ./test/generated/v2/models/SimpleFile.ts 1`] = `
/**
* This is a simple file
*/
export type SimpleFile = any;"
export type SimpleFile = Blob;"
`;
exports[`v2 should generate: ./test/generated/v2/models/SimpleInteger.ts 1`] = `
@ -1122,7 +1124,7 @@ exports[`v2 should generate: ./test/generated/v2/schemas/$ModelThatExtends.ts 1`
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { $ModelWithString } from './$ModelWithString';
import { $ModelWithString } from './$ModelWithString';
export const $ModelThatExtends = {
properties: {
@ -1141,8 +1143,8 @@ exports[`v2 should generate: ./test/generated/v2/schemas/$ModelThatExtendsExtend
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { $ModelWithString } from './$ModelWithString';
import type { $ModelThatExtends } from './$ModelThatExtends';
import { $ModelWithString } from './$ModelWithString';
import { $ModelThatExtends } from './$ModelThatExtends';
export const $ModelThatExtendsExtends = {
properties: {
@ -1481,7 +1483,7 @@ exports[`v2 should generate: ./test/generated/v2/schemas/$SimpleFile.ts 1`] = `
/* tslint:disable */
/* eslint-disable */
export const $SimpleFile = {
type: 'any',
type: 'File',
};"
`;
@ -2284,7 +2286,9 @@ export async function request(options: ApiRequestOptions): Promise<ApiResult> {
catchErrors(options, result);
return result;
}"
}
"
`;
exports[`v3 should generate: ./test/generated/v3/index.ts 1`] = `
@ -2660,7 +2664,7 @@ import type { ModelWithString } from './ModelWithString';
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
propWithFile?: Array<any>;
propWithFile?: Array<Blob>;
propWithNumber?: Array<number>;
}
"
@ -3012,7 +3016,7 @@ exports[`v3 should generate: ./test/generated/v3/models/SimpleFile.ts 1`] = `
/**
* This is a simple file
*/
export type SimpleFile = any;"
export type SimpleFile = Blob;"
`;
exports[`v3 should generate: ./test/generated/v3/models/SimpleInteger.ts 1`] = `
@ -3199,7 +3203,7 @@ exports[`v3 should generate: ./test/generated/v3/schemas/$ModelThatExtends.ts 1`
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { $ModelWithString } from './$ModelWithString';
import { $ModelWithString } from './$ModelWithString';
export const $ModelThatExtends = {
properties: {
@ -3218,8 +3222,8 @@ exports[`v3 should generate: ./test/generated/v3/schemas/$ModelThatExtendsExtend
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { $ModelWithString } from './$ModelWithString';
import type { $ModelThatExtends } from './$ModelThatExtends';
import { $ModelWithString } from './$ModelWithString';
import { $ModelThatExtends } from './$ModelThatExtends';
export const $ModelThatExtendsExtends = {
properties: {
@ -3592,7 +3596,7 @@ exports[`v3 should generate: ./test/generated/v3/schemas/$SimpleFile.ts 1`] = `
/* tslint:disable */
/* eslint-disable */
export const $SimpleFile = {
type: 'any',
type: 'File',
};"
`;
@ -4301,7 +4305,7 @@ export class UploadService {
* @throws ApiError
*/
public static async uploadFile(
file: any,
file: Blob,
): Promise<boolean> {
const result = await __request({
method: 'POST',

View File

@ -4,14 +4,14 @@ const ts = require('typescript');
const path = require('path');
const os = require('os');
function compile(dir, isBrowser) {
function compile(dir) {
const baseDir = `./test/e2e/generated/${dir}/`;
const tsconfig = {
compilerOptions: {
target: 'es6',
module: 'es6',
moduleResolution: 'node',
lib: isBrowser ? ['es6', 'dom'] : ['es6']
lib: ['es6', 'dom'],
},
include: ['./index.ts'],
};

View File

@ -11,7 +11,7 @@ describe('v2.fetch', () => {
beforeAll(async () => {
await generate('v2/fetch', 'v2', 'fetch');
await copy('v2/fetch');
compile('v2/fetch', true);
compile('v2/fetch');
await server.start('v2/fetch');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v2.xhr', () => {
beforeAll(async () => {
await generate('v2/xhr', 'v2', 'xhr');
await copy('v2/xhr');
compile('v2/xhr', true);
compile('v2/xhr');
await server.start('v2/xhr');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v3.fetch', () => {
beforeAll(async () => {
await generate('v3/fetch', 'v3', 'fetch');
await copy('v3/fetch');
compile('v3/fetch', true);
compile('v3/fetch');
await server.start('v3/fetch');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v3.xhr', () => {
beforeAll(async () => {
await generate('v3/xhr', 'v3', 'xhr');
await copy('v3/xhr');
compile('v3/xhr', true);
compile('v3/xhr');
await server.start('v3/xhr');
await browser.start();
}, 30000);

View File

@ -4,7 +4,7 @@
"target": "es6",
"module": "es6",
"moduleResolution": "node",
"lib": ["es6"],
"lib": ["es6", "dom"],
"types": ["jest", "node"],
"declaration": false,
"declarationMap": false,