- Added postfix property for service names

- Added cross blob for node js implementations
- Fixed unittests
- Fixed typing issue with headers
This commit is contained in:
Ferdi Koomen 2021-11-10 14:31:03 +01:00
parent 3b6716a018
commit aecdc3eb15
38 changed files with 5975 additions and 5884 deletions

View File

@ -8,4 +8,7 @@ updates:
open-pull-requests-limit: 10
ignore:
- dependency-name: "@types/node-fetch"
- dependency-name: "abort-controller"
- dependency-name: "cross-blob"
- dependency-name: "form-data"
- dependency-name: "node-fetch"

View File

@ -47,6 +47,7 @@ $ openapi --help
--exportServices <value> Write services to disk (default: true)
--exportModels <value> Write models to disk (default: true)
--exportSchemas <value> Write schemas to disk (default: false)
--postfix <value> Service name postfix (default: "Service")
--request <value> Path to custom request file
-h, --help display help for command
@ -459,10 +460,11 @@ This will generate a client that uses [`node-fetch`](https://www.npmjs.com/packa
in order to compile and run this client, you might need to install the `node-fetch@2.x` dependencies:
```
npm install @types/node-fetch --save-dev
npm install abort-controller --save-dev
npm install node-fetch --save-dev
npm install form-data --save-dev
npm install @types/node-fetch@2.x --save-dev
npm install abort-controller@3.x --save-dev
npm install cross-blob@2.x --save-dev
npm install form-data@4.x --save-dev
npm install node-fetch@2.x --save-dev
```
In order to compile the project and resolve the imports, you will need to enable the `allowSyntheticDefaultImports`

View File

@ -19,6 +19,7 @@ const params = program
.option('--exportServices <value>', 'Write services to disk', true)
.option('--exportModels <value>', 'Write models to disk', true)
.option('--exportSchemas <value>', 'Write schemas to disk', false)
.option('--postfix <value>', 'Service name postfix', 'Service')
.option('--request <value>', 'Path to custom request file')
.parse(process.argv)
.opts();
@ -36,6 +37,7 @@ if (OpenAPI) {
exportServices: JSON.parse(params.exportServices) === true,
exportModels: JSON.parse(params.exportModels) === true,
exportSchemas: JSON.parse(params.exportSchemas) === true,
postfix: params.postfix,
request: params.request,
})
.then(() => {

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.11.8",
"version": "0.12.0-alpha",
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
@ -65,6 +65,7 @@
"axios": "^0.24.0",
"camelcase": "^6.2.0",
"commander": "^8.3.0",
"cross-blob": "^2.0.1",
"form-data": "^4.0.0",
"handlebars": "^4.7.6",
"json-schema-ref-parser": "^9.0.7",
@ -82,10 +83,10 @@
"@types/express": "4.17.13",
"@types/glob": "7.2.0",
"@types/jest": "27.0.2",
"@types/node": "16.11.6",
"@types/node": "16.11.7",
"@types/qs": "6.9.7",
"@typescript-eslint/eslint-plugin": "5.3.0",
"@typescript-eslint/parser": "5.3.0",
"@typescript-eslint/eslint-plugin": "5.3.1",
"@typescript-eslint/parser": "5.3.1",
"codecov": "3.8.3",
"eslint": "8.2.0",
"eslint-config-prettier": "8.3.0",

View File

@ -20,6 +20,7 @@ export type Options = {
exportServices?: boolean;
exportModels?: boolean;
exportSchemas?: boolean;
postfix?: string;
request?: string;
write?: boolean;
};
@ -37,6 +38,7 @@ export type Options = {
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param postfix: Service name postfix
* @param request: Path to custom request file
* @param write Write the files to disk (true or false)
*/
@ -50,6 +52,7 @@ export async function generate({
exportServices = true,
exportModels = true,
exportSchemas = false,
postfix = 'Service',
request,
write = true,
}: Options): Promise<void> {
@ -77,6 +80,7 @@ export async function generate({
exportServices,
exportModels,
exportSchemas,
postfix,
request
);
break;
@ -97,6 +101,7 @@ export async function generate({
exportServices,
exportModels,
exportSchemas,
postfix,
request
);
break;

View File

@ -1,22 +1,22 @@
import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('binary');
expect(getMappedType('file')).toEqual('binary');
expect(getMappedType('string')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');
expect(getMappedType('float')).toEqual('number');
expect(getMappedType('double')).toEqual('number');
expect(getMappedType('short')).toEqual('number');
expect(getMappedType('int')).toEqual('number');
expect(getMappedType('boolean')).toEqual('boolean');
expect(getMappedType('any')).toEqual('any');
expect(getMappedType('object')).toEqual('any');
expect(getMappedType('void')).toEqual('void');
expect(getMappedType('null')).toEqual('null');
expect(getMappedType('unknown')).toEqual(undefined);
expect(getMappedType('')).toEqual(undefined);
});
});
import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('binary');
expect(getMappedType('file')).toEqual('binary');
expect(getMappedType('string')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');
expect(getMappedType('float')).toEqual('number');
expect(getMappedType('double')).toEqual('number');
expect(getMappedType('short')).toEqual('number');
expect(getMappedType('int')).toEqual('number');
expect(getMappedType('boolean')).toEqual('boolean');
expect(getMappedType('any')).toEqual('any');
expect(getMappedType('object')).toEqual('any');
expect(getMappedType('void')).toEqual('void');
expect(getMappedType('null')).toEqual('null');
expect(getMappedType('unknown')).toEqual(undefined);
expect(getMappedType('')).toEqual(undefined);
});
});

View File

@ -1,33 +1,33 @@
const TYPE_MAPPINGS = new Map<string, string>([
['File', 'binary'],
['file', 'binary'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string, format?: string): string | undefined {
if (format === 'binary') {
return 'binary';
}
return TYPE_MAPPINGS.get(type);
}
const TYPE_MAPPINGS = new Map<string, string>([
['File', 'binary'],
['file', 'binary'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string, format?: string): string | undefined {
if (format === 'binary') {
return 'binary';
}
return TYPE_MAPPINGS.get(type);
}

View File

@ -3,14 +3,11 @@ import { getServiceClassName } from './getServiceClassName';
describe('getServiceClassName', () => {
it('should produce correct result', () => {
expect(getServiceClassName('')).toEqual('');
expect(getServiceClassName('FooBar')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar')).toEqual('FooBarService');
expect(getServiceClassName('foo bar')).toEqual('FooBarService');
expect(getServiceClassName('FooBarService')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar Service')).toEqual('FooBarService');
expect(getServiceClassName('foo bar service')).toEqual('FooBarService');
expect(getServiceClassName('@fooBar')).toEqual('FooBarService');
expect(getServiceClassName('$fooBar')).toEqual('FooBarService');
expect(getServiceClassName('123fooBar')).toEqual('FooBarService');
expect(getServiceClassName('FooBar')).toEqual('FooBar');
expect(getServiceClassName('Foo Bar')).toEqual('FooBar');
expect(getServiceClassName('foo bar')).toEqual('FooBar');
expect(getServiceClassName('@fooBar')).toEqual('FooBar');
expect(getServiceClassName('$fooBar')).toEqual('FooBar');
expect(getServiceClassName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -2,16 +2,12 @@ import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* the input string to PascalCase and appends the "Service" prefix if needed.
* the input string to PascalCase.
*/
export function getServiceClassName(value: string): string {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
const name = camelCase(clean, { pascalCase: true });
if (name && !name.endsWith('Service')) {
return `${name}Service`;
}
return name;
return camelCase(clean, { pascalCase: true });
}

View File

@ -1,22 +1,22 @@
import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('binary');
expect(getMappedType('file')).toEqual('binary');
expect(getMappedType('string')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');
expect(getMappedType('float')).toEqual('number');
expect(getMappedType('double')).toEqual('number');
expect(getMappedType('short')).toEqual('number');
expect(getMappedType('int')).toEqual('number');
expect(getMappedType('boolean')).toEqual('boolean');
expect(getMappedType('any')).toEqual('any');
expect(getMappedType('object')).toEqual('any');
expect(getMappedType('void')).toEqual('void');
expect(getMappedType('null')).toEqual('null');
expect(getMappedType('unknown')).toEqual(undefined);
expect(getMappedType('')).toEqual(undefined);
});
});
import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('binary');
expect(getMappedType('file')).toEqual('binary');
expect(getMappedType('string')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');
expect(getMappedType('float')).toEqual('number');
expect(getMappedType('double')).toEqual('number');
expect(getMappedType('short')).toEqual('number');
expect(getMappedType('int')).toEqual('number');
expect(getMappedType('boolean')).toEqual('boolean');
expect(getMappedType('any')).toEqual('any');
expect(getMappedType('object')).toEqual('any');
expect(getMappedType('void')).toEqual('void');
expect(getMappedType('null')).toEqual('null');
expect(getMappedType('unknown')).toEqual(undefined);
expect(getMappedType('')).toEqual(undefined);
});
});

View File

@ -1,33 +1,33 @@
const TYPE_MAPPINGS = new Map<string, string>([
['File', 'binary'],
['file', 'binary'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string, format?: string): string | undefined {
if (format === 'binary') {
return 'binary';
}
return TYPE_MAPPINGS.get(type);
}
const TYPE_MAPPINGS = new Map<string, string>([
['File', 'binary'],
['file', 'binary'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string, format?: string): string | undefined {
if (format === 'binary') {
return 'binary';
}
return TYPE_MAPPINGS.get(type);
}

View File

@ -3,14 +3,11 @@ import { getServiceClassName } from './getServiceClassName';
describe('getServiceClassName', () => {
it('should produce correct result', () => {
expect(getServiceClassName('')).toEqual('');
expect(getServiceClassName('FooBar')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar')).toEqual('FooBarService');
expect(getServiceClassName('foo bar')).toEqual('FooBarService');
expect(getServiceClassName('FooBarService')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar Service')).toEqual('FooBarService');
expect(getServiceClassName('foo bar service')).toEqual('FooBarService');
expect(getServiceClassName('@fooBar')).toEqual('FooBarService');
expect(getServiceClassName('$fooBar')).toEqual('FooBarService');
expect(getServiceClassName('123fooBar')).toEqual('FooBarService');
expect(getServiceClassName('FooBar')).toEqual('FooBar');
expect(getServiceClassName('Foo Bar')).toEqual('FooBar');
expect(getServiceClassName('foo bar')).toEqual('FooBar');
expect(getServiceClassName('@fooBar')).toEqual('FooBar');
expect(getServiceClassName('$fooBar')).toEqual('FooBar');
expect(getServiceClassName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -2,16 +2,12 @@ import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* the input string to PascalCase and appends the "Service" prefix if needed.
* the input string to PascalCase.
*/
export function getServiceClassName(value: string): string {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
const name = camelCase(clean, { pascalCase: true });
if (name && !name.endsWith('Service')) {
return `${name}Service`;
}
return name;
return camelCase(clean, { pascalCase: true });
}

View File

@ -14,7 +14,7 @@ async function getHeaders(options: ApiRequestOptions, formData?: FormData): Prom
.filter(([_, value]) => isDefined(value))
.reduce((headers, [key, value]) => ({
...headers,
[key]: value,
[key]: String(value),
}), {} as Record<string, string>);
if (isStringWithValue(token)) {

View File

@ -1,6 +1,7 @@
{{>header}}
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import Blob from 'cross-blob'
import FormData from 'form-data';
import { ApiError } from './ApiError';
@ -19,6 +20,9 @@ import { OpenAPI } from './OpenAPI';
{{>functions/isStringWithValue}}
{{>functions/isBlob}}
{{>functions/isSuccess}}

View File

@ -12,8 +12,8 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
.filter(([_, value]) => isDefined(value))
.reduce((headers, [key, value]) => ({
...headers,
[key]: value,
}), {});
[key]: String(value),
}), {} as Record<string, string>);
const headers = new Headers(defaultHeaders);

View File

@ -2,12 +2,15 @@ function getFormData(options: ApiRequestOptions): FormData | undefined {
if (options.formData) {
const formData = new FormData();
Object.keys(options.formData).forEach(key => {
const value = options.formData?.[key];
if (isDefined(value)) {
formData.append(key, value);
}
});
Object.entries(options.formData)
.filter(([_, value]) => isDefined(value))
.forEach(([key, value]) => {
if (isString(value) || isBlob(value)) {
formData.append(key, value);
} else {
formData.append(key, JSON.stringify(value));
}
});
return formData;
}

View File

@ -1,6 +0,0 @@
function isBinary(value: any): value is Buffer | ArrayBuffer | ArrayBufferView {
const isBuffer = Buffer.isBuffer(value);
const isArrayBuffer = types.isArrayBuffer(value);
const isArrayBufferView = types.isArrayBufferView(value);
return isBuffer || isArrayBuffer || isArrayBufferView;
}

View File

@ -12,8 +12,8 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
.filter(([_, value]) => isDefined(value))
.reduce((headers, [key, value]) => ({
...headers,
[key]: value,
}), {});
[key]: String(value),
}), {} as Record<string, string>);
const headers = new Headers(defaultHeaders);
@ -29,7 +29,7 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
if (options.body) {
if (options.mediaType) {
headers.append('Content-Type', options.mediaType);
} else if (isBinary(options.body)) {
} else if (isBlob(options.body)) {
headers.append('Content-Type', 'application/octet-stream');
} else if (isString(options.body)) {
headers.append('Content-Type', 'text/plain');

View File

@ -2,7 +2,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
if (options.body) {
if (options.mediaType?.includes('/json')) {
return JSON.stringify(options.body)
} else if (isString(options.body) || isBlob(options.body) || isBinary(options.body)) {
} else if (isString(options.body) || isBlob(options.body)) {
return options.body as any;
} else {
return JSON.stringify(options.body);

View File

@ -1,9 +1,9 @@
{{>header}}
import { AbortController } from 'abort-controller';
import Blob from 'cross-blob'
import FormData from 'form-data';
import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch';
import { types } from 'util';
import { ApiError } from './ApiError';
import type { ApiRequestOptions } from './ApiRequestOptions';
@ -24,9 +24,6 @@ import { OpenAPI } from './OpenAPI';
{{>functions/isBlob}}
{{>functions/isBinary}}
{{>functions/base64}}

View File

@ -12,8 +12,8 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
.filter(([_, value]) => isDefined(value))
.reduce((headers, [key, value]) => ({
...headers,
[key]: value,
}), {});
[key]: String(value),
}), {} as Record<string, string>);
const headers = new Headers(defaultHeaders);

View File

@ -1,3 +1,3 @@
{{>header}}
export const ${{{name}}} = {{>schema}};
export const ${{{name}}} = {{>schema}} as const;

View File

@ -11,7 +11,7 @@ import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
{{/if}}
export class {{{name}}} {
export class {{{name}}}{{{@root.postfix}}} {
{{#each operations}}
/**

View File

@ -33,7 +33,7 @@ export { ${{{name}}} } from './schemas/${{{name}}}';
{{#if services}}
{{#each services}}
export { {{{name}}} } from './services/{{{name}}}';
export { {{{name}}}{{{@root.postfix}}} } from './services/{{{name}}}{{{@root.postfix}}}';
{{/each}}
{{/if}}
{{/if}}

View File

@ -1,8 +1,8 @@
{{~#equals base 'binary'~}}
{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'axios'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'node'}}Buffer | ArrayBuffer | ArrayBufferView{{/equals~}}
{{~else~}}
{{{base}}}
{{~/equals~}}
{{~#equals base 'binary'~}}
{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'axios'}}Blob{{/equals~}}
{{~#equals @root.httpClient 'node'}}Blob{{/equals~}}
{{~else~}}
{{{base}}}
{{~/equals~}}

View File

@ -22,7 +22,6 @@ import functionCatchErrors from '../templates/core/functions/catchErrors.hbs';
import functionGetFormData from '../templates/core/functions/getFormData.hbs';
import functionGetQueryString from '../templates/core/functions/getQueryString.hbs';
import functionGetUrl from '../templates/core/functions/getUrl.hbs';
import functionIsBinary from '../templates/core/functions/isBinary.hbs';
import functionIsBlob from '../templates/core/functions/isBlob.hbs';
import functionIsDefined from '../templates/core/functions/isDefined.hbs';
import functionIsString from '../templates/core/functions/isString.hbs';
@ -156,7 +155,6 @@ export function registerHandlebarTemplates(root: {
Handlebars.registerPartial('functions/getFormData', Handlebars.template(functionGetFormData));
Handlebars.registerPartial('functions/getQueryString', Handlebars.template(functionGetQueryString));
Handlebars.registerPartial('functions/getUrl', Handlebars.template(functionGetUrl));
Handlebars.registerPartial('functions/isBinary', Handlebars.template(functionIsBinary));
Handlebars.registerPartial('functions/isBlob', Handlebars.template(functionIsBlob));
Handlebars.registerPartial('functions/isDefined', Handlebars.template(functionIsDefined));
Handlebars.registerPartial('functions/isString', Handlebars.template(functionIsString));

View File

@ -23,6 +23,8 @@ import { writeClientServices } from './writeClientServices';
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param exportSchemas: Generate schemas
* @param postfix: Service name postfix
* @param request: Path to custom request file
*/
export async function writeClient(
@ -36,6 +38,7 @@ export async function writeClient(
exportServices: boolean,
exportModels: boolean,
exportSchemas: boolean,
postfix: string,
request?: string
): Promise<void> {
const outputPath = resolve(process.cwd(), output);
@ -63,7 +66,8 @@ export async function writeClient(
outputPathServices,
httpClient,
useUnionTypes,
useOptions
useOptions,
postfix
);
}
@ -89,7 +93,8 @@ export async function writeClient(
exportCore,
exportServices,
exportModels,
exportSchemas
exportSchemas,
postfix
);
}
}

View File

@ -26,11 +26,12 @@ describe('writeClientIndex', () => {
apiError: () => 'apiError',
apiRequestOptions: () => 'apiRequestOptions',
apiResult: () => 'apiResult',
cancelablePromise: () => 'cancelablePromise',
request: () => 'request',
},
};
await writeClientIndex(client, templates, '/', true, true, true, true, true);
await writeClientIndex(client, templates, '/', true, true, true, true, true, 'Service');
expect(writeFile).toBeCalledWith('/index.ts', 'index');
});

View File

@ -18,6 +18,7 @@ import { sortServicesByName } from './sortServicesByName';
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param postfix: Service name postfix
*/
export async function writeClientIndex(
client: Client,
@ -27,7 +28,8 @@ export async function writeClientIndex(
exportCore: boolean,
exportServices: boolean,
exportModels: boolean,
exportSchemas: boolean
exportSchemas: boolean,
postfix: string
): Promise<void> {
await writeFile(
resolve(outputPath, 'index.ts'),
@ -37,6 +39,7 @@ export async function writeClientIndex(
exportModels,
exportSchemas,
useUnionTypes,
postfix,
server: client.server,
version: client.version,
models: sortModelsByName(client.models),

View File

@ -11,9 +11,9 @@ describe('writeClientModels', () => {
const models: Model[] = [
{
export: 'interface',
name: 'MyModel',
type: 'MyModel',
base: 'MyModel',
name: 'User',
type: 'User',
base: 'User',
template: null,
link: null,
description: null,
@ -40,12 +40,13 @@ describe('writeClientModels', () => {
apiError: () => 'apiError',
apiRequestOptions: () => 'apiRequestOptions',
apiResult: () => 'apiResult',
cancelablePromise: () => 'cancelablePromise',
request: () => 'request',
},
};
await writeClientModels(models, templates, '/', HttpClient.FETCH, false);
expect(writeFile).toBeCalledWith('/MyModel.ts', 'model');
expect(writeFile).toBeCalledWith('/User.ts', 'model');
});
});

View File

@ -11,9 +11,9 @@ describe('writeClientSchemas', () => {
const models: Model[] = [
{
export: 'interface',
name: 'MyModel',
type: 'MyModel',
base: 'MyModel',
name: 'User',
type: 'User',
base: 'User',
template: null,
link: null,
description: null,
@ -40,12 +40,13 @@ describe('writeClientSchemas', () => {
apiError: () => 'apiError',
apiRequestOptions: () => 'apiRequestOptions',
apiResult: () => 'apiResult',
cancelablePromise: () => 'cancelablePromise',
request: () => 'request',
},
};
await writeClientSchemas(models, templates, '/', HttpClient.FETCH, false);
expect(writeFile).toBeCalledWith('/$MyModel.ts', 'schema');
expect(writeFile).toBeCalledWith('/$User.ts', 'schema');
});
});

View File

@ -10,7 +10,7 @@ describe('writeClientServices', () => {
it('should write to filesystem', async () => {
const services: Service[] = [
{
name: 'MyService',
name: 'User',
operations: [],
imports: [],
},
@ -28,12 +28,13 @@ describe('writeClientServices', () => {
apiError: () => 'apiError',
apiRequestOptions: () => 'apiRequestOptions',
apiResult: () => 'apiResult',
cancelablePromise: () => 'cancelablePromise',
request: () => 'request',
},
};
await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false);
await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false, 'Service');
expect(writeFile).toBeCalledWith('/MyService.ts', 'service');
expect(writeFile).toBeCalledWith('/UserService.ts', 'service');
});
});

View File

@ -16,6 +16,7 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
* @param useOptions Use options or arguments functions
* @param postfix: Service name postfix
*/
export async function writeClientServices(
services: Service[],
@ -23,10 +24,11 @@ export async function writeClientServices(
outputPath: string,
httpClient: HttpClient,
useUnionTypes: boolean,
useOptions: boolean
useOptions: boolean,
postfix: string
): Promise<void> {
for (const service of services) {
const file = resolve(outputPath, `${service.name}.ts`);
const file = resolve(outputPath, `${service.name}${postfix}.ts`);
const useVersion = service.operations.some(operation => operation.path.includes(VERSION_TEMPLATE_STRING));
const templateResult = templates.exports.service({
...service,
@ -34,6 +36,7 @@ export async function writeClientServices(
useUnionTypes,
useVersion,
useOptions,
postfix,
});
await writeFile(file, format(templateResult));
}

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ async function generate(input, output) {
exportSchemas: true,
exportModels: true,
exportServices: true,
// postfix: 'Api',
// request: './test/custom/request.ts',
});
}

View File

@ -1083,6 +1083,43 @@
}
},
"/api/v{api-version}/multipart": {
"post": {
"tags": [
"multipart"
],
"operationId": "MultipartRequest",
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"content": {
"type": "string",
"format": "binary"
},
"data": {
"oneOf": [
{
"$ref": "#/components/schemas/ModelWithString"
}
],
"nullable": true
}
}
},
"encoding": {
"content": {
"style": "form"
},
"data": {
"style": "form"
}
}
}
}
}
},
"get": {
"tags": [
"multipart"

108
yarn.lock
View File

@ -1626,10 +1626,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97"
integrity sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==
"@types/node@16.11.6":
version "16.11.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
"@types/node@16.11.7":
version "16.11.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42"
integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==
"@types/prettier@^2.1.5":
version "2.4.1"
@ -1685,13 +1685,13 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz#a55ae72d28ffeb6badd817fe4566c9cced1f5e29"
integrity sha512-ARUEJHJrq85aaiCqez7SANeahDsJTD3AEua34EoQN9pHS6S5Bq9emcIaGGySt/4X2zSi+vF5hAH52sEen7IO7g==
"@typescript-eslint/eslint-plugin@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3"
integrity sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw==
dependencies:
"@typescript-eslint/experimental-utils" "5.3.0"
"@typescript-eslint/scope-manager" "5.3.0"
"@typescript-eslint/experimental-utils" "5.3.1"
"@typescript-eslint/scope-manager" "5.3.1"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
@ -1699,60 +1699,60 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/experimental-utils@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.0.tgz#ee56b4957547ed2b0fc7451205e41502e664f546"
integrity sha512-NFVxYTjKj69qB0FM+piah1x3G/63WB8vCBMnlnEHUsiLzXSTWb9FmFn36FD9Zb4APKBLY3xRArOGSMQkuzTF1w==
"@typescript-eslint/experimental-utils@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz#bbd8f9b67b4d5fdcb9d2f90297d8fcda22561e05"
integrity sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.3.0"
"@typescript-eslint/types" "5.3.0"
"@typescript-eslint/typescript-estree" "5.3.0"
"@typescript-eslint/scope-manager" "5.3.1"
"@typescript-eslint/types" "5.3.1"
"@typescript-eslint/typescript-estree" "5.3.1"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/parser@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.3.0.tgz#7879f15e26d370ed3f653fb7dd06479531ed3ab9"
integrity sha512-rKu/yAReip7ovx8UwOAszJVO5MgBquo8WjIQcp1gx4pYQCwYzag+I5nVNHO4MqyMkAo0gWt2gWUi+36gWAVKcw==
"@typescript-eslint/parser@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.3.1.tgz#8ff1977c3d3200c217b3e4628d43ef92f89e5261"
integrity sha512-TD+ONlx5c+Qhk21x9gsJAMRohWAUMavSOmJgv3JGy9dgPhuBd5Wok0lmMClZDyJNLLZK1JRKiATzCKZNUmoyfw==
dependencies:
"@typescript-eslint/scope-manager" "5.3.0"
"@typescript-eslint/types" "5.3.0"
"@typescript-eslint/typescript-estree" "5.3.0"
"@typescript-eslint/scope-manager" "5.3.1"
"@typescript-eslint/types" "5.3.1"
"@typescript-eslint/typescript-estree" "5.3.1"
debug "^4.3.2"
"@typescript-eslint/scope-manager@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.0.tgz#97d0ccc7c9158e89e202d5e24ce6ba49052d432e"
integrity sha512-22Uic9oRlTsPppy5Tcwfj+QET5RWEnZ5414Prby465XxQrQFZ6nnm5KnXgnsAJefG4hEgMnaxTB3kNEyjdjj6A==
"@typescript-eslint/scope-manager@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz#3cfbfbcf5488fb2a9a6fbbe97963ee1e8d419269"
integrity sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg==
dependencies:
"@typescript-eslint/types" "5.3.0"
"@typescript-eslint/visitor-keys" "5.3.0"
"@typescript-eslint/types" "5.3.1"
"@typescript-eslint/visitor-keys" "5.3.1"
"@typescript-eslint/types@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.0.tgz#af29fd53867c2df0028c57c36a655bd7e9e05416"
integrity sha512-fce5pG41/w8O6ahQEhXmMV+xuh4+GayzqEogN24EK+vECA3I6pUwKuLi5QbXO721EMitpQne5VKXofPonYlAQg==
"@typescript-eslint/types@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.1.tgz#afaa715b69ebfcfde3af8b0403bf27527912f9b7"
integrity sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ==
"@typescript-eslint/typescript-estree@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.0.tgz#4f68ddd46dc2983182402d2ab21fb44ad94988cf"
integrity sha512-FJ0nqcaUOpn/6Z4Jwbtf+o0valjBLkqc3MWkMvrhA2TvzFXtcclIM8F4MBEmYa2kgcI8EZeSAzwoSrIC8JYkug==
"@typescript-eslint/typescript-estree@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz#50cc4bfb93dc31bc75e08ae52e29fcb786d606ec"
integrity sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ==
dependencies:
"@typescript-eslint/types" "5.3.0"
"@typescript-eslint/visitor-keys" "5.3.0"
"@typescript-eslint/types" "5.3.1"
"@typescript-eslint/visitor-keys" "5.3.1"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/visitor-keys@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.0.tgz#a6258790f3b7b2547f70ed8d4a1e0c3499994523"
integrity sha512-oVIAfIQuq0x2TFDNLVavUn548WL+7hdhxYn+9j3YdJJXB7mH9dAmZNJsPDa7Jc+B9WGqoiex7GUDbyMxV0a/aw==
"@typescript-eslint/visitor-keys@5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz#c2860ff22939352db4f3806f34b21d8ad00588ba"
integrity sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ==
dependencies:
"@typescript-eslint/types" "5.3.0"
"@typescript-eslint/types" "5.3.1"
eslint-visitor-keys "^3.0.0"
abab@^2.0.3, abab@^2.0.5:
@ -2019,6 +2019,11 @@ bl@^4.0.3:
inherits "^2.0.4"
readable-stream "^3.4.0"
blob-polyfill@^5.0.20210201:
version "5.0.20210201"
resolved "https://registry.yarnpkg.com/blob-polyfill/-/blob-polyfill-5.0.20210201.tgz#0024bfa5dcc3440eb5a2f1e5991cb1612a558465"
integrity sha512-SrH6IG6aXL9pCgSysBCiDpGcAJ1j6/c1qCwR3sTEQJhb+MTk6FITNA6eW6WNYQDNZVi4Z9GjxH5v2MMTv59CrQ==
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@ -2325,6 +2330,14 @@ core-js-compat@^3.18.0, core-js-compat@^3.19.0:
browserslist "^4.17.5"
semver "7.0.0"
cross-blob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/cross-blob/-/cross-blob-2.0.1.tgz#7c187282e0855353705ed9ac5af2ee7f27c910c6"
integrity sha512-ARuKPPo3I6DSqizal4UCyMCiGPQdMpMJS3Owx6Lleuh26vSt2UnfWRwbMLCYqbJUrcol+KzGVSLR91ezSHP80A==
dependencies:
blob-polyfill "^5.0.20210201"
fetch-blob "^2.1.2"
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -2828,6 +2841,11 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
fetch-blob@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c"
integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"