diff --git a/src/templates/core/ApiResult.hbs b/src/templates/core/ApiResult.hbs index a768b8c5a..8667f59e8 100644 --- a/src/templates/core/ApiResult.hbs +++ b/src/templates/core/ApiResult.hbs @@ -1,9 +1,9 @@ {{>header}} -export type ApiResult = { - readonly url: string; +export type ApiResult = { + readonly body: TData; readonly ok: boolean; readonly status: number; readonly statusText: string; - readonly body: any; + readonly url: string; }; diff --git a/src/templates/core/types.hbs b/src/templates/core/types.hbs new file mode 100644 index 000000000..88298d007 --- /dev/null +++ b/src/templates/core/types.hbs @@ -0,0 +1,14 @@ +{{>header}} + +import type { ApiResult } from './ApiResult'; + +export type TResult = 'body' | 'raw'; + +export type TApiResponse = + Exclude extends never + ? ApiResult + : ApiResult['body']; + +export type TConfig = { + _result?: T; +}; diff --git a/src/templates/exportService.hbs b/src/templates/exportService.hbs index 1b1a94637..46b978ab8 100644 --- a/src/templates/exportService.hbs +++ b/src/templates/exportService.hbs @@ -29,6 +29,9 @@ import type { BaseHttpRequest } from '../core/BaseHttpRequest'; {{else}} import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; +{{#if @root.useOptions}} +import type { TApiResponse, TConfig, TResult } from '../core/types'; +{{/if}} {{/if}} {{#if @root.useOptions}} @@ -84,21 +87,21 @@ export class {{{name}}}{{{@root.postfix}}} { */ {{#if @root.exportClient}} {{#equals @root.httpClient 'angular'}} - public {{{name}}}({{>parameters}}): Observable<{{>result}}> { + public {{{name}}}{{>operationTypes}}({{>operationParameters}}): Observable<{{>operationResult}}> { {{>destructureData}} return this.httpRequest.request({ {{else}} - public {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> { + public {{{name}}}{{>operationTypes}}({{>operationParameters}}): CancelablePromise<{{>operationResult}}> { {{>destructureData}} return this.httpRequest.request({ {{/equals}} {{else}} {{#equals @root.httpClient 'angular'}} - public {{{name}}}({{>parameters}}): Observable<{{>result}}> { + public {{{name}}}{{>operationTypes}}({{>operationParameters}}): Observable<{{>operationResult}}> { {{>destructureData}} return __request(OpenAPI, this.http, { {{else}} - public static {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> { + public static {{{name}}}{{>operationTypes}}({{>operationParameters}}): CancelablePromise<{{>operationResult}}> { {{>destructureData}} return __request(OpenAPI, { {{/equals}} diff --git a/src/templates/partials/parameters.hbs b/src/templates/partials/operationParameters.hbs similarity index 54% rename from src/templates/partials/parameters.hbs rename to src/templates/partials/operationParameters.hbs index 7ee8d7cc2..29754ee43 100644 --- a/src/templates/partials/parameters.hbs +++ b/src/templates/partials/operationParameters.hbs @@ -1,7 +1,7 @@ -{{#if parameters}} {{#if @root.useOptions~}} -data: {{{nameOperationDataType name}}}{{#ifOperationDataOptional parameters}} = {}{{/ifOperationDataOptional}} +data: {{#if parameters}}{{{nameOperationDataType name}}} & {{/if}}TConfig{{#ifOperationDataOptional parameters}} = {}{{/ifOperationDataOptional}} {{~else}} +{{#if parameters}} {{#each parameters}} {{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}}, diff --git a/src/templates/partials/result.hbs b/src/templates/partials/operationResult.hbs similarity index 55% rename from src/templates/partials/result.hbs rename to src/templates/partials/operationResult.hbs index c8f4770fd..876eacdf1 100644 --- a/src/templates/partials/result.hbs +++ b/src/templates/partials/operationResult.hbs @@ -1,5 +1,10 @@ +{{~#if @root.useOptions~}} +TApiResponsetype}}{{#unless @last}} | {{/unless}}{{/each}} {{~else~}} void {{~/if~}} +{{~#if @root.useOptions~}} +> +{{~/if~}} \ No newline at end of file diff --git a/src/templates/partials/operationTypes.hbs b/src/templates/partials/operationTypes.hbs new file mode 100644 index 000000000..765826190 --- /dev/null +++ b/src/templates/partials/operationTypes.hbs @@ -0,0 +1,3 @@ +{{#if @root.useOptions~}} + +{{~/if}} diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index f99dab8fb..0cbf92747 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -45,6 +45,7 @@ import nodeRequest from '../templates/core/node/request.hbs'; import nodeSendRequest from '../templates/core/node/sendRequest.hbs'; import templateCoreSettings from '../templates/core/OpenAPI.hbs'; import templateCoreRequest from '../templates/core/request.hbs'; +import templateCoreTypes from '../templates/core/types.hbs'; import xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs'; import xhrGetRequestBody from '../templates/core/xhr/getRequestBody.hbs'; import xhrGetResponseBody from '../templates/core/xhr/getResponseBody.hbs'; @@ -65,8 +66,9 @@ import partialHeader from '../templates/partials/header.hbs'; import partialIsNullable from '../templates/partials/isNullable.hbs'; import partialIsReadOnly from '../templates/partials/isReadOnly.hbs'; import partialIsRequired from '../templates/partials/isRequired.hbs'; -import partialParameters from '../templates/partials/parameters.hbs'; -import partialResult from '../templates/partials/result.hbs'; +import partialOperationParameters from '../templates/partials/operationParameters.hbs'; +import partialOperationResult from '../templates/partials/operationResult.hbs'; +import partialOperationTypes from '../templates/partials/operationTypes.hbs'; import partialSchema from '../templates/partials/schema.hbs'; import partialSchemaArray from '../templates/partials/schemaArray.hbs'; import partialSchemaComposition from '../templates/partials/schemaComposition.hbs'; @@ -96,6 +98,7 @@ export interface Templates { httpRequest: Handlebars.TemplateDelegate; request: Handlebars.TemplateDelegate; settings: Handlebars.TemplateDelegate; + types: Handlebars.TemplateDelegate; }; exports: { model: Handlebars.TemplateDelegate; @@ -126,6 +129,7 @@ export const registerHandlebarTemplates = ( httpRequest: Handlebars.template(templateCoreHttpRequest), request: Handlebars.template(templateCoreRequest), settings: Handlebars.template(templateCoreSettings), + types: Handlebars.template(templateCoreTypes), }, exports: { model: Handlebars.template(templateExportModel), @@ -146,8 +150,9 @@ export const registerHandlebarTemplates = ( Handlebars.registerPartial('isNullable', Handlebars.template(partialIsNullable)); Handlebars.registerPartial('isReadOnly', Handlebars.template(partialIsReadOnly)); Handlebars.registerPartial('isRequired', Handlebars.template(partialIsRequired)); - Handlebars.registerPartial('parameters', Handlebars.template(partialParameters)); - Handlebars.registerPartial('result', Handlebars.template(partialResult)); + Handlebars.registerPartial('operationParameters', Handlebars.template(partialOperationParameters)); + Handlebars.registerPartial('operationResult', Handlebars.template(partialOperationResult)); + Handlebars.registerPartial('operationTypes', Handlebars.template(partialOperationTypes)); Handlebars.registerPartial('schema', Handlebars.template(partialSchema)); Handlebars.registerPartial('schemaArray', Handlebars.template(partialSchemaArray)); Handlebars.registerPartial('schemaComposition', Handlebars.template(partialSchemaComposition)); diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index c9cd85afd..77948efcf 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -17,23 +17,24 @@ describe('writeClient', () => { }; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClient(client, templates, { diff --git a/src/utils/writeClientClass.spec.ts b/src/utils/writeClientClass.spec.ts index 102f2eb57..72e7f2800 100644 --- a/src/utils/writeClientClass.spec.ts +++ b/src/utils/writeClientClass.spec.ts @@ -17,23 +17,24 @@ describe('writeClientClass', () => { }; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientClass(client, templates, './dist', HttpClient.FETCH, 'AppClient', Indent.SPACE_4, ''); diff --git a/src/utils/writeClientCore.spec.ts b/src/utils/writeClientCore.spec.ts index 7db71f59b..e43dcad9b 100644 --- a/src/utils/writeClientCore.spec.ts +++ b/src/utils/writeClientCore.spec.ts @@ -20,23 +20,24 @@ describe('writeClientCore', () => { }; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientCore(client, templates, '/', HttpClient.FETCH, Indent.SPACE_4); @@ -47,5 +48,6 @@ describe('writeClientCore', () => { expect(writeFile).toBeCalledWith(resolve('/', '/ApiResult.ts'), `apiResult${EOL}`); expect(writeFile).toBeCalledWith(resolve('/', '/CancelablePromise.ts'), `cancelablePromise${EOL}`); expect(writeFile).toBeCalledWith(resolve('/', '/request.ts'), `request${EOL}`); + expect(writeFile).toBeCalledWith(resolve('/', '/types.ts'), `types${EOL}`); }); }); diff --git a/src/utils/writeClientCore.ts b/src/utils/writeClientCore.ts index 509a83b1a..15de51ff3 100644 --- a/src/utils/writeClientCore.ts +++ b/src/utils/writeClientCore.ts @@ -48,6 +48,7 @@ export const writeClientCore = async ( i(templates.core.cancelablePromise(context), indent) ); await writeFile(Path.resolve(outputPath, 'request.ts'), i(templates.core.request(context), indent)); + await writeFile(Path.resolve(outputPath, 'types.ts'), i(templates.core.types(context), indent)); if (Boolean(clientName)) { await writeFile( diff --git a/src/utils/writeClientIndex.spec.ts b/src/utils/writeClientIndex.spec.ts index a7d5b610a..1adbf4920 100644 --- a/src/utils/writeClientIndex.spec.ts +++ b/src/utils/writeClientIndex.spec.ts @@ -17,23 +17,24 @@ describe('writeClientIndex', () => { }; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientIndex(client, templates, '/', true, true, true, true, true, 'Service', ''); diff --git a/src/utils/writeClientModels.spec.ts b/src/utils/writeClientModels.spec.ts index 4cca63231..11507a3cb 100644 --- a/src/utils/writeClientModels.spec.ts +++ b/src/utils/writeClientModels.spec.ts @@ -33,23 +33,24 @@ describe('writeClientModels', () => { ]; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientModels(models, templates, '/', { diff --git a/src/utils/writeClientSchemas.spec.ts b/src/utils/writeClientSchemas.spec.ts index e75928b8c..3ace4f746 100644 --- a/src/utils/writeClientSchemas.spec.ts +++ b/src/utils/writeClientSchemas.spec.ts @@ -33,23 +33,24 @@ describe('writeClientSchemas', () => { ]; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientSchemas(models, templates, '/', HttpClient.FETCH, false, Indent.SPACE_4); diff --git a/src/utils/writeClientServices.spec.ts b/src/utils/writeClientServices.spec.ts index 6ae029141..0d1f48a1b 100644 --- a/src/utils/writeClientServices.spec.ts +++ b/src/utils/writeClientServices.spec.ts @@ -21,23 +21,24 @@ describe('writeClientServices', () => { ]; const templates: Templates = { - index: () => 'index', client: () => 'client', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, core: { - settings: () => 'settings', apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - cancelablePromise: () => 'cancelablePromise', - request: () => 'request', baseHttpRequest: () => 'baseHttpRequest', + cancelablePromise: () => 'cancelablePromise', httpRequest: () => 'httpRequest', + request: () => 'request', + settings: () => 'settings', + types: () => 'types', }, + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + index: () => 'index', }; await writeClientServices(services, templates, '/', { diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index fc379814a..c46620d0a 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -55,12 +55,12 @@ exports[`v2 should generate: test/generated/v2/core/ApiResult.ts 1`] = ` /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type ApiResult = { - readonly url: string; +export type ApiResult = { + readonly body: TData; readonly ok: boolean; readonly status: number; readonly statusText: string; - readonly body: any; + readonly url: string; }; " `; @@ -558,6 +558,26 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C " `; +exports[`v2 should generate: test/generated/v2/core/types.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiResult } from './ApiResult'; + +export type TResult = 'body' | 'raw'; + +export type TApiResponse = + Exclude extends never + ? ApiResult + : ApiResult['body']; + +export type TConfig = { + _result?: T; +}; +" +`; + exports[`v2 should generate: test/generated/v2/index.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ @@ -3270,154 +3290,7 @@ export const $ModelWithPattern = { " `; -exports[`v3 should generate optional argument: test/generated/v3_options/index.ts 1`] = ` -"/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type { ModelWithString } from './models/ModelWithString'; - -export { DefaultsService } from './services/DefaultsService'; -" -`; - -exports[`v3 should generate optional argument: test/generated/v3_options/models/ModelWithString.ts 1`] = ` -"/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * This is a model with one string property - */ -export type ModelWithString = { - /** - * This is a simple string property - */ - prop?: string; -}; - -" -`; - -exports[`v3 should generate optional argument: test/generated/v3_options/services/DefaultsService.ts 1`] = ` -"/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { ModelWithString } from '../models/ModelWithString'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; -type TDataCallWithDefaultParameters = { - parameterString?: string | null, - parameterNumber?: number | null, - parameterBoolean?: boolean | null, - parameterEnum?: 'Success' | 'Warning' | 'Error', - parameterModel?: ModelWithString | null, -} -type TDataCallWithDefaultOptionalParameters = { - parameterString?: string, - parameterNumber?: number, - parameterBoolean?: boolean, - parameterEnum?: 'Success' | 'Warning' | 'Error', - parameterModel?: ModelWithString, -} -type TDataCallToTestOrderOfParams = { - parameterStringWithNoDefault: string, - parameterOptionalStringWithDefault?: string, - parameterOptionalStringWithEmptyDefault?: string, - parameterOptionalStringWithNoDefault?: string, - parameterStringWithDefault?: string, - parameterStringWithEmptyDefault?: string, - parameterStringNullableWithNoDefault?: string | null, - parameterStringNullableWithDefault?: string | null, -} -export class DefaultsService { - /** - * @throws ApiError - */ - public static callWithDefaultParameters(data: TDataCallWithDefaultParameters = {}): CancelablePromise { - const { - parameterString = 'Hello World!', - parameterNumber = 123, - parameterBoolean = true, - parameterEnum = 'Success', - parameterModel = { - "prop": "Hello World!" - }, - } = data; - return __request(OpenAPI, { - method: 'GET', - url: '/api/v{api-version}/defaults', - query: { - 'parameterString': parameterString, - 'parameterNumber': parameterNumber, - 'parameterBoolean': parameterBoolean, - 'parameterEnum': parameterEnum, - 'parameterModel': parameterModel, - }, - }); - } - /** - * @throws ApiError - */ - public static callWithDefaultOptionalParameters(data: TDataCallWithDefaultOptionalParameters = {}): CancelablePromise { - const { - parameterString = 'Hello World!', - parameterNumber = 123, - parameterBoolean = true, - parameterEnum = 'Success', - parameterModel = { - "prop": "Hello World!" - }, - } = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v{api-version}/defaults', - query: { - 'parameterString': parameterString, - 'parameterNumber': parameterNumber, - 'parameterBoolean': parameterBoolean, - 'parameterEnum': parameterEnum, - 'parameterModel': parameterModel, - }, - }); - } - /** - * @throws ApiError - */ - public static callToTestOrderOfParams(data: TDataCallToTestOrderOfParams): CancelablePromise { - const { - parameterStringWithNoDefault, - parameterOptionalStringWithDefault = 'Hello World!', - parameterOptionalStringWithEmptyDefault = '', - parameterOptionalStringWithNoDefault, - parameterStringWithDefault = 'Hello World!', - parameterStringWithEmptyDefault = '', - parameterStringNullableWithNoDefault, - parameterStringNullableWithDefault = null, - } = data; - return __request(OpenAPI, { - method: 'PUT', - url: '/api/v{api-version}/defaults', - query: { - 'parameterOptionalStringWithDefault': parameterOptionalStringWithDefault, - 'parameterOptionalStringWithEmptyDefault': parameterOptionalStringWithEmptyDefault, - 'parameterOptionalStringWithNoDefault': parameterOptionalStringWithNoDefault, - 'parameterStringWithDefault': parameterStringWithDefault, - 'parameterStringWithEmptyDefault': parameterStringWithEmptyDefault, - 'parameterStringWithNoDefault': parameterStringWithNoDefault, - 'parameterStringNullableWithNoDefault': parameterStringNullableWithNoDefault, - 'parameterStringNullableWithDefault': parameterStringNullableWithDefault, - }, - }); - } -} -" -`; - -exports[`v3 should generate: test/generated/v3/core/ApiError.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/ApiError.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ @@ -3446,7 +3319,7 @@ export class ApiError extends Error { " `; -exports[`v3 should generate: test/generated/v3/core/ApiRequestOptions.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/ApiRequestOptions.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ @@ -3467,22 +3340,22 @@ export type ApiRequestOptions = { " `; -exports[`v3 should generate: test/generated/v3/core/ApiResult.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/ApiResult.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type ApiResult = { - readonly url: string; +export type ApiResult = { + readonly body: TData; readonly ok: boolean; readonly status: number; readonly statusText: string; - readonly body: any; + readonly url: string; }; " `; -exports[`v3 should generate: test/generated/v3/core/CancelablePromise.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/CancelablePromise.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ @@ -3617,7 +3490,7 @@ export class CancelablePromise implements Promise { " `; -exports[`v3 should generate: test/generated/v3/core/OpenAPI.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/OpenAPI.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ @@ -3653,7 +3526,7 @@ export const OpenAPI: OpenAPIConfig = { " `; -exports[`v3 should generate: test/generated/v3/core/request.ts 1`] = ` +exports[`v3 should generate optional argument: test/generated/v3_options/core/request.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ /* tslint:disable */ @@ -3975,6 +3848,756 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C " `; +exports[`v3 should generate optional argument: test/generated/v3_options/core/types.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiResult } from './ApiResult'; + +export type TResult = 'body' | 'raw'; + +export type TApiResponse = + Exclude extends never + ? ApiResult + : ApiResult['body']; + +export type TConfig = { + _result?: T; +}; +" +`; + +exports[`v3 should generate optional argument: test/generated/v3_options/index.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export type { ModelWithString } from './models/ModelWithString'; + +export { DefaultsService } from './services/DefaultsService'; +" +`; + +exports[`v3 should generate optional argument: test/generated/v3_options/models/ModelWithString.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +" +`; + +exports[`v3 should generate optional argument: test/generated/v3_options/services/DefaultsService.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +import type { TApiResponse, TConfig, TResult } from '../core/types'; +type TDataCallWithDefaultParameters = { + parameterString?: string | null, + parameterNumber?: number | null, + parameterBoolean?: boolean | null, + parameterEnum?: 'Success' | 'Warning' | 'Error', + parameterModel?: ModelWithString | null, +} +type TDataCallWithDefaultOptionalParameters = { + parameterString?: string, + parameterNumber?: number, + parameterBoolean?: boolean, + parameterEnum?: 'Success' | 'Warning' | 'Error', + parameterModel?: ModelWithString, +} +type TDataCallToTestOrderOfParams = { + parameterStringWithNoDefault: string, + parameterOptionalStringWithDefault?: string, + parameterOptionalStringWithEmptyDefault?: string, + parameterOptionalStringWithNoDefault?: string, + parameterStringWithDefault?: string, + parameterStringWithEmptyDefault?: string, + parameterStringNullableWithNoDefault?: string | null, + parameterStringNullableWithDefault?: string | null, +} +export class DefaultsService { + /** + * @throws ApiError + */ + public static callWithDefaultParameters(data: TDataCallWithDefaultParameters & TConfig = {}): CancelablePromise> { + const { + parameterString = 'Hello World!', + parameterNumber = 123, + parameterBoolean = true, + parameterEnum = 'Success', + parameterModel = { + "prop": "Hello World!" + }, + } = data; + return __request(OpenAPI, { + method: 'GET', + url: '/api/v{api-version}/defaults', + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + } + /** + * @throws ApiError + */ + public static callWithDefaultOptionalParameters(data: TDataCallWithDefaultOptionalParameters & TConfig = {}): CancelablePromise> { + const { + parameterString = 'Hello World!', + parameterNumber = 123, + parameterBoolean = true, + parameterEnum = 'Success', + parameterModel = { + "prop": "Hello World!" + }, + } = data; + return __request(OpenAPI, { + method: 'POST', + url: '/api/v{api-version}/defaults', + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + } + /** + * @throws ApiError + */ + public static callToTestOrderOfParams(data: TDataCallToTestOrderOfParams & TConfig): CancelablePromise> { + const { + parameterStringWithNoDefault, + parameterOptionalStringWithDefault = 'Hello World!', + parameterOptionalStringWithEmptyDefault = '', + parameterOptionalStringWithNoDefault, + parameterStringWithDefault = 'Hello World!', + parameterStringWithEmptyDefault = '', + parameterStringNullableWithNoDefault, + parameterStringNullableWithDefault = null, + } = data; + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v{api-version}/defaults', + query: { + 'parameterOptionalStringWithDefault': parameterOptionalStringWithDefault, + 'parameterOptionalStringWithEmptyDefault': parameterOptionalStringWithEmptyDefault, + 'parameterOptionalStringWithNoDefault': parameterOptionalStringWithNoDefault, + 'parameterStringWithDefault': parameterStringWithDefault, + 'parameterStringWithEmptyDefault': parameterStringWithEmptyDefault, + 'parameterStringWithNoDefault': parameterStringWithNoDefault, + 'parameterStringNullableWithNoDefault': parameterStringNullableWithNoDefault, + 'parameterStringNullableWithDefault': parameterStringNullableWithDefault, + }, + }); + } +} +" +`; + +exports[`v3 should generate: test/generated/v3/core/ApiError.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} +" +`; + +exports[`v3 should generate: test/generated/v3/core/ApiRequestOptions.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; +" +`; + +exports[`v3 should generate: test/generated/v3/core/ApiResult.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly body: TData; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly url: string; +}; +" +`; + +exports[`v3 should generate: test/generated/v3/core/CancelablePromise.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise; + #resolve?: (value: T | PromiseLike) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + if (this.#resolve) this.#resolve(value); + }; + + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + if (this.#reject) this.#reject(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.#cancelHandlers.length = 0; + if (this.#reject) this.#reject(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this.#isCancelled; + } +} +" +`; + +exports[`v3 should generate: test/generated/v3/core/OpenAPI.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + HEADERS?: Headers | Resolver | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:3000/base', + VERSION: '1.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; +" +`; + +exports[`v3 should generate: test/generated/v3/core/request.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isString = (value: any): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +export const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }; + + const process = (key: string, value: any) => { + if (value) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(\`\${key}[\${k}]\`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + + return ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = \`\${config.BASE}\${path}\`; + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => value) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS), + ]); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => value) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = \`Bearer \${token}\`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(\`\${username}:\${password}\`); + headers['Authorization'] = \`Basic \${credentials}\`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new Headers(headers); +}; + +export const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body !== undefined) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + const request: RequestInit = { + headers, + body: body ?? formData, + method: options.method, + signal: controller.signal, + }; + + if (config.WITH_CREDENTIALS) { + request.credentials = config.CREDENTIALS; + } + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; + +export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const jsonTypes = ['application/json', 'application/problem+json'] + const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + \`Generic Error: status: \${errorStatus}; status text: \${errorStatusText}; body: \${errorBody}\` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; +" +`; + +exports[`v3 should generate: test/generated/v3/core/types.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiResult } from './ApiResult'; + +export type TResult = 'body' | 'raw'; + +export type TApiResponse = + Exclude extends never + ? ApiResult + : ApiResult['body']; + +export type TConfig = { + _result?: T; +}; +" +`; + exports[`v3 should generate: test/generated/v3/index.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do not edit */ /* istanbul ignore file */ diff --git a/test/index.spec.ts b/test/index.spec.ts index 727cd3c54..3c41d6887 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -70,7 +70,7 @@ describe('v3', () => { it('should generate optional argument', async () => { await generate({ autoformat: false, - exportCore: false, + exportCore: true, exportModels: '^ModelWithString', exportSchemas: false, exportServices: '^Defaults',