From e23135a2a37c6f8f323ed0fc9176f6e3d677845d Mon Sep 17 00:00:00 2001 From: lub0v-parsable Date: Wed, 4 Aug 2021 21:35:29 -0700 Subject: [PATCH 1/2] generate client instances with --exportClient option --- README.md | 25 + bin/index.js | 4 + jest.config.js | 12 +- src/index.ts | 14 +- .../v2/parser/getOperationPath.spec.ts | 4 +- src/openApi/v2/parser/getOperationPath.ts | 8 +- .../v3/parser/getOperationPath.spec.ts | 4 +- src/openApi/v3/parser/getOperationPath.ts | 8 +- src/templates/core/BaseHttpRequest.hbs | 17 + .../{request.hbs => ConcreteHttpRequest.hbs} | 0 src/templates/core/OpenAPI.hbs | 13 +- src/templates/core/fetch/getHeaders.hbs | 10 +- src/templates/core/fetch/request.hbs | 41 +- src/templates/core/fetch/sendRequest.hbs | 6 +- src/templates/core/functions/getUrl.hbs | 4 +- src/templates/core/node/getHeaders.hbs | 10 +- src/templates/core/node/request.hbs | 41 +- src/templates/core/node/sendRequest.hbs | 4 +- src/templates/core/xhr/getHeaders.hbs | 10 +- src/templates/core/xhr/request.hbs | 42 +- src/templates/core/xhr/sendRequest.hbs | 6 +- src/templates/exportAppClient.hbs | 32 + src/templates/exportService.hbs | 19 +- src/templates/index.hbs | 12 + src/utils/getHttpClientName.spec.ts | 14 + src/utils/getHttpRequestName.ts | 3 + src/utils/postProcessClient.ts | 5 +- src/utils/postProcessService.ts | 4 +- src/utils/postProcessServiceOperations.ts | 5 +- src/utils/registerHandlebarTemplates.spec.ts | 4 +- src/utils/registerHandlebarTemplates.ts | 12 +- src/utils/writeAppClient.spec.ts | 39 + src/utils/writeAppClient.ts | 32 + src/utils/writeClient.spec.ts | 6 +- src/utils/writeClient.ts | 17 +- src/utils/writeClientCore.spec.ts | 61 +- src/utils/writeClientCore.ts | 12 +- src/utils/writeClientIndex.spec.ts | 7 +- src/utils/writeClientIndex.ts | 13 +- src/utils/writeClientModels.spec.ts | 4 +- src/utils/writeClientSchemas.spec.ts | 4 +- src/utils/writeClientServices.spec.ts | 6 +- src/utils/writeClientServices.ts | 14 +- test/__snapshots__/index.client.spec.js.snap | 5416 +++++++++++++++++ test/__snapshots__/index.spec.js.snap | 86 +- test/e2e/scripts/generate.js | 3 +- test/e2e/v2.babel.spec.js | 47 +- test/e2e/v2.fetch.spec.js | 47 +- test/e2e/v2.node.spec.js | 42 +- test/e2e/v2.xhr.spec.js | 47 +- test/e2e/v3.babel.spec.js | 56 +- test/e2e/v3.fetch.spec.js | 56 +- test/e2e/v3.node.spec.js | 49 +- test/e2e/v3.xhr.spec.js | 56 +- test/index.client.spec.js | 51 + test/index.spec.js | 2 + 56 files changed, 6373 insertions(+), 193 deletions(-) create mode 100644 src/templates/core/BaseHttpRequest.hbs rename src/templates/core/{request.hbs => ConcreteHttpRequest.hbs} (100%) create mode 100644 src/templates/exportAppClient.hbs create mode 100644 src/utils/getHttpClientName.spec.ts create mode 100644 src/utils/getHttpRequestName.ts create mode 100644 src/utils/writeAppClient.spec.ts create mode 100644 src/utils/writeAppClient.ts create mode 100644 test/__snapshots__/index.client.spec.js.snap create mode 100644 test/index.client.spec.js diff --git a/README.md b/README.md index eacdf7069..b241c9703 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ $ openapi --help --exportServices Write services to disk (default: true) --exportModels Write models to disk (default: true) --exportSchemas Write schemas to disk (default: false) + --exportClient Generate and write client class to disk (default: false) + --name Custom client class name (default: "AppClient") Examples $ openapi --input ./spec.json @@ -392,6 +394,29 @@ const getToken = async () => { OpenAPI.TOKEN = getToken; ``` +### Generate client instance with `--exportClient` option + +The OpenAPI generator allows to create client instances to support the multiple backend services use case. +The generated client uses an instance of the server configuration and not the global `OpenAPI` constant. + +To generate a client instance, use `--exportClient` option. To set a custom name to the client class, use `--name` option. + +``` +openapi --input ./spec.json --output ./dist --exportClient true --name DemoAppClient +``` + +The generated client will be exported from the `index` file and can be used as shown below: +```typescript +// create the client instance with server and authentication details +const appClient = new DemoAppClient({ BASE: 'http://server-host.com', TOKEN: '1234' }); + +// use the client instance to make the API call +const res: OrganizationResponse = await appClient.organizations.createOrganization({ + name: 'OrgName', + description: 'OrgDescription', +}); +``` + ### References Local references to schema definitions (those beginning with `#/definitions/schemas/`) diff --git a/bin/index.js b/bin/index.js index 4419b9964..27f34b71e 100755 --- a/bin/index.js +++ b/bin/index.js @@ -19,7 +19,9 @@ const params = program .option('--exportServices ', 'Write services to disk', true) .option('--exportModels ', 'Write models to disk', true) .option('--exportSchemas ', 'Write schemas to disk', false) + .option('--exportClient ', 'Generate and write client class to disk', false) .option('--request ', 'Path to custom request file') + .option('--name ', 'Custom client class name', 'AppClient') .parse(process.argv) .opts(); @@ -36,6 +38,8 @@ if (OpenAPI) { exportServices: JSON.parse(params.exportServices) === true, exportModels: JSON.parse(params.exportModels) === true, exportSchemas: JSON.parse(params.exportSchemas) === true, + exportClient: JSON.parse(params.exportClient) === true, + clientName: params.name, request: params.request, }) .then(() => { diff --git a/jest.config.js b/jest.config.js index 427bbb70a..ec73e8919 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,10 +5,7 @@ module.exports = { { displayName: 'UNIT', testEnvironment: 'node', - testMatch: [ - '/src/**/*.spec.ts', - '/test/index.spec.js', - ], + testMatch: ['/src/**/*.spec.ts', '/test/index.spec.js', '/test/index.client.spec.js'], moduleFileExtensions: ['js', 'ts', 'd.ts'], moduleNameMapper: { '\\.hbs$': '/src/templates/__mocks__/index.js', @@ -29,10 +26,5 @@ module.exports = { ], }, ], - collectCoverageFrom: [ - '/src/**/*.ts', - '!/src/**/*.d.ts', - '!/bin', - '!/dist', - ], + collectCoverageFrom: ['/src/**/*.ts', '!/src/**/*.d.ts', '!/bin', '!/dist'], }; diff --git a/src/index.ts b/src/index.ts index 7a67ee56d..a2e188402 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,9 @@ export type Options = { exportServices?: boolean; exportModels?: boolean; exportSchemas?: boolean; + exportClient?: boolean; request?: string; + clientName?: string; write?: boolean; }; @@ -37,6 +39,8 @@ export type Options = { * @param exportServices: Generate services * @param exportModels: Generate models * @param exportSchemas: Generate schemas + * @param exportClient: Generate client class + * @param clientName: Custom client class name * @param request: Path to custom request file * @param write Write the files to disk (true or false) */ @@ -50,6 +54,8 @@ export async function generate({ exportServices = true, exportModels = true, exportSchemas = false, + exportClient = false, + clientName = 'AppClient', request, write = true, }: Options): Promise { @@ -64,17 +70,17 @@ export async function generate({ switch (openApiVersion) { case OpenApiVersion.V2: { const client = parseV2(openApi); - const clientFinal = postProcessClient(client); + const clientFinal = postProcessClient(client, exportClient); if (!write) break; - await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, request); + await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, exportClient, clientName, request); break; } case OpenApiVersion.V3: { const client = parseV3(openApi); - const clientFinal = postProcessClient(client); + const clientFinal = postProcessClient(client, exportClient); if (!write) break; - await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, request); + await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, exportClient, clientName, request); break; } } diff --git a/src/openApi/v2/parser/getOperationPath.spec.ts b/src/openApi/v2/parser/getOperationPath.spec.ts index 41df428d1..2fa2c0752 100644 --- a/src/openApi/v2/parser/getOperationPath.spec.ts +++ b/src/openApi/v2/parser/getOperationPath.spec.ts @@ -2,8 +2,8 @@ import { getOperationPath } from './getOperationPath'; describe('getOperationPath', () => { it('should produce correct result', () => { - expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}'); - expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}'); + expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${apiVersion}/list/${id}/${type}'); + expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${apiVersion}/list/${id}'); expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}'); expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}'); expect(getOperationPath('/api/{fooBar}')).toEqual('/api/${fooBar}'); diff --git a/src/openApi/v2/parser/getOperationPath.ts b/src/openApi/v2/parser/getOperationPath.ts index 7d2a07cff..8897d2db8 100644 --- a/src/openApi/v2/parser/getOperationPath.ts +++ b/src/openApi/v2/parser/getOperationPath.ts @@ -8,9 +8,7 @@ import { getOperationParameterName } from './getOperationParameterName'; * @param path */ export function getOperationPath(path: string): string { - return path - .replace(/\{(.*?)\}/g, (_, w: string) => { - return `\${${getOperationParameterName(w)}}`; - }) - .replace('${apiVersion}', '${OpenAPI.VERSION}'); + return path.replace(/\{(.*?)\}/g, (_, w: string) => { + return `\${${getOperationParameterName(w)}}`; + }); } diff --git a/src/openApi/v3/parser/getOperationPath.spec.ts b/src/openApi/v3/parser/getOperationPath.spec.ts index 41df428d1..2fa2c0752 100644 --- a/src/openApi/v3/parser/getOperationPath.spec.ts +++ b/src/openApi/v3/parser/getOperationPath.spec.ts @@ -2,8 +2,8 @@ import { getOperationPath } from './getOperationPath'; describe('getOperationPath', () => { it('should produce correct result', () => { - expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}'); - expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}'); + expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${apiVersion}/list/${id}/${type}'); + expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${apiVersion}/list/${id}'); expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}'); expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}'); expect(getOperationPath('/api/{fooBar}')).toEqual('/api/${fooBar}'); diff --git a/src/openApi/v3/parser/getOperationPath.ts b/src/openApi/v3/parser/getOperationPath.ts index 7d2a07cff..8897d2db8 100644 --- a/src/openApi/v3/parser/getOperationPath.ts +++ b/src/openApi/v3/parser/getOperationPath.ts @@ -8,9 +8,7 @@ import { getOperationParameterName } from './getOperationParameterName'; * @param path */ export function getOperationPath(path: string): string { - return path - .replace(/\{(.*?)\}/g, (_, w: string) => { - return `\${${getOperationParameterName(w)}}`; - }) - .replace('${apiVersion}', '${OpenAPI.VERSION}'); + return path.replace(/\{(.*?)\}/g, (_, w: string) => { + return `\${${getOperationParameterName(w)}}`; + }); } diff --git a/src/templates/core/BaseHttpRequest.hbs b/src/templates/core/BaseHttpRequest.hbs new file mode 100644 index 000000000..ad6636a32 --- /dev/null +++ b/src/templates/core/BaseHttpRequest.hbs @@ -0,0 +1,17 @@ +{{>header}} + +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; + +export class BaseHttpRequest { + readonly openApiConfig: OpenAPIConfig; + + constructor(openApiConfig: OpenAPIConfig) { + this.openApiConfig = openApiConfig; + } + + async request(options: ApiRequestOptions): Promise { + throw new Error('Not Implemented'); + } +} diff --git a/src/templates/core/request.hbs b/src/templates/core/ConcreteHttpRequest.hbs similarity index 100% rename from src/templates/core/request.hbs rename to src/templates/core/ConcreteHttpRequest.hbs diff --git a/src/templates/core/OpenAPI.hbs b/src/templates/core/OpenAPI.hbs index 2b59a9708..6ddd59fd8 100644 --- a/src/templates/core/OpenAPI.hbs +++ b/src/templates/core/OpenAPI.hbs @@ -5,17 +5,17 @@ import type { ApiRequestOptions } from './ApiRequestOptions'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; -type Config = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; +export type OpenAPIConfig = { + BASE?: string; + VERSION?: string; + WITH_CREDENTIALS?: boolean; TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; HEADERS?: Headers | Resolver; } - -export const OpenAPI: Config = { +{{#unless @root.exportClient}} +export const OpenAPI: OpenAPIConfig = { BASE: '{{{server}}}', VERSION: '{{{version}}}', WITH_CREDENTIALS: false, @@ -24,3 +24,4 @@ export const OpenAPI: Config = { PASSWORD: undefined, HEADERS: undefined, }; +{{/unless}} diff --git a/src/templates/core/fetch/getHeaders.hbs b/src/templates/core/fetch/getHeaders.hbs index 3d1a62c7e..1ef2301f0 100644 --- a/src/templates/core/fetch/getHeaders.hbs +++ b/src/templates/core/fetch/getHeaders.hbs @@ -1,8 +1,8 @@ -async function getHeaders(options: ApiRequestOptions): Promise { - const token = await resolve(options, OpenAPI.TOKEN); - const username = await resolve(options, OpenAPI.USERNAME); - const password = await resolve(options, OpenAPI.PASSWORD); - const defaultHeaders = await resolve(options, OpenAPI.HEADERS); +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); const headers = new Headers({ Accept: 'application/json', diff --git a/src/templates/core/fetch/request.hbs b/src/templates/core/fetch/request.hbs index c22330e51..1e34f4fbb 100644 --- a/src/templates/core/fetch/request.hbs +++ b/src/templates/core/fetch/request.hbs @@ -3,7 +3,12 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +{{#if @root.exportClient}} +import { BaseHttpRequest } from './BaseHttpRequest'; +{{else}} import { OpenAPI } from './OpenAPI'; +{{/if}} {{>functions/isDefined}} @@ -47,6 +52,37 @@ import { OpenAPI } from './OpenAPI'; {{>functions/catchErrors}} +{{#if @root.exportClient}} +export class FetchHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; + } +} +{{else}} /** * Request using fetch client * @param options The request options from the the service @@ -54,8 +90,8 @@ import { OpenAPI } from './OpenAPI'; * @throws ApiError */ export async function request(options: ApiRequestOptions): Promise { - const url = getUrl(options); - const response = await sendRequest(options, url); + const url = getUrl(options, OpenAPI); + const response = await sendRequest(options, OpenAPI, url); const responseBody = await getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -70,3 +106,4 @@ export async function request(options: ApiRequestOptions): Promise { catchErrors(options, result); return result; } +{{/if}} diff --git a/src/templates/core/fetch/sendRequest.hbs b/src/templates/core/fetch/sendRequest.hbs index 4afd07317..bdcf5b26f 100644 --- a/src/templates/core/fetch/sendRequest.hbs +++ b/src/templates/core/fetch/sendRequest.hbs @@ -1,10 +1,10 @@ -async function sendRequest(options: ApiRequestOptions, url: string): Promise { +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { const request: RequestInit = { method: options.method, - headers: await getHeaders(options), + headers: await getHeaders(options, config), body: getRequestBody(options), }; - if (OpenAPI.WITH_CREDENTIALS) { + if (config.WITH_CREDENTIALS) { request.credentials = 'include'; } return await fetch(url, request); diff --git a/src/templates/core/functions/getUrl.hbs b/src/templates/core/functions/getUrl.hbs index be040bbb6..758ab7549 100644 --- a/src/templates/core/functions/getUrl.hbs +++ b/src/templates/core/functions/getUrl.hbs @@ -1,6 +1,6 @@ -function getUrl(options: ApiRequestOptions): string { +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { const path = options.path.replace(/[:]/g, '_'); - const url = `${OpenAPI.BASE}${path}`; + const url = `${config.BASE}${path}`; if (options.query) { return `${url}${getQueryString(options.query)}`; diff --git a/src/templates/core/node/getHeaders.hbs b/src/templates/core/node/getHeaders.hbs index fc6d55164..61f2e47ce 100644 --- a/src/templates/core/node/getHeaders.hbs +++ b/src/templates/core/node/getHeaders.hbs @@ -1,8 +1,8 @@ -async function getHeaders(options: ApiRequestOptions): Promise { - const token = await resolve(options, OpenAPI.TOKEN); - const username = await resolve(options, OpenAPI.USERNAME); - const password = await resolve(options, OpenAPI.PASSWORD); - const defaultHeaders = await resolve(options, OpenAPI.HEADERS); +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); const headers = new Headers({ Accept: 'application/json', diff --git a/src/templates/core/node/request.hbs b/src/templates/core/node/request.hbs index 1bf15050b..229c02fdb 100644 --- a/src/templates/core/node/request.hbs +++ b/src/templates/core/node/request.hbs @@ -7,7 +7,12 @@ import { types } from 'util'; import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +{{#if @root.exportClient}} +import { BaseHttpRequest } from './BaseHttpRequest'; +{{else}} import { OpenAPI } from './OpenAPI'; +{{/if}} {{>functions/isDefined}} @@ -51,6 +56,37 @@ import { OpenAPI } from './OpenAPI'; {{>functions/catchErrors}} +{{#if @root.exportClient}} +export class NodeHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using node-fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; + } +} +{{else}} /** * Request using node-fetch client * @param options The request options from the the service @@ -58,8 +94,8 @@ import { OpenAPI } from './OpenAPI'; * @throws ApiError */ export async function request(options: ApiRequestOptions): Promise { - const url = getUrl(options); - const response = await sendRequest(options, url); + const url = getUrl(options, OpenAPI); + const response = await sendRequest(options, OpenAPI, url); const responseBody = await getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -74,3 +110,4 @@ export async function request(options: ApiRequestOptions): Promise { catchErrors(options, result); return result; } +{{/if}} diff --git a/src/templates/core/node/sendRequest.hbs b/src/templates/core/node/sendRequest.hbs index e0a296e07..9ef53752f 100644 --- a/src/templates/core/node/sendRequest.hbs +++ b/src/templates/core/node/sendRequest.hbs @@ -1,7 +1,7 @@ -async function sendRequest(options: ApiRequestOptions, url: string): Promise { +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { const request: RequestInit = { method: options.method, - headers: await getHeaders(options), + headers: await getHeaders(options, config), body: getRequestBody(options), }; return await fetch(url, request); diff --git a/src/templates/core/xhr/getHeaders.hbs b/src/templates/core/xhr/getHeaders.hbs index 3d1a62c7e..1ef2301f0 100644 --- a/src/templates/core/xhr/getHeaders.hbs +++ b/src/templates/core/xhr/getHeaders.hbs @@ -1,8 +1,8 @@ -async function getHeaders(options: ApiRequestOptions): Promise { - const token = await resolve(options, OpenAPI.TOKEN); - const username = await resolve(options, OpenAPI.USERNAME); - const password = await resolve(options, OpenAPI.PASSWORD); - const defaultHeaders = await resolve(options, OpenAPI.HEADERS); +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); const headers = new Headers({ Accept: 'application/json', diff --git a/src/templates/core/xhr/request.hbs b/src/templates/core/xhr/request.hbs index 9faf192f2..7749c7e35 100644 --- a/src/templates/core/xhr/request.hbs +++ b/src/templates/core/xhr/request.hbs @@ -3,7 +3,13 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +{{#if @root.exportClient}} +import { BaseHttpRequest } from './BaseHttpRequest'; +{{else}} import { OpenAPI } from './OpenAPI'; +{{/if}} + {{>functions/isDefined}} @@ -50,6 +56,37 @@ import { OpenAPI } from './OpenAPI'; {{>functions/catchErrors}} +{{#if @root.exportClient}} +export class XhrHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using XHR client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; + + catchErrors(options, result); + return result; + } +} +{{else}} /** * Request using XHR client * @param options The request options from the the service @@ -57,8 +94,8 @@ import { OpenAPI } from './OpenAPI'; * @throws ApiError */ export async function request(options: ApiRequestOptions): Promise { - const url = getUrl(options); - const response = await sendRequest(options, url); + const url = getUrl(options, OpenAPI); + const response = await sendRequest(options, OpenAPI, url); const responseBody = getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -73,3 +110,4 @@ export async function request(options: ApiRequestOptions): Promise { catchErrors(options, result); return result; } +{{/if}} diff --git a/src/templates/core/xhr/sendRequest.hbs b/src/templates/core/xhr/sendRequest.hbs index 70c08b1fd..5c6d9bfd8 100644 --- a/src/templates/core/xhr/sendRequest.hbs +++ b/src/templates/core/xhr/sendRequest.hbs @@ -1,10 +1,10 @@ -async function sendRequest(options: ApiRequestOptions, url: string): Promise { +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { const xhr = new XMLHttpRequest(); xhr.open(options.method, url, true); - xhr.withCredentials = OpenAPI.WITH_CREDENTIALS; + xhr.withCredentials = config.WITH_CREDENTIALS ?? false; - const headers = await getHeaders(options); + const headers = await getHeaders(options, config); headers.forEach((value: string, key: string) => { xhr.setRequestHeader(key, value); }); diff --git a/src/templates/exportAppClient.hbs b/src/templates/exportAppClient.hbs new file mode 100644 index 000000000..3cb7417bc --- /dev/null +++ b/src/templates/exportAppClient.hbs @@ -0,0 +1,32 @@ +{{>header}} + +import { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { {{{httpClientRequest}}} } from './core/{{{httpClientRequest}}}'; +{{#if services}} +{{#each services}} +import { {{{name}}} } from './services/{{{name}}}'; +{{/each}} +{{/if}} + +export class {{{clientName}}} { +{{#each services}} + readonly {{{shortName}}}: {{{name}}}; +{{/each}} + readonly request: BaseHttpRequest; + + constructor(openApiConfig?: OpenAPIConfig, HttpRequest: new (config: OpenAPIConfig) => BaseHttpRequest = {{{httpClientRequest}}}) { + this.request = new HttpRequest({ + BASE: openApiConfig?.BASE ?? '{{{server}}}', + VERSION: openApiConfig?.VERSION ?? '{{{version}}}', + WITH_CREDENTIALS: openApiConfig?.WITH_CREDENTIALS ?? false, + TOKEN: openApiConfig?.TOKEN, + USERNAME: openApiConfig?.USERNAME, + PASSWORD: openApiConfig?.PASSWORD, + HEADERS: openApiConfig?.HEADERS, + }); + {{#each services}} + this.{{{shortName}}} = new {{{name}}}(this.request); + {{/each}} + } +} diff --git a/src/templates/exportService.hbs b/src/templates/exportService.hbs index c45ed830d..7e0f2a134 100644 --- a/src/templates/exportService.hbs +++ b/src/templates/exportService.hbs @@ -5,12 +5,23 @@ import type { {{{this}}} } from '../models/{{{this}}}'; {{/each}} {{/if}} +{{#if @root.exportClient}} +import { BaseHttpRequest } from '../core/BaseHttpRequest'; +{{else}} import { request as __request } from '../core/request'; {{#if @root.useVersion}} import { OpenAPI } from '../core/OpenAPI'; {{/if}} +{{/if}} export class {{{name}}} { + {{#if @root.exportClient}} + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + {{/if}} {{#each operations}} /** @@ -33,10 +44,10 @@ export class {{{name}}} { {{#each results}} * @returns {{{type}}} {{{description}}} {{/each}} - * @throws ApiError - */ - public static async {{{name}}}({{>parameters}}): Promise<{{>result}}> { - const result = await __request({ + * @throws ApiError + */ + public{{#unless @root.exportClient}} static{{/unless}} async {{{name}}}({{>parameters}}): Promise<{{>result}}> { + const result = await {{#if @root.exportClient}}this.httpRequest.request{{else}}__request{{/if}}({ method: '{{{method}}}', path: `{{{path}}}`, {{#if parametersCookie}} diff --git a/src/templates/index.hbs b/src/templates/index.hbs index e10436ba6..cb5b6b0b4 100644 --- a/src/templates/index.hbs +++ b/src/templates/index.hbs @@ -2,8 +2,16 @@ {{#if @root.exportCore}} export { ApiError } from './core/ApiError'; +{{#if @root.exportClient}} +export type { ApiRequestOptions } from './core/ApiRequestOptions'; +export type { ApiResult } from './core/ApiResult'; +export type { OpenAPIConfig } from './core/OpenAPI'; +export { BaseHttpRequest } from './core/BaseHttpRequest'; +export { {{{httpRequestName}}} } from './core/{{{httpRequestName}}}'; +{{else}} export { OpenAPI } from './core/OpenAPI'; {{/if}} +{{/if}} {{#if @root.exportModels}} {{#if models}} @@ -36,3 +44,7 @@ export { {{{name}}} } from './services/{{{name}}}'; {{/each}} {{/if}} {{/if}} + +{{#if @root.exportClient}} +export { {{{clientName}}} } from './client'; +{{/if}} diff --git a/src/utils/getHttpClientName.spec.ts b/src/utils/getHttpClientName.spec.ts new file mode 100644 index 000000000..ffeb13094 --- /dev/null +++ b/src/utils/getHttpClientName.spec.ts @@ -0,0 +1,14 @@ +import { HttpClient } from '../HttpClient'; +import { getHttpRequestName } from './getHttpRequestName'; + +describe('getHttpClientName', () => { + it('should convert the FETCH client', () => { + expect(getHttpRequestName(HttpClient.FETCH)).toEqual('FetchHttpRequest'); + }); + it('should convert the NODE client', () => { + expect(getHttpRequestName(HttpClient.NODE)).toEqual('NodeHttpRequest'); + }); + it('should convert the XHR client', () => { + expect(getHttpRequestName(HttpClient.XHR)).toEqual('XhrHttpRequest'); + }); +}); diff --git a/src/utils/getHttpRequestName.ts b/src/utils/getHttpRequestName.ts new file mode 100644 index 000000000..ab05375cf --- /dev/null +++ b/src/utils/getHttpRequestName.ts @@ -0,0 +1,3 @@ +export function getHttpRequestName(httpClientName: string): string { + return httpClientName[0].toUpperCase() + httpClientName.substring(1) + 'HttpRequest'; +} diff --git a/src/utils/postProcessClient.ts b/src/utils/postProcessClient.ts index 9e0e00dd7..f522044f1 100644 --- a/src/utils/postProcessClient.ts +++ b/src/utils/postProcessClient.ts @@ -5,11 +5,12 @@ import { postProcessService } from './postProcessService'; /** * Post process client * @param client Client object with all the models, services, etc. + * @param exportClient Create client class */ -export function postProcessClient(client: Client): Client { +export function postProcessClient(client: Client, exportClient: boolean): Client { return { ...client, models: client.models.map(model => postProcessModel(model)), - services: client.services.map(service => postProcessService(service)), + services: client.services.map(service => postProcessService(service, exportClient)), }; } diff --git a/src/utils/postProcessService.ts b/src/utils/postProcessService.ts index d604dcdd7..4b247c099 100644 --- a/src/utils/postProcessService.ts +++ b/src/utils/postProcessService.ts @@ -2,9 +2,9 @@ import type { Service } from '../client/interfaces/Service'; import { postProcessServiceImports } from './postProcessServiceImports'; import { postProcessServiceOperations } from './postProcessServiceOperations'; -export function postProcessService(service: Service): Service { +export function postProcessService(service: Service, exportClient: boolean): Service { const clone = { ...service }; - clone.operations = postProcessServiceOperations(clone); + clone.operations = postProcessServiceOperations(clone, exportClient); clone.operations.forEach(operation => { clone.imports.push(...operation.imports); }); diff --git a/src/utils/postProcessServiceOperations.ts b/src/utils/postProcessServiceOperations.ts index 62faad724..759af5573 100644 --- a/src/utils/postProcessServiceOperations.ts +++ b/src/utils/postProcessServiceOperations.ts @@ -2,7 +2,7 @@ import type { Operation } from '../client/interfaces/Operation'; import type { Service } from '../client/interfaces/Service'; import { flatMap } from './flatMap'; -export function postProcessServiceOperations(service: Service): Operation[] { +export function postProcessServiceOperations(service: Service, exportClient: boolean): Operation[] { const names = new Map(); return service.operations.map(operation => { @@ -21,6 +21,9 @@ export function postProcessServiceOperations(service: Service): Operation[] { } names.set(name, index + 1); + // Update the operation path with the dynamically injected version + clone.path = clone.path.replace('${apiVersion}', exportClient ? '${this.httpRequest.openApiConfig.VERSION}' : '${OpenAPI.VERSION}'); + return clone; }); } diff --git a/src/utils/registerHandlebarTemplates.spec.ts b/src/utils/registerHandlebarTemplates.spec.ts index 5e1302384..f5a90aa42 100644 --- a/src/utils/registerHandlebarTemplates.spec.ts +++ b/src/utils/registerHandlebarTemplates.spec.ts @@ -16,6 +16,8 @@ describe('registerHandlebarTemplates', () => { expect(templates.core.apiError).toBeDefined(); expect(templates.core.apiRequestOptions).toBeDefined(); expect(templates.core.apiResult).toBeDefined(); - expect(templates.core.request).toBeDefined(); + expect(templates.core.concreteHttpRequest).toBeDefined(); + expect(templates.core.baseHttpRequest).toBeDefined(); + expect(templates.client).toBeDefined(); }); }); diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index 347b2b98b..0b0c45913 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -4,6 +4,8 @@ import { HttpClient } from '../HttpClient'; import templateCoreApiError from '../templates/core/ApiError.hbs'; import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs'; import templateCoreApiResult from '../templates/core/ApiResult.hbs'; +import templateCoreBaseHttpClient from '../templates/core/BaseHttpRequest.hbs'; +import templateCoreConcreteHttpClient from '../templates/core/ConcreteHttpRequest.hbs'; import fetchGetHeaders from '../templates/core/fetch/getHeaders.hbs'; import fetchGetRequestBody from '../templates/core/fetch/getRequestBody.hbs'; import fetchGetResponseBody from '../templates/core/fetch/getResponseBody.hbs'; @@ -28,13 +30,13 @@ import nodeGetResponseHeader from '../templates/core/node/getResponseHeader.hbs' 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 xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs'; import xhrGetRequestBody from '../templates/core/xhr/getRequestBody.hbs'; import xhrGetResponseBody from '../templates/core/xhr/getResponseBody.hbs'; import xhrGetResponseHeader from '../templates/core/xhr/getResponseHeader.hbs'; import xhrRequest from '../templates/core/xhr/request.hbs'; import xhrSendRequest from '../templates/core/xhr/sendRequest.hbs'; +import templateAppClient from '../templates/exportAppClient.hbs'; import templateExportModel from '../templates/exportModel.hbs'; import templateExportSchema from '../templates/exportSchema.hbs'; import templateExportService from '../templates/exportService.hbs'; @@ -70,6 +72,7 @@ import { registerHandlebarHelpers } from './registerHandlebarHelpers'; export interface Templates { index: Handlebars.TemplateDelegate; + client: Handlebars.TemplateDelegate; exports: { model: Handlebars.TemplateDelegate; schema: Handlebars.TemplateDelegate; @@ -80,7 +83,8 @@ export interface Templates { apiError: Handlebars.TemplateDelegate; apiRequestOptions: Handlebars.TemplateDelegate; apiResult: Handlebars.TemplateDelegate; - request: Handlebars.TemplateDelegate; + baseHttpRequest: Handlebars.TemplateDelegate; + concreteHttpRequest: Handlebars.TemplateDelegate; }; } @@ -94,6 +98,7 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp // Main templates (entry points for the files we write to disk) const templates: Templates = { index: Handlebars.template(templateIndex), + client: Handlebars.template(templateAppClient), exports: { model: Handlebars.template(templateExportModel), schema: Handlebars.template(templateExportSchema), @@ -104,7 +109,8 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp apiError: Handlebars.template(templateCoreApiError), apiRequestOptions: Handlebars.template(templateCoreApiRequestOptions), apiResult: Handlebars.template(templateCoreApiResult), - request: Handlebars.template(templateCoreRequest), + baseHttpRequest: Handlebars.template(templateCoreBaseHttpClient), + concreteHttpRequest: Handlebars.template(templateCoreConcreteHttpClient), }, }; diff --git a/src/utils/writeAppClient.spec.ts b/src/utils/writeAppClient.spec.ts new file mode 100644 index 000000000..0e2085c54 --- /dev/null +++ b/src/utils/writeAppClient.spec.ts @@ -0,0 +1,39 @@ +import type { Client } from '../client/interfaces/Client'; +import { HttpClient } from '../HttpClient'; +import { writeFile } from './fileSystem'; +import { Templates } from './registerHandlebarTemplates'; +import { writeAppClient } from './writeAppClient'; + +jest.mock('./fileSystem'); + +describe('writeAppClient', () => { + it('should write to filesystem', async () => { + const client: Client = { + server: 'http://localhost:8080', + version: 'v1', + models: [], + services: [], + }; + + const templates: Templates = { + index: () => 'index', + client: () => 'client', + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + core: { + settings: () => 'settings', + apiError: () => 'apiError', + apiRequestOptions: () => 'apiRequestOptions', + apiResult: () => 'apiResult', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', + }, + }; + + await writeAppClient(client, templates, '/', HttpClient.FETCH, 'AppClient'); + expect(writeFile).toBeCalledWith('/client.ts', 'client'); + }); +}); diff --git a/src/utils/writeAppClient.ts b/src/utils/writeAppClient.ts new file mode 100644 index 000000000..fc2a86b6e --- /dev/null +++ b/src/utils/writeAppClient.ts @@ -0,0 +1,32 @@ +import { resolve } from 'path'; + +import { Client } from '../client/interfaces/Client'; +import { HttpClient } from '../HttpClient'; +import { writeFile } from './fileSystem'; +import { getHttpRequestName } from './getHttpRequestName'; +import { Templates } from './registerHandlebarTemplates'; +import { sortServicesByName } from './sortServicesByName'; + +/** + * Generate App Client class using the Handlebar template and write to disk. + * @param client Client object, containing, models, schemas and services + * @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 clientName Client class name + */ +export async function writeAppClient(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, clientName: string): Promise { + await writeFile( + resolve(outputPath, 'client.ts'), + templates.client({ + services: sortServicesByName(client.services).map(s => ({ + name: s.name, + shortName: s.name.replace('Service', '').toLowerCase(), + })), + clientName, + httpClientRequest: getHttpRequestName(httpClient), + server: client.server, + version: client.version, + }) + ); +} diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index 75fdf1771..7f9d77810 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -17,6 +17,7 @@ describe('writeClient', () => { const templates: Templates = { index: () => 'index', + client: () => 'client', exports: { model: () => 'model', schema: () => 'schema', @@ -27,11 +28,12 @@ describe('writeClient', () => { apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - request: () => 'request', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', }, }; - await writeClient(client, templates, './dist', HttpClient.FETCH, false, false, true, true, true, true); + await writeClient(client, templates, './dist', HttpClient.FETCH, false, false, true, true, true, true, false, 'AppClient'); expect(rmdir).toBeCalled(); expect(mkdir).toBeCalled(); diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index 2cfc4e364..7b556f954 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -5,6 +5,7 @@ import { HttpClient } from '../HttpClient'; import { mkdir, rmdir } from './fileSystem'; import { isSubDirectory } from './isSubdirectory'; import { Templates } from './registerHandlebarTemplates'; +import { writeAppClient } from './writeAppClient'; import { writeClientCore } from './writeClientCore'; import { writeClientIndex } from './writeClientIndex'; import { writeClientModels } from './writeClientModels'; @@ -23,6 +24,8 @@ import { writeClientServices } from './writeClientServices'; * @param exportServices: Generate services * @param exportModels: Generate models * @param exportSchemas: Generate schemas + * @param exportClient: Generate client class + * @param clientName: Custom client class name * @param request: Path to custom request file */ export async function writeClient( @@ -36,6 +39,8 @@ export async function writeClient( exportServices: boolean, exportModels: boolean, exportSchemas: boolean, + exportClient: boolean, + clientName: string, request?: string ): Promise { const outputPath = resolve(process.cwd(), output); @@ -51,13 +56,13 @@ export async function writeClient( if (exportCore) { await rmdir(outputPathCore); await mkdir(outputPathCore); - await writeClientCore(client, templates, outputPathCore, httpClient, request); + await writeClientCore(client, templates, outputPathCore, httpClient, exportClient, request); } if (exportServices) { await rmdir(outputPathServices); await mkdir(outputPathServices); - await writeClientServices(client.services, templates, outputPathServices, httpClient, useUnionTypes, useOptions); + await writeClientServices(client.services, templates, outputPathServices, httpClient, useUnionTypes, useOptions, exportClient); } if (exportSchemas) { @@ -72,8 +77,12 @@ export async function writeClient( await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes); } - if (exportCore || exportServices || exportSchemas || exportModels) { + if (exportClient) { + await writeAppClient(client, templates, outputPath, httpClient, clientName); + } + + if (exportCore || exportServices || exportSchemas || exportModels || exportClient) { await mkdir(outputPath); - await writeClientIndex(client, templates, outputPath, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas); + await writeClientIndex(client, templates, outputPath, clientName, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, exportClient, httpClient); } } diff --git a/src/utils/writeClientCore.spec.ts b/src/utils/writeClientCore.spec.ts index 8f35776f8..e39d81214 100644 --- a/src/utils/writeClientCore.spec.ts +++ b/src/utils/writeClientCore.spec.ts @@ -7,36 +7,49 @@ import { writeClientCore } from './writeClientCore'; jest.mock('./fileSystem'); describe('writeClientCore', () => { - it('should write to filesystem', async () => { - const client: Client = { - server: 'http://localhost:8080', - version: '1.0', - models: [], - services: [], - }; + const client: Client = { + server: 'http://localhost:8080', + version: '1.0', + models: [], + services: [], + }; - const templates: Templates = { - index: () => 'index', - exports: { - model: () => 'model', - schema: () => 'schema', - service: () => 'service', - }, - core: { - settings: () => 'settings', - apiError: () => 'apiError', - apiRequestOptions: () => 'apiRequestOptions', - apiResult: () => 'apiResult', - request: () => 'request', - }, - }; + const templates: Templates = { + index: () => 'index', + client: () => 'client', + exports: { + model: () => 'model', + schema: () => 'schema', + service: () => 'service', + }, + core: { + settings: () => 'settings', + apiError: () => 'apiError', + apiRequestOptions: () => 'apiRequestOptions', + apiResult: () => 'apiResult', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', + }, + }; - await writeClientCore(client, templates, '/', HttpClient.FETCH); + it('should write to filesystem when exportClient false', async () => { + await writeClientCore(client, templates, '/', HttpClient.FETCH, false); expect(writeFile).toBeCalledWith('/OpenAPI.ts', 'settings'); expect(writeFile).toBeCalledWith('/ApiError.ts', 'apiError'); expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', 'apiRequestOptions'); expect(writeFile).toBeCalledWith('/ApiResult.ts', 'apiResult'); - expect(writeFile).toBeCalledWith('/request.ts', 'request'); + expect(writeFile).toBeCalledWith('/request.ts', 'concreteHttpRequest'); + }); + + it('should write to filesystem when exportClient true', async () => { + await writeClientCore(client, templates, '/', HttpClient.FETCH, true); + + expect(writeFile).toBeCalledWith('/OpenAPI.ts', 'settings'); + expect(writeFile).toBeCalledWith('/ApiError.ts', 'apiError'); + expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', 'apiRequestOptions'); + expect(writeFile).toBeCalledWith('/ApiResult.ts', 'apiResult'); + expect(writeFile).toBeCalledWith('/BaseHttpRequest.ts', 'baseHttpRequest'); + expect(writeFile).toBeCalledWith('/FetchHttpRequest.ts', 'concreteHttpRequest'); }); }); diff --git a/src/utils/writeClientCore.ts b/src/utils/writeClientCore.ts index c7cac72f8..21a4baa4e 100644 --- a/src/utils/writeClientCore.ts +++ b/src/utils/writeClientCore.ts @@ -3,6 +3,7 @@ import { resolve } from 'path'; import type { Client } from '../client/interfaces/Client'; import { HttpClient } from '../HttpClient'; import { copyFile, exists, writeFile } from './fileSystem'; +import { getHttpRequestName } from './getHttpRequestName'; import { Templates } from './registerHandlebarTemplates'; /** @@ -11,20 +12,27 @@ import { Templates } from './registerHandlebarTemplates'; * @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 exportClient Create client class * @param request: Path to custom request file */ -export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, request?: string): Promise { +export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, exportClient: boolean, request?: string): Promise { const context = { httpClient, server: client.server, version: client.version, + exportClient, }; await writeFile(resolve(outputPath, 'OpenAPI.ts'), templates.core.settings(context)); await writeFile(resolve(outputPath, 'ApiError.ts'), templates.core.apiError({})); await writeFile(resolve(outputPath, 'ApiRequestOptions.ts'), templates.core.apiRequestOptions({})); await writeFile(resolve(outputPath, 'ApiResult.ts'), templates.core.apiResult({})); - await writeFile(resolve(outputPath, 'request.ts'), templates.core.request(context)); + if (exportClient) { + await writeFile(resolve(outputPath, 'BaseHttpRequest.ts'), templates.core.baseHttpRequest({})); + await writeFile(resolve(outputPath, `${getHttpRequestName(httpClient)}.ts`), templates.core.concreteHttpRequest(context)); + } else { + await writeFile(resolve(outputPath, `request.ts`), templates.core.concreteHttpRequest(context)); + } if (request) { const requestFile = resolve(process.cwd(), request); diff --git a/src/utils/writeClientIndex.spec.ts b/src/utils/writeClientIndex.spec.ts index ded3932fb..be821193b 100644 --- a/src/utils/writeClientIndex.spec.ts +++ b/src/utils/writeClientIndex.spec.ts @@ -1,4 +1,5 @@ import type { Client } from '../client/interfaces/Client'; +import { HttpClient } from '../HttpClient'; import { writeFile } from './fileSystem'; import { Templates } from './registerHandlebarTemplates'; import { writeClientIndex } from './writeClientIndex'; @@ -16,6 +17,7 @@ describe('writeClientIndex', () => { const templates: Templates = { index: () => 'index', + client: () => 'client', exports: { model: () => 'model', schema: () => 'schema', @@ -26,11 +28,12 @@ describe('writeClientIndex', () => { apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - request: () => 'request', + baseHttpRequest: () => 'baseHttpClient', + concreteHttpRequest: () => 'concreteHttpClient', }, }; - await writeClientIndex(client, templates, '/', true, true, true, true, true); + await writeClientIndex(client, templates, '/', 'AppClient', true, true, true, true, false, false, HttpClient.FETCH); expect(writeFile).toBeCalledWith('/index.ts', 'index'); }); diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index ca2e1645d..356148d3d 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -1,7 +1,9 @@ import { resolve } from 'path'; import type { Client } from '../client/interfaces/Client'; +import { HttpClient } from '../HttpClient'; import { writeFile } from './fileSystem'; +import { getHttpRequestName } from './getHttpRequestName'; import { Templates } from './registerHandlebarTemplates'; import { sortModelsByName } from './sortModelsByName'; import { sortServicesByName } from './sortServicesByName'; @@ -13,21 +15,27 @@ import { sortServicesByName } from './sortServicesByName'; * @param client Client object, containing, models, schemas and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to + * @param clientName Custom client class name * @param useUnionTypes Use union types instead of enums * @param exportCore: Generate core * @param exportServices: Generate services * @param exportModels: Generate models * @param exportSchemas: Generate schemas + * @param exportClient: Generate client class + * @param httpClient The selected httpClient (fetch, xhr or node) */ export async function writeClientIndex( client: Client, templates: Templates, outputPath: string, + clientName: string, useUnionTypes: boolean, exportCore: boolean, exportServices: boolean, exportModels: boolean, - exportSchemas: boolean + exportSchemas: boolean, + exportClient: boolean, + httpClient: HttpClient ): Promise { await writeFile( resolve(outputPath, 'index.ts'), @@ -36,11 +44,14 @@ export async function writeClientIndex( exportServices, exportModels, exportSchemas, + exportClient, useUnionTypes, server: client.server, version: client.version, models: sortModelsByName(client.models), services: sortServicesByName(client.services), + httpRequestName: getHttpRequestName(httpClient), + clientName, }) ); } diff --git a/src/utils/writeClientModels.spec.ts b/src/utils/writeClientModels.spec.ts index 0186812b7..44227dc12 100644 --- a/src/utils/writeClientModels.spec.ts +++ b/src/utils/writeClientModels.spec.ts @@ -30,6 +30,7 @@ describe('writeClientModels', () => { const templates: Templates = { index: () => 'index', + client: () => 'client', exports: { model: () => 'model', schema: () => 'schema', @@ -40,7 +41,8 @@ describe('writeClientModels', () => { apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - request: () => 'request', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', }, }; diff --git a/src/utils/writeClientSchemas.spec.ts b/src/utils/writeClientSchemas.spec.ts index fd69fc1b4..1dd4e9196 100644 --- a/src/utils/writeClientSchemas.spec.ts +++ b/src/utils/writeClientSchemas.spec.ts @@ -30,6 +30,7 @@ describe('writeClientSchemas', () => { const templates: Templates = { index: () => 'index', + client: () => 'client', exports: { model: () => 'model', schema: () => 'schema', @@ -40,7 +41,8 @@ describe('writeClientSchemas', () => { apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - request: () => 'request', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', }, }; diff --git a/src/utils/writeClientServices.spec.ts b/src/utils/writeClientServices.spec.ts index a1be88096..91c105121 100644 --- a/src/utils/writeClientServices.spec.ts +++ b/src/utils/writeClientServices.spec.ts @@ -18,6 +18,7 @@ describe('writeClientServices', () => { const templates: Templates = { index: () => 'index', + client: () => 'client', exports: { model: () => 'model', schema: () => 'schema', @@ -28,11 +29,12 @@ describe('writeClientServices', () => { apiError: () => 'apiError', apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', - request: () => 'request', + baseHttpRequest: () => 'baseHttpRequest', + concreteHttpRequest: () => 'concreteHttpRequest', }, }; - await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false); + await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false, false); expect(writeFile).toBeCalledWith('/MyService.ts', 'service'); }); diff --git a/src/utils/writeClientServices.ts b/src/utils/writeClientServices.ts index 8f82e0ce4..934d29bd8 100644 --- a/src/utils/writeClientServices.ts +++ b/src/utils/writeClientServices.ts @@ -4,6 +4,7 @@ import type { Service } from '../client/interfaces/Service'; import { HttpClient } from '../HttpClient'; import { writeFile } from './fileSystem'; import { format } from './format'; +import { getHttpRequestName } from './getHttpRequestName'; import { Templates } from './registerHandlebarTemplates'; const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION'; @@ -16,8 +17,17 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION'; * @param httpClient The selected httpClient (fetch, xhr or node) * @param useUnionTypes Use union types instead of enums * @param useOptions Use options or arguments functions + * @param exportClient Create client class */ -export async function writeClientServices(services: Service[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean, useOptions: boolean): Promise { +export async function writeClientServices( + services: Service[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + useOptions: boolean, + exportClient: boolean +): Promise { for (const service of services) { const file = resolve(outputPath, `${service.name}.ts`); const useVersion = service.operations.some(operation => operation.path.includes(VERSION_TEMPLATE_STRING)); @@ -27,6 +37,8 @@ export async function writeClientServices(services: Service[], templates: Templa useUnionTypes, useVersion, useOptions, + exportClient, + httpClientRequest: getHttpRequestName(httpClient), }); await writeFile(file, format(templateResult)); } diff --git a/test/__snapshots__/index.client.spec.js.snap b/test/__snapshots__/index.client.spec.js.snap new file mode 100644 index 000000000..b92bed6d9 --- /dev/null +++ b/test/__snapshots__/index.client.spec.js.snap @@ -0,0 +1,5416 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/client.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { FetchHttpRequest } from './core/FetchHttpRequest'; +import { CollectionFormatService } from './services/CollectionFormatService'; +import { ComplexService } from './services/ComplexService'; +import { DefaultsService } from './services/DefaultsService'; +import { DuplicateService } from './services/DuplicateService'; +import { HeaderService } from './services/HeaderService'; +import { NoContentService } from './services/NoContentService'; +import { ParametersService } from './services/ParametersService'; +import { ResponseService } from './services/ResponseService'; +import { SimpleService } from './services/SimpleService'; +import { TypesService } from './services/TypesService'; + +export class TestClient { + readonly collectionformat: CollectionFormatService; + readonly complex: ComplexService; + readonly defaults: DefaultsService; + readonly duplicate: DuplicateService; + readonly header: HeaderService; + readonly nocontent: NoContentService; + readonly parameters: ParametersService; + readonly response: ResponseService; + readonly simple: SimpleService; + readonly types: TypesService; + readonly request: BaseHttpRequest; + + constructor(openApiConfig?: OpenAPIConfig, HttpRequest: new (config: OpenAPIConfig) => BaseHttpRequest = FetchHttpRequest) { + this.request = new HttpRequest({ + BASE: openApiConfig?.BASE ?? 'http://localhost:3000/base', + VERSION: openApiConfig?.VERSION ?? '1.0', + WITH_CREDENTIALS: openApiConfig?.WITH_CREDENTIALS ?? false, + TOKEN: openApiConfig?.TOKEN, + USERNAME: openApiConfig?.USERNAME, + PASSWORD: openApiConfig?.PASSWORD, + HEADERS: openApiConfig?.HEADERS, + }); + this.collectionformat = new CollectionFormatService(this.request); + this.complex = new ComplexService(this.request); + this.defaults = new DefaultsService(this.request); + this.duplicate = new DuplicateService(this.request); + this.header = new HeaderService(this.request); + this.nocontent = new NoContentService(this.request); + this.parameters = new ParametersService(this.request); + this.response = new ResponseService(this.request); + this.simple = new SimpleService(this.request); + this.types = new TypesService(this.request); + } +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/ApiError.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +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; + + constructor(response: ApiResult, message: string) { + super(message); + + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + } +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/ApiRequestOptions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly path: string; + 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[`v2 should generate with exportClient: ./test/generated/v2_client/core/ApiResult.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/BaseHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; + +export class BaseHttpRequest { + readonly openApiConfig: OpenAPIConfig; + + constructor(openApiConfig: OpenAPIConfig) { + this.openApiConfig = openApiConfig; + } + + async request(options: ApiRequestOptions): Promise { + throw new Error('Not Implemented'); + } +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/FetchHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +function isBlob(value: any): value is Blob { + return value instanceof Blob; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = btoa(\`\${username}:\${password}\`); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBlob(options.body)) { + headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + const request: RequestInit = { + method: options.method, + headers: await getHeaders(options, config), + body: getRequestBody(options), + }; + if (config.WITH_CREDENTIALS) { + request.credentials = 'include'; + } + return await fetch(url, request); +} + +function getResponseHeader(response: Response, responseHeader?: string): string | null { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +async function getResponseBody(response: Response): Promise { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} + +export class FetchHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; + } +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/OpenAPI.ts 1`] = ` +"/* 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; + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/index.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export type { ApiRequestOptions } from './core/ApiRequestOptions'; +export type { ApiResult } from './core/ApiResult'; +export type { OpenAPIConfig } from './core/OpenAPI'; +export { BaseHttpRequest } from './core/BaseHttpRequest'; +export { FetchHttpRequest } from './core/FetchHttpRequest'; + +export type { ArrayWithArray } from './models/ArrayWithArray'; +export type { ArrayWithBooleans } from './models/ArrayWithBooleans'; +export type { ArrayWithNumbers } from './models/ArrayWithNumbers'; +export type { ArrayWithProperties } from './models/ArrayWithProperties'; +export type { ArrayWithReferences } from './models/ArrayWithReferences'; +export type { ArrayWithStrings } from './models/ArrayWithStrings'; +export type { Date } from './models/Date'; +export type { DictionaryWithArray } from './models/DictionaryWithArray'; +export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary'; +export type { DictionaryWithProperties } from './models/DictionaryWithProperties'; +export type { DictionaryWithReference } from './models/DictionaryWithReference'; +export type { DictionaryWithString } from './models/DictionaryWithString'; +export { EnumFromDescription } from './models/EnumFromDescription'; +export { EnumWithExtensions } from './models/EnumWithExtensions'; +export { EnumWithNumbers } from './models/EnumWithNumbers'; +export { EnumWithStrings } from './models/EnumWithStrings'; +export type { ModelThatExtends } from './models/ModelThatExtends'; +export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends'; +export type { ModelWithArray } from './models/ModelWithArray'; +export type { ModelWithBoolean } from './models/ModelWithBoolean'; +export type { ModelWithCircularReference } from './models/ModelWithCircularReference'; +export type { ModelWithDictionary } from './models/ModelWithDictionary'; +export type { ModelWithDuplicateImports } from './models/ModelWithDuplicateImports'; +export type { ModelWithDuplicateProperties } from './models/ModelWithDuplicateProperties'; +export { ModelWithEnum } from './models/ModelWithEnum'; +export { ModelWithEnumFromDescription } from './models/ModelWithEnumFromDescription'; +export type { ModelWithInteger } from './models/ModelWithInteger'; +export type { ModelWithNestedEnums } from './models/ModelWithNestedEnums'; +export type { ModelWithNestedProperties } from './models/ModelWithNestedProperties'; +export type { ModelWithNullableString } from './models/ModelWithNullableString'; +export type { ModelWithOrderedProperties } from './models/ModelWithOrderedProperties'; +export type { ModelWithPattern } from './models/ModelWithPattern'; +export type { ModelWithProperties } from './models/ModelWithProperties'; +export type { ModelWithReference } from './models/ModelWithReference'; +export type { ModelWithString } from './models/ModelWithString'; +export type { MultilineComment } from './models/MultilineComment'; +export type { SimpleBoolean } from './models/SimpleBoolean'; +export type { SimpleFile } from './models/SimpleFile'; +export type { SimpleInteger } from './models/SimpleInteger'; +export type { SimpleReference } from './models/SimpleReference'; +export type { SimpleString } from './models/SimpleString'; +export type { SimpleStringWithPattern } from './models/SimpleStringWithPattern'; + +export { $ArrayWithArray } from './schemas/$ArrayWithArray'; +export { $ArrayWithBooleans } from './schemas/$ArrayWithBooleans'; +export { $ArrayWithNumbers } from './schemas/$ArrayWithNumbers'; +export { $ArrayWithProperties } from './schemas/$ArrayWithProperties'; +export { $ArrayWithReferences } from './schemas/$ArrayWithReferences'; +export { $ArrayWithStrings } from './schemas/$ArrayWithStrings'; +export { $Date } from './schemas/$Date'; +export { $DictionaryWithArray } from './schemas/$DictionaryWithArray'; +export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary'; +export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties'; +export { $DictionaryWithReference } from './schemas/$DictionaryWithReference'; +export { $DictionaryWithString } from './schemas/$DictionaryWithString'; +export { $EnumFromDescription } from './schemas/$EnumFromDescription'; +export { $EnumWithExtensions } from './schemas/$EnumWithExtensions'; +export { $EnumWithNumbers } from './schemas/$EnumWithNumbers'; +export { $EnumWithStrings } from './schemas/$EnumWithStrings'; +export { $ModelThatExtends } from './schemas/$ModelThatExtends'; +export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends'; +export { $ModelWithArray } from './schemas/$ModelWithArray'; +export { $ModelWithBoolean } from './schemas/$ModelWithBoolean'; +export { $ModelWithCircularReference } from './schemas/$ModelWithCircularReference'; +export { $ModelWithDictionary } from './schemas/$ModelWithDictionary'; +export { $ModelWithDuplicateImports } from './schemas/$ModelWithDuplicateImports'; +export { $ModelWithDuplicateProperties } from './schemas/$ModelWithDuplicateProperties'; +export { $ModelWithEnum } from './schemas/$ModelWithEnum'; +export { $ModelWithEnumFromDescription } from './schemas/$ModelWithEnumFromDescription'; +export { $ModelWithInteger } from './schemas/$ModelWithInteger'; +export { $ModelWithNestedEnums } from './schemas/$ModelWithNestedEnums'; +export { $ModelWithNestedProperties } from './schemas/$ModelWithNestedProperties'; +export { $ModelWithNullableString } from './schemas/$ModelWithNullableString'; +export { $ModelWithOrderedProperties } from './schemas/$ModelWithOrderedProperties'; +export { $ModelWithPattern } from './schemas/$ModelWithPattern'; +export { $ModelWithProperties } from './schemas/$ModelWithProperties'; +export { $ModelWithReference } from './schemas/$ModelWithReference'; +export { $ModelWithString } from './schemas/$ModelWithString'; +export { $MultilineComment } from './schemas/$MultilineComment'; +export { $SimpleBoolean } from './schemas/$SimpleBoolean'; +export { $SimpleFile } from './schemas/$SimpleFile'; +export { $SimpleInteger } from './schemas/$SimpleInteger'; +export { $SimpleReference } from './schemas/$SimpleReference'; +export { $SimpleString } from './schemas/$SimpleString'; +export { $SimpleStringWithPattern } from './schemas/$SimpleStringWithPattern'; + +export { CollectionFormatService } from './services/CollectionFormatService'; +export { ComplexService } from './services/ComplexService'; +export { DefaultsService } from './services/DefaultsService'; +export { DuplicateService } from './services/DuplicateService'; +export { HeaderService } from './services/HeaderService'; +export { NoContentService } from './services/NoContentService'; +export { ParametersService } from './services/ParametersService'; +export { ResponseService } from './services/ResponseService'; +export { SimpleService } from './services/SimpleService'; +export { TypesService } from './services/TypesService'; + +export { TestClient } from './client'; +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithBooleans.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + foo?: string, + bar?: string, +}>;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithReferences.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ArrayWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/Date.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a type-only model that defines Date as a string + */ +export type Date = string;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/DictionaryWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = Record>;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/DictionaryWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = Record>;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/DictionaryWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = Record;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/DictionaryWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a string reference + */ +export type DictionaryWithReference = Record;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/DictionaryWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a string dictionary + */ +export type DictionaryWithString = Record;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/EnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Success=1,Warning=2,Error=3 + */ +export enum EnumFromDescription { + SUCCESS = 1, + WARNING = 2, + ERROR = 3, +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/EnumWithExtensions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with numbers + */ +export enum EnumWithExtensions { + /** + * Used when the status of something is successful + */ + CUSTOM_SUCCESS = 200, + /** + * Used when the status of something has a warning + */ + CUSTOM_WARNING = 400, + /** + * Used when the status of something has an error + */ + CUSTOM_ERROR = 500, +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/EnumWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with numbers + */ +export enum EnumWithNumbers { + '_1' = 1, + '_2' = 2, + '_3' = 3, + '_1.1' = 1.1, + '_1.2' = 1.2, + '_1.3' = 1.3, + '_100' = 100, + '_200' = 200, + '_300' = 300, + '_-100' = -100, + '_-200' = -200, + '_-300' = -300, + '_-1.1' = -1.1, + '_-1.2' = -1.2, + '_-1.3' = -1.3, +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/EnumWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with strings + */ +export enum EnumWithStrings { + SUCCESS = 'Success', + WARNING = 'Warning', + ERROR = 'Error', +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelThatExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = (ModelWithString & { + propExtendsA?: string, + propExtendsB?: ModelWithString, +}); +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelThatExtendsExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelThatExtends } from './ModelThatExtends'; +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = (ModelWithString & ModelThatExtends & { + propExtendsC?: string, + propExtendsD?: ModelWithString, +}); +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithCircularReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: Record; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithDuplicateImports.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithDuplicateProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithEnum.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + test?: ModelWithEnum.test; + /** + * These are the HTTP error code enums + */ + statusCode?: ModelWithEnum.statusCode; + /** + * Simple boolean enum + */ + bool?: boolean; +} + +export namespace ModelWithEnum { + + /** + * This is a simple enum with strings + */ + export enum test { + SUCCESS = 'Success', + WARNING = 'Warning', + ERROR = 'Error', + } + + /** + * These are the HTTP error code enums + */ + export enum statusCode { + _100 = '100', + _200_FOO = '200 FOO', + _300_FOO_BAR = '300 FOO_BAR', + _400_FOO_BAR = '400 foo-bar', + _500_FOO_BAR = '500 foo.bar', + _600_FOO_BAR = '600 foo&bar', + } + + +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithEnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: ModelWithEnumFromDescription.test; +} + +export namespace ModelWithEnumFromDescription { + + /** + * Success=1,Warning=2,Error=3 + */ + export enum test { + SUCCESS = 1, + WARNING = 2, + ERROR = 3, + } + + +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithNestedEnums.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: Record; + dictionaryWithEnumFromDescription?: Record; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array<1 | 2 | 3>; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithNestedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string, + }, + }; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithNullableString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp: string | null; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithOrderedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithProperties } from './ModelWithProperties'; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/ModelWithString.ts 1`] = ` +"/* 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[`v2 should generate with exportClient: ./test/generated/v2_client/models/MultilineComment.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Testing multiline comments. + * This must go to the next line. + * + * This will contain a break. + */ +export type MultilineComment = number;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleFile.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple file + */ +export type SimpleFile = Blob;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple number + */ +export type SimpleInteger = number;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple string + */ +export type SimpleString = string;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/models/SimpleStringWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string;" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithArray = { + type: 'array', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithBooleans.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithBooleans = { + type: 'array', + contains: { + type: 'boolean', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithNumbers = { + type: 'array', + contains: { + type: 'number', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithProperties = { + type: 'array', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithReferences.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithReferences = { + type: 'array', + contains: { + type: 'ModelWithString', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ArrayWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithStrings = { + type: 'array', + contains: { + type: 'string', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$Date.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $Date = { + type: 'string', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$DictionaryWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithArray = { + type: 'dictionary', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$DictionaryWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithDictionary = { + type: 'dictionary', + contains: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$DictionaryWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithProperties = { + type: 'dictionary', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$DictionaryWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithReference = { + type: 'dictionary', + contains: { + type: 'ModelWithString', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$DictionaryWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithString = { + type: 'dictionary', + contains: { + type: 'string', + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$EnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumFromDescription = { + type: 'Enum', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$EnumWithExtensions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithExtensions = { + type: 'Enum', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$EnumWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithNumbers = { + type: 'Enum', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$EnumWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithStrings = { + type: 'Enum', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelThatExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelThatExtends = { + type: 'all-of', + contains: [{ + type: 'ModelWithString', + }, { + properties: { + propExtendsA: { + type: 'string', + }, + propExtendsB: { + type: 'ModelWithString', + }, + }, + }], +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelThatExtendsExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelThatExtendsExtends = { + type: 'all-of', + contains: [{ + type: 'ModelWithString', + }, { + type: 'ModelThatExtends', + }, { + properties: { + propExtendsC: { + type: 'string', + }, + propExtendsD: { + type: 'ModelWithString', + }, + }, + }], +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithArray = { + properties: { + prop: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, + propWithFile: { + type: 'array', + contains: { + type: 'File', + }, + }, + propWithNumber: { + type: 'array', + contains: { + type: 'number', + }, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithBoolean = { + properties: { + prop: { + type: 'boolean', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithCircularReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithCircularReference = { + properties: { + prop: { + type: 'ModelWithCircularReference', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDictionary = { + properties: { + prop: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithDuplicateImports.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDuplicateImports = { + properties: { + propA: { + type: 'ModelWithString', + }, + propB: { + type: 'ModelWithString', + }, + propC: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithDuplicateProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDuplicateProperties = { + properties: { + prop: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithEnum.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithEnum = { + properties: { + test: { + type: 'Enum', + }, + statusCode: { + type: 'Enum', + }, + bool: { + type: 'boolean', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithEnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithEnumFromDescription = { + properties: { + test: { + type: 'Enum', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithInteger = { + properties: { + prop: { + type: 'number', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithNestedEnums.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithNestedEnums = { + properties: { + dictionaryWithEnum: { + type: 'dictionary', + contains: { + type: 'Enum', + }, + }, + dictionaryWithEnumFromDescription: { + type: 'dictionary', + contains: { + type: 'Enum', + }, + }, + arrayWithEnum: { + type: 'array', + contains: { + type: 'Enum', + }, + }, + arrayWithDescription: { + type: 'array', + contains: { + type: 'Enum', + }, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithNestedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithNestedProperties = { + properties: { + first: { + properties: { + second: { + properties: { + third: { + type: 'string', + isReadOnly: true, + isRequired: true, + }, + }, + isReadOnly: true, + isRequired: true, + }, + }, + isReadOnly: true, + isRequired: true, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithNullableString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithNullableString = { + properties: { + nullableProp: { + type: 'string', + isNullable: true, + }, + nullableRequiredProp: { + type: 'string', + isRequired: true, + isNullable: true, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithOrderedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithOrderedProperties = { + properties: { + zebra: { + type: 'string', + }, + apple: { + type: 'string', + }, + hawaii: { + type: 'string', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithPattern = { + properties: { + key: { + type: 'string', + isRequired: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', + }, + name: { + type: 'string', + isRequired: true, + maxLength: 255, + }, + enabled: { + type: 'boolean', + isReadOnly: true, + }, + modified: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + id: { + type: 'string', + pattern: '^\\\\\\\\d{2}-\\\\\\\\d{3}-\\\\\\\\d{4}$', + }, + text: { + type: 'string', + pattern: '^\\\\\\\\w+$', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithProperties = { + properties: { + required: { + type: 'string', + isRequired: true, + }, + requiredAndReadOnly: { + type: 'string', + isReadOnly: true, + isRequired: true, + }, + string: { + type: 'string', + }, + number: { + type: 'number', + }, + boolean: { + type: 'boolean', + }, + reference: { + type: 'ModelWithString', + }, + 'property with space': { + type: 'string', + }, + default: { + type: 'string', + }, + try: { + type: 'string', + }, + '@namespace.string': { + type: 'string', + isReadOnly: true, + }, + '@namespace.integer': { + type: 'number', + isReadOnly: true, + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithReference = { + properties: { + prop: { + type: 'ModelWithProperties', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$ModelWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithString = { + properties: { + prop: { + type: 'string', + }, + }, +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$MultilineComment.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $MultilineComment = { + type: 'number', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleBoolean = { + type: 'boolean', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleFile.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleFile = { + type: 'File', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleInteger = { + type: 'number', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleReference = { + type: 'ModelWithString', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleString = { + type: 'string', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/schemas/$SimpleStringWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleStringWithPattern = { + type: 'string', + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', +};" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/CollectionFormatService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CollectionFormatService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterArrayCsv This is an array parameter that is send as csv format (comma-separated values) + * @param parameterArraySsv This is an array parameter that is send as ssv format (space-separated values) + * @param parameterArrayTsv This is an array parameter that is send as tsv format (tab-separated values) + * @param parameterArrayPipes This is an array parameter that is send as pipes format (pipe-separated values) + * @param parameterArrayMulti This is an array parameter that is send as multi format (multiple parameter instances) + * @throws ApiError + */ + public async collectionFormat( + parameterArrayCsv: Array, + parameterArraySsv: Array, + parameterArrayTsv: Array, + parameterArrayPipes: Array, + parameterArrayMulti: Array, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/collectionFormat\`, + query: { + 'parameterArrayCSV': parameterArrayCsv, + 'parameterArraySSV': parameterArraySsv, + 'parameterArrayTSV': parameterArrayTsv, + 'parameterArrayPipes': parameterArrayPipes, + 'parameterArrayMulti': parameterArrayMulti, + }, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/ComplexService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ComplexService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterObject Parameter containing object + * @param parameterReference Parameter containing reference + * @returns ModelWithString Successful response + * @throws ApiError + */ + public async complexTypes( + parameterObject: { + first?: { + second?: { + third?: string, + }, + }, + }, + parameterReference: ModelWithString, + ): Promise> { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/complex\`, + query: { + 'parameterObject': parameterObject, + 'parameterReference': parameterReference, + }, + errors: { + 400: \`400 server error\`, + 500: \`500 server error\`, + }, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/DefaultsService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class DefaultsService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterString This is a simple string with default value + * @param parameterNumber This is a simple number with default value + * @param parameterBoolean This is a simple boolean with default value + * @param parameterEnum This is a simple enum with default value + * @param parameterModel This is a simple model with default value + * @throws ApiError + */ + public async callWithDefaultParameters( + parameterString: string = 'Hello World!', + parameterNumber: number = 123, + parameterBoolean: boolean = true, + parameterEnum: 'Success' | 'Warning' | 'Error' = 'Success', + parameterModel: ModelWithString = { + \\"prop\\": \\"Hello World!\\" + }, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + return result.body; + } + + /** + * @param parameterString This is a simple string that is optional with default value + * @param parameterNumber This is a simple number that is optional with default value + * @param parameterBoolean This is a simple boolean that is optional with default value + * @param parameterEnum This is a simple enum that is optional with default value + * @param parameterModel This is a simple model that is optional with default value + * @throws ApiError + */ + public async callWithDefaultOptionalParameters( + parameterString: string = 'Hello World!', + parameterNumber: number = 123, + parameterBoolean: boolean = true, + parameterEnum: 'Success' | 'Warning' | 'Error' = 'Success', + parameterModel: ModelWithString = { + \\"prop\\": \\"Hello World!\\" + }, + ): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + return result.body; + } + + /** + * @param parameterStringWithNoDefault This is a string with no default + * @param parameterOptionalStringWithDefault This is a optional string with default + * @param parameterOptionalStringWithEmptyDefault This is a optional string with empty default + * @param parameterOptionalStringWithNoDefault This is a optional string with no default + * @param parameterStringWithDefault This is a string with default + * @param parameterStringWithEmptyDefault This is a string with empty default + * @throws ApiError + */ + public async callToTestOrderOfParams( + parameterStringWithNoDefault: string, + parameterOptionalStringWithDefault: string = 'Hello World!', + parameterOptionalStringWithEmptyDefault: string = '', + parameterOptionalStringWithNoDefault?: string, + parameterStringWithDefault: string = 'Hello World!', + parameterStringWithEmptyDefault: string = '', + ): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterStringWithNoDefault': parameterStringWithNoDefault, + 'parameterOptionalStringWithDefault': parameterOptionalStringWithDefault, + 'parameterOptionalStringWithEmptyDefault': parameterOptionalStringWithEmptyDefault, + 'parameterOptionalStringWithNoDefault': parameterOptionalStringWithNoDefault, + 'parameterStringWithDefault': parameterStringWithDefault, + 'parameterStringWithEmptyDefault': parameterStringWithEmptyDefault, + }, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/DuplicateService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class DuplicateService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @throws ApiError + */ + public async duplicateName(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName1(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName2(): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName3(): Promise { + const result = await this.httpRequest.request({ + method: 'DELETE', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/HeaderService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class HeaderService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns string Successful response + * @throws ApiError + */ + public async callWithResultFromHeader(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/header\`, + responseHeader: 'operation-location', + errors: { + 400: \`400 server error\`, + 500: \`500 server error\`, + }, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/NoContentService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class NoContentService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns void + * @throws ApiError + */ + public async callWithNoContentResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/no-content\`, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/ParametersService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ParametersService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterHeader This is the parameter that goes into the header + * @param parameterQuery This is the parameter that goes into the query params + * @param parameterForm This is the parameter that goes into the form data + * @param parameterBody This is the parameter that is send as request body + * @param parameterPath This is the parameter that goes into the path + * @throws ApiError + */ + public async callWithParameters( + parameterHeader: string, + parameterQuery: string, + parameterForm: string, + parameterBody: string, + parameterPath: string, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\${parameterPath}\`, + headers: { + 'parameterHeader': parameterHeader, + }, + query: { + 'parameterQuery': parameterQuery, + }, + formData: { + 'parameterForm': parameterForm, + }, + body: parameterBody, + }); + return result.body; + } + + /** + * @param parameterHeader This is the parameter that goes into the request header + * @param parameterQuery This is the parameter that goes into the request query params + * @param parameterForm This is the parameter that goes into the request form data + * @param parameterBody This is the parameter that is send as request body + * @param parameterPath1 This is the parameter that goes into the path + * @param parameterPath2 This is the parameter that goes into the path + * @param parameterPath3 This is the parameter that goes into the path + * @param _default This is the parameter with a reserved keyword + * @throws ApiError + */ + public async callWithWeirdParameterNames( + parameterHeader: string, + parameterQuery: string, + parameterForm: string, + parameterBody: string, + parameterPath1?: string, + parameterPath2?: string, + parameterPath3?: string, + _default?: string, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\${parameterPath1}/\${parameterPath2}/\${parameterPath3}\`, + headers: { + 'parameter.header': parameterHeader, + }, + query: { + 'parameter-query': parameterQuery, + 'default': _default, + }, + formData: { + 'parameter_form': parameterForm, + }, + body: parameterBody, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/ResponseService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelThatExtends } from '../models/ModelThatExtends'; +import type { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends'; +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ResponseService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns ModelWithString Message for default response + * @throws ApiError + */ + public async callWithResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + }); + return result.body; + } + + /** + * @returns ModelWithString Message for default response + * @throws ApiError + */ + public async callWithDuplicateResponses(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + errors: { + 500: \`Message for 500 error\`, + 501: \`Message for 501 error\`, + 502: \`Message for 502 error\`, + }, + }); + return result.body; + } + + /** + * @returns any Message for 200 response + * @returns ModelWithString Message for default response + * @returns ModelThatExtends Message for 201 response + * @returns ModelThatExtendsExtends Message for 202 response + * @throws ApiError + */ + public async callWithResponses(): Promise<{ + readonly '@namespace.string'?: string, + readonly '@namespace.integer'?: number, + readonly value?: Array, + } | ModelWithString | ModelThatExtends | ModelThatExtendsExtends> { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + errors: { + 500: \`Message for 500 error\`, + 501: \`Message for 501 error\`, + 502: \`Message for 502 error\`, + }, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/SimpleService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class SimpleService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @throws ApiError + */ + public async getCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async putCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async postCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async deleteCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'DELETE', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async optionsCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'OPTIONS', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async headCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'HEAD', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async patchCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'PATCH', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + +}" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/services/TypesService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class TypesService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterArray This is an array parameter + * @param parameterDictionary This is a dictionary parameter + * @param parameterEnum This is an enum parameter + * @param parameterNumber This is a number parameter + * @param parameterString This is a string parameter + * @param parameterBoolean This is a boolean parameter + * @param parameterObject This is an object parameter + * @param id This is a number parameter + * @returns number Response is a simple number + * @returns string Response is a simple string + * @returns boolean Response is a simple boolean + * @returns any Response is a simple object + * @throws ApiError + */ + public async types( + parameterArray: Array, + parameterDictionary: Record, + parameterEnum: 'Success' | 'Warning' | 'Error', + parameterNumber: number = 123, + parameterString: string = 'default', + parameterBoolean: boolean = true, + parameterObject: any = null, + id?: number, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/types\`, + query: { + 'parameterArray': parameterArray, + 'parameterDictionary': parameterDictionary, + 'parameterEnum': parameterEnum, + 'parameterNumber': parameterNumber, + 'parameterString': parameterString, + 'parameterBoolean': parameterBoolean, + 'parameterObject': parameterObject, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/client.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { FetchHttpRequest } from './core/FetchHttpRequest'; +import { CollectionFormatService } from './services/CollectionFormatService'; +import { ComplexService } from './services/ComplexService'; +import { DefaultsService } from './services/DefaultsService'; +import { DuplicateService } from './services/DuplicateService'; +import { HeaderService } from './services/HeaderService'; +import { MultipartService } from './services/MultipartService'; +import { NoContentService } from './services/NoContentService'; +import { ParametersService } from './services/ParametersService'; +import { RequestBodyService } from './services/RequestBodyService'; +import { ResponseService } from './services/ResponseService'; +import { SimpleService } from './services/SimpleService'; +import { TypesService } from './services/TypesService'; +import { UploadService } from './services/UploadService'; + +export class TestClient { + readonly collectionformat: CollectionFormatService; + readonly complex: ComplexService; + readonly defaults: DefaultsService; + readonly duplicate: DuplicateService; + readonly header: HeaderService; + readonly multipart: MultipartService; + readonly nocontent: NoContentService; + readonly parameters: ParametersService; + readonly requestbody: RequestBodyService; + readonly response: ResponseService; + readonly simple: SimpleService; + readonly types: TypesService; + readonly upload: UploadService; + readonly request: BaseHttpRequest; + + constructor(openApiConfig?: OpenAPIConfig, HttpRequest: new (config: OpenAPIConfig) => BaseHttpRequest = FetchHttpRequest) { + this.request = new HttpRequest({ + BASE: openApiConfig?.BASE ?? 'http://localhost:3000/base', + VERSION: openApiConfig?.VERSION ?? '1.0', + WITH_CREDENTIALS: openApiConfig?.WITH_CREDENTIALS ?? false, + TOKEN: openApiConfig?.TOKEN, + USERNAME: openApiConfig?.USERNAME, + PASSWORD: openApiConfig?.PASSWORD, + HEADERS: openApiConfig?.HEADERS, + }); + this.collectionformat = new CollectionFormatService(this.request); + this.complex = new ComplexService(this.request); + this.defaults = new DefaultsService(this.request); + this.duplicate = new DuplicateService(this.request); + this.header = new HeaderService(this.request); + this.multipart = new MultipartService(this.request); + this.nocontent = new NoContentService(this.request); + this.parameters = new ParametersService(this.request); + this.requestbody = new RequestBodyService(this.request); + this.response = new ResponseService(this.request); + this.simple = new SimpleService(this.request); + this.types = new TypesService(this.request); + this.upload = new UploadService(this.request); + } +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/ApiError.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +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; + + constructor(response: ApiResult, message: string) { + super(message); + + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + } +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/ApiRequestOptions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly path: string; + 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 with exportClient: ./test/generated/v3_client/core/ApiResult.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/BaseHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; + +export class BaseHttpRequest { + readonly openApiConfig: OpenAPIConfig; + + constructor(openApiConfig: OpenAPIConfig) { + this.openApiConfig = openApiConfig; + } + + async request(options: ApiRequestOptions): Promise { + throw new Error('Not Implemented'); + } +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/FetchHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +function isBlob(value: any): value is Blob { + return value instanceof Blob; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = btoa(\`\${username}:\${password}\`); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBlob(options.body)) { + headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + const request: RequestInit = { + method: options.method, + headers: await getHeaders(options, config), + body: getRequestBody(options), + }; + if (config.WITH_CREDENTIALS) { + request.credentials = 'include'; + } + return await fetch(url, request); +} + +function getResponseHeader(response: Response, responseHeader?: string): string | null { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +async function getResponseBody(response: Response): Promise { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} + +export class FetchHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; + } +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/OpenAPI.ts 1`] = ` +"/* 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; + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/index.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export type { ApiRequestOptions } from './core/ApiRequestOptions'; +export type { ApiResult } from './core/ApiResult'; +export type { OpenAPIConfig } from './core/OpenAPI'; +export { BaseHttpRequest } from './core/BaseHttpRequest'; +export { FetchHttpRequest } from './core/FetchHttpRequest'; + +export type { ArrayWithArray } from './models/ArrayWithArray'; +export type { ArrayWithBooleans } from './models/ArrayWithBooleans'; +export type { ArrayWithNumbers } from './models/ArrayWithNumbers'; +export type { ArrayWithProperties } from './models/ArrayWithProperties'; +export type { ArrayWithReferences } from './models/ArrayWithReferences'; +export type { ArrayWithStrings } from './models/ArrayWithStrings'; +export type { CompositionWithAllOfAndNullable } from './models/CompositionWithAllOfAndNullable'; +export type { CompositionWithAnyOf } from './models/CompositionWithAnyOf'; +export type { CompositionWithAnyOfAndNullable } from './models/CompositionWithAnyOfAndNullable'; +export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyOfAnonymous'; +export type { CompositionWithOneOf } from './models/CompositionWithOneOf'; +export type { CompositionWithOneOfAndNullable } from './models/CompositionWithOneOfAndNullable'; +export type { CompositionWithOneOfAnonymous } from './models/CompositionWithOneOfAnonymous'; +export type { DictionaryWithArray } from './models/DictionaryWithArray'; +export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary'; +export type { DictionaryWithProperties } from './models/DictionaryWithProperties'; +export type { DictionaryWithReference } from './models/DictionaryWithReference'; +export type { DictionaryWithString } from './models/DictionaryWithString'; +export { EnumFromDescription } from './models/EnumFromDescription'; +export { EnumWithExtensions } from './models/EnumWithExtensions'; +export { EnumWithNumbers } from './models/EnumWithNumbers'; +export { EnumWithStrings } from './models/EnumWithStrings'; +export type { ModelThatExtends } from './models/ModelThatExtends'; +export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends'; +export type { ModelWithArray } from './models/ModelWithArray'; +export type { ModelWithBoolean } from './models/ModelWithBoolean'; +export type { ModelWithCircularReference } from './models/ModelWithCircularReference'; +export type { ModelWithDictionary } from './models/ModelWithDictionary'; +export type { ModelWithDuplicateImports } from './models/ModelWithDuplicateImports'; +export type { ModelWithDuplicateProperties } from './models/ModelWithDuplicateProperties'; +export { ModelWithEnum } from './models/ModelWithEnum'; +export { ModelWithEnumFromDescription } from './models/ModelWithEnumFromDescription'; +export type { ModelWithInteger } from './models/ModelWithInteger'; +export type { ModelWithNestedEnums } from './models/ModelWithNestedEnums'; +export type { ModelWithNestedProperties } from './models/ModelWithNestedProperties'; +export type { ModelWithOrderedProperties } from './models/ModelWithOrderedProperties'; +export type { ModelWithPattern } from './models/ModelWithPattern'; +export type { ModelWithProperties } from './models/ModelWithProperties'; +export type { ModelWithReference } from './models/ModelWithReference'; +export type { ModelWithString } from './models/ModelWithString'; +export type { MultilineComment } from './models/MultilineComment'; +export type { SimpleBoolean } from './models/SimpleBoolean'; +export type { SimpleFile } from './models/SimpleFile'; +export type { SimpleInteger } from './models/SimpleInteger'; +export type { SimpleReference } from './models/SimpleReference'; +export type { SimpleString } from './models/SimpleString'; +export type { SimpleStringWithPattern } from './models/SimpleStringWithPattern'; + +export { $ArrayWithArray } from './schemas/$ArrayWithArray'; +export { $ArrayWithBooleans } from './schemas/$ArrayWithBooleans'; +export { $ArrayWithNumbers } from './schemas/$ArrayWithNumbers'; +export { $ArrayWithProperties } from './schemas/$ArrayWithProperties'; +export { $ArrayWithReferences } from './schemas/$ArrayWithReferences'; +export { $ArrayWithStrings } from './schemas/$ArrayWithStrings'; +export { $CompositionWithAllOfAndNullable } from './schemas/$CompositionWithAllOfAndNullable'; +export { $CompositionWithAnyOf } from './schemas/$CompositionWithAnyOf'; +export { $CompositionWithAnyOfAndNullable } from './schemas/$CompositionWithAnyOfAndNullable'; +export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfAnonymous'; +export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf'; +export { $CompositionWithOneOfAndNullable } from './schemas/$CompositionWithOneOfAndNullable'; +export { $CompositionWithOneOfAnonymous } from './schemas/$CompositionWithOneOfAnonymous'; +export { $DictionaryWithArray } from './schemas/$DictionaryWithArray'; +export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary'; +export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties'; +export { $DictionaryWithReference } from './schemas/$DictionaryWithReference'; +export { $DictionaryWithString } from './schemas/$DictionaryWithString'; +export { $EnumFromDescription } from './schemas/$EnumFromDescription'; +export { $EnumWithExtensions } from './schemas/$EnumWithExtensions'; +export { $EnumWithNumbers } from './schemas/$EnumWithNumbers'; +export { $EnumWithStrings } from './schemas/$EnumWithStrings'; +export { $ModelThatExtends } from './schemas/$ModelThatExtends'; +export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends'; +export { $ModelWithArray } from './schemas/$ModelWithArray'; +export { $ModelWithBoolean } from './schemas/$ModelWithBoolean'; +export { $ModelWithCircularReference } from './schemas/$ModelWithCircularReference'; +export { $ModelWithDictionary } from './schemas/$ModelWithDictionary'; +export { $ModelWithDuplicateImports } from './schemas/$ModelWithDuplicateImports'; +export { $ModelWithDuplicateProperties } from './schemas/$ModelWithDuplicateProperties'; +export { $ModelWithEnum } from './schemas/$ModelWithEnum'; +export { $ModelWithEnumFromDescription } from './schemas/$ModelWithEnumFromDescription'; +export { $ModelWithInteger } from './schemas/$ModelWithInteger'; +export { $ModelWithNestedEnums } from './schemas/$ModelWithNestedEnums'; +export { $ModelWithNestedProperties } from './schemas/$ModelWithNestedProperties'; +export { $ModelWithOrderedProperties } from './schemas/$ModelWithOrderedProperties'; +export { $ModelWithPattern } from './schemas/$ModelWithPattern'; +export { $ModelWithProperties } from './schemas/$ModelWithProperties'; +export { $ModelWithReference } from './schemas/$ModelWithReference'; +export { $ModelWithString } from './schemas/$ModelWithString'; +export { $MultilineComment } from './schemas/$MultilineComment'; +export { $SimpleBoolean } from './schemas/$SimpleBoolean'; +export { $SimpleFile } from './schemas/$SimpleFile'; +export { $SimpleInteger } from './schemas/$SimpleInteger'; +export { $SimpleReference } from './schemas/$SimpleReference'; +export { $SimpleString } from './schemas/$SimpleString'; +export { $SimpleStringWithPattern } from './schemas/$SimpleStringWithPattern'; + +export { CollectionFormatService } from './services/CollectionFormatService'; +export { ComplexService } from './services/ComplexService'; +export { DefaultsService } from './services/DefaultsService'; +export { DuplicateService } from './services/DuplicateService'; +export { HeaderService } from './services/HeaderService'; +export { MultipartService } from './services/MultipartService'; +export { NoContentService } from './services/NoContentService'; +export { ParametersService } from './services/ParametersService'; +export { RequestBodyService } from './services/RequestBodyService'; +export { ResponseService } from './services/ResponseService'; +export { SimpleService } from './services/SimpleService'; +export { TypesService } from './services/TypesService'; +export { UploadService } from './services/UploadService'; + +export { TestClient } from './client'; +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithBooleans.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + foo?: string, + bar?: string, +}>;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithReferences.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ArrayWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithAllOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +import type { ModelWithEnum } from './ModelWithEnum'; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean, + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithAnyOf.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +import type { ModelWithEnum } from './ModelWithEnum'; +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary); +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithAnyOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +import type { ModelWithEnum } from './ModelWithEnum'; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: ({ + boolean?: boolean, + } | ModelWithEnum | ModelWithArray | ModelWithDictionary) | null; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithAnyOfAnonymous.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: ({ + propA?: any, + } | string | number); +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithOneOf.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +import type { ModelWithEnum } from './ModelWithEnum'; +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary); +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithOneOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +import type { ModelWithEnum } from './ModelWithEnum'; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: ({ + boolean?: boolean, + } | ModelWithEnum | ModelWithArray | ModelWithDictionary) | null; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/CompositionWithOneOfAnonymous.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: ({ + propA?: any, + } | string | number); +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/DictionaryWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = Record>;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/DictionaryWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = Record>;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/DictionaryWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = Record;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/DictionaryWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a string reference + */ +export type DictionaryWithReference = Record;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/DictionaryWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a string dictionary + */ +export type DictionaryWithString = Record;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/EnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Success=1,Warning=2,Error=3 + */ +export enum EnumFromDescription { + SUCCESS = 1, + WARNING = 2, + ERROR = 3, +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/EnumWithExtensions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with numbers + */ +export enum EnumWithExtensions { + /** + * Used when the status of something is successful + */ + CUSTOM_SUCCESS = 200, + /** + * Used when the status of something has a warning + */ + CUSTOM_WARNING = 400, + /** + * Used when the status of something has an error + */ + CUSTOM_ERROR = 500, +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/EnumWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with numbers + */ +export enum EnumWithNumbers { + '_1' = 1, + '_2' = 2, + '_3' = 3, + '_1.1' = 1.1, + '_1.2' = 1.2, + '_1.3' = 1.3, + '_100' = 100, + '_200' = 200, + '_300' = 300, + '_-100' = -100, + '_-200' = -200, + '_-300' = -300, + '_-1.1' = -1.1, + '_-1.2' = -1.2, + '_-1.3' = -1.3, +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/EnumWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple enum with strings + */ +export enum EnumWithStrings { + SUCCESS = 'Success', + WARNING = 'Warning', + ERROR = 'Error', +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelThatExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = (ModelWithString & { + propExtendsA?: string, + propExtendsB?: ModelWithString, +}); +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelThatExtendsExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelThatExtends } from './ModelThatExtends'; +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = (ModelWithString & ModelThatExtends & { + propExtendsC?: string, + propExtendsD?: ModelWithString, +}); +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithCircularReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: Record; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithDuplicateImports.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithDuplicateProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithEnum.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + test?: ModelWithEnum.test; + /** + * These are the HTTP error code enums + */ + statusCode?: ModelWithEnum.statusCode; + /** + * Simple boolean enum + */ + bool?: boolean; +} + +export namespace ModelWithEnum { + + /** + * This is a simple enum with strings + */ + export enum test { + SUCCESS = 'Success', + WARNING = 'Warning', + ERROR = 'Error', + } + + /** + * These are the HTTP error code enums + */ + export enum statusCode { + _100 = '100', + _200_FOO = '200 FOO', + _300_FOO_BAR = '300 FOO_BAR', + _400_FOO_BAR = '400 foo-bar', + _500_FOO_BAR = '500 foo.bar', + _600_FOO_BAR = '600 foo&bar', + } + + +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithEnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: ModelWithEnumFromDescription.test; +} + +export namespace ModelWithEnumFromDescription { + + /** + * Success=1,Warning=2,Error=3 + */ + export enum test { + SUCCESS = 1, + WARNING = 2, + ERROR = 3, + } + + +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithNestedEnums.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: Record; + dictionaryWithEnumFromDescription?: Record; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array<1 | 2 | 3>; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithNestedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null, + } | null, + } | null; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithOrderedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithProperties } from './ModelWithProperties'; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +} +" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/ModelWithString.ts 1`] = ` +"/* 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 with exportClient: ./test/generated/v3_client/models/MultilineComment.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Testing multiline comments. + * This must go to the next line. + * + * This will contain a break. + */ +export type MultilineComment = number;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleFile.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple file + */ +export type SimpleFile = Blob;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple number + */ +export type SimpleInteger = number;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ModelWithString } from './ModelWithString'; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple string + */ +export type SimpleString = string;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/models/SimpleStringWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null;" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithArray = { + type: 'array', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithBooleans.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithBooleans = { + type: 'array', + contains: { + type: 'boolean', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithNumbers = { + type: 'array', + contains: { + type: 'number', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithProperties = { + type: 'array', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithReferences.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithReferences = { + type: 'array', + contains: { + type: 'ModelWithString', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ArrayWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithStrings = { + type: 'array', + contains: { + type: 'string', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithAllOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithAllOfAndNullable = { + properties: { + propA: { + type: 'all-of', + contains: [{ + properties: { + boolean: { + type: 'boolean', + }, + }, + }, { + type: 'ModelWithEnum', + }, { + type: 'ModelWithArray', + }, { + type: 'ModelWithDictionary', + }], + isNullable: true, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithAnyOf.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithAnyOf = { + properties: { + propA: { + type: 'any-of', + contains: [{ + type: 'ModelWithString', + }, { + type: 'ModelWithEnum', + }, { + type: 'ModelWithArray', + }, { + type: 'ModelWithDictionary', + }], + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithAnyOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithAnyOfAndNullable = { + properties: { + propA: { + type: 'any-of', + contains: [{ + properties: { + boolean: { + type: 'boolean', + }, + }, + }, { + type: 'ModelWithEnum', + }, { + type: 'ModelWithArray', + }, { + type: 'ModelWithDictionary', + }], + isNullable: true, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithAnyOfAnonymous.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithAnyOfAnonymous = { + properties: { + propA: { + type: 'any-of', + contains: [{ + properties: { + propA: { + properties: { + }, + }, + }, + }, { + type: 'string', + }, { + type: 'number', + }], + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithOneOf.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithOneOf = { + properties: { + propA: { + type: 'one-of', + contains: [{ + type: 'ModelWithString', + }, { + type: 'ModelWithEnum', + }, { + type: 'ModelWithArray', + }, { + type: 'ModelWithDictionary', + }], + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithOneOfAndNullable.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithOneOfAndNullable = { + properties: { + propA: { + type: 'one-of', + contains: [{ + properties: { + boolean: { + type: 'boolean', + }, + }, + }, { + type: 'ModelWithEnum', + }, { + type: 'ModelWithArray', + }, { + type: 'ModelWithDictionary', + }], + isNullable: true, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$CompositionWithOneOfAnonymous.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithOneOfAnonymous = { + properties: { + propA: { + type: 'one-of', + contains: [{ + properties: { + propA: { + properties: { + }, + }, + }, + }, { + type: 'string', + }, { + type: 'number', + }], + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$DictionaryWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithArray = { + type: 'dictionary', + contains: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$DictionaryWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithDictionary = { + type: 'dictionary', + contains: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$DictionaryWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithProperties = { + type: 'dictionary', + contains: { + properties: { + foo: { + type: 'string', + }, + bar: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$DictionaryWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithReference = { + type: 'dictionary', + contains: { + type: 'ModelWithString', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$DictionaryWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $DictionaryWithString = { + type: 'dictionary', + contains: { + type: 'string', + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$EnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumFromDescription = { + type: 'Enum', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$EnumWithExtensions.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithExtensions = { + type: 'Enum', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$EnumWithNumbers.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithNumbers = { + type: 'Enum', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$EnumWithStrings.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $EnumWithStrings = { + type: 'Enum', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelThatExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelThatExtends = { + type: 'all-of', + contains: [{ + type: 'ModelWithString', + }, { + properties: { + propExtendsA: { + type: 'string', + }, + propExtendsB: { + type: 'ModelWithString', + }, + }, + }], +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelThatExtendsExtends.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelThatExtendsExtends = { + type: 'all-of', + contains: [{ + type: 'ModelWithString', + }, { + type: 'ModelThatExtends', + }, { + properties: { + propExtendsC: { + type: 'string', + }, + propExtendsD: { + type: 'ModelWithString', + }, + }, + }], +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithArray.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithArray = { + properties: { + prop: { + type: 'array', + contains: { + type: 'ModelWithString', + }, + }, + propWithFile: { + type: 'array', + contains: { + type: 'File', + }, + }, + propWithNumber: { + type: 'array', + contains: { + type: 'number', + }, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithBoolean = { + properties: { + prop: { + type: 'boolean', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithCircularReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithCircularReference = { + properties: { + prop: { + type: 'ModelWithCircularReference', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithDictionary.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDictionary = { + properties: { + prop: { + type: 'dictionary', + contains: { + type: 'string', + }, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithDuplicateImports.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDuplicateImports = { + properties: { + propA: { + type: 'ModelWithString', + }, + propB: { + type: 'ModelWithString', + }, + propC: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithDuplicateProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithDuplicateProperties = { + properties: { + prop: { + type: 'ModelWithString', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithEnum.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithEnum = { + properties: { + test: { + type: 'Enum', + }, + statusCode: { + type: 'Enum', + }, + bool: { + type: 'boolean', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithEnumFromDescription.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithEnumFromDescription = { + properties: { + test: { + type: 'Enum', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithInteger = { + properties: { + prop: { + type: 'number', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithNestedEnums.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithNestedEnums = { + properties: { + dictionaryWithEnum: { + type: 'dictionary', + contains: { + type: 'Enum', + }, + }, + dictionaryWithEnumFromDescription: { + type: 'dictionary', + contains: { + type: 'Enum', + }, + }, + arrayWithEnum: { + type: 'array', + contains: { + type: 'Enum', + }, + }, + arrayWithDescription: { + type: 'array', + contains: { + type: 'Enum', + }, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithNestedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithNestedProperties = { + properties: { + first: { + properties: { + second: { + properties: { + third: { + type: 'string', + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, + isReadOnly: true, + isRequired: true, + isNullable: true, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithOrderedProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithOrderedProperties = { + properties: { + zebra: { + type: 'string', + }, + apple: { + type: 'string', + }, + hawaii: { + type: 'string', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithPattern = { + properties: { + key: { + type: 'string', + isRequired: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', + }, + name: { + type: 'string', + isRequired: true, + maxLength: 255, + }, + enabled: { + type: 'boolean', + isReadOnly: true, + }, + modified: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + id: { + type: 'string', + pattern: '^\\\\\\\\d{2}-\\\\\\\\d{3}-\\\\\\\\d{4}$', + }, + text: { + type: 'string', + pattern: '^\\\\\\\\w+$', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithProperties.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithProperties = { + properties: { + required: { + type: 'string', + isRequired: true, + }, + requiredAndReadOnly: { + type: 'string', + isReadOnly: true, + isRequired: true, + }, + requiredAndNullable: { + type: 'string', + isRequired: true, + isNullable: true, + }, + string: { + type: 'string', + }, + number: { + type: 'number', + }, + boolean: { + type: 'boolean', + }, + reference: { + type: 'ModelWithString', + }, + 'property with space': { + type: 'string', + }, + default: { + type: 'string', + }, + try: { + type: 'string', + }, + '@namespace.string': { + type: 'string', + isReadOnly: true, + }, + '@namespace.integer': { + type: 'number', + isReadOnly: true, + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithReference = { + properties: { + prop: { + type: 'ModelWithProperties', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$ModelWithString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithString = { + properties: { + prop: { + type: 'string', + }, + }, +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$MultilineComment.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $MultilineComment = { + type: 'number', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleBoolean.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleBoolean = { + type: 'boolean', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleFile.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleFile = { + type: 'File', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleInteger.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleInteger = { + type: 'number', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleReference.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleReference = { + type: 'ModelWithString', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleString.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleString = { + type: 'string', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/schemas/$SimpleStringWithPattern.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $SimpleStringWithPattern = { + type: 'string', + isNullable: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', +};" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/CollectionFormatService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CollectionFormatService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterArrayCsv This is an array parameter that is send as csv format (comma-separated values) + * @param parameterArraySsv This is an array parameter that is send as ssv format (space-separated values) + * @param parameterArrayTsv This is an array parameter that is send as tsv format (tab-separated values) + * @param parameterArrayPipes This is an array parameter that is send as pipes format (pipe-separated values) + * @param parameterArrayMulti This is an array parameter that is send as multi format (multiple parameter instances) + * @throws ApiError + */ + public async collectionFormat( + parameterArrayCsv: Array | null, + parameterArraySsv: Array | null, + parameterArrayTsv: Array | null, + parameterArrayPipes: Array | null, + parameterArrayMulti: Array | null, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/collectionFormat\`, + query: { + 'parameterArrayCSV': parameterArrayCsv, + 'parameterArraySSV': parameterArraySsv, + 'parameterArrayTSV': parameterArrayTsv, + 'parameterArrayPipes': parameterArrayPipes, + 'parameterArrayMulti': parameterArrayMulti, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/ComplexService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithArray } from '../models/ModelWithArray'; +import type { ModelWithDictionary } from '../models/ModelWithDictionary'; +import type { ModelWithEnum } from '../models/ModelWithEnum'; +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ComplexService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterObject Parameter containing object + * @param parameterReference Parameter containing reference + * @returns ModelWithString Successful response + * @throws ApiError + */ + public async complexTypes( + parameterObject: { + first?: { + second?: { + third?: string, + }, + }, + }, + parameterReference: ModelWithString, + ): Promise> { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/complex\`, + query: { + 'parameterObject': parameterObject, + 'parameterReference': parameterReference, + }, + errors: { + 400: \`400 server error\`, + 500: \`500 server error\`, + }, + }); + return result.body; + } + + /** + * @param id + * @param requestBody + * @returns ModelWithString Success + * @throws ApiError + */ + public async complexParams( + id: number, + requestBody?: { + readonly key: string | null, + name: string | null, + enabled: boolean, + readonly type: 'Monkey' | 'Horse' | 'Bird', + listOfModels?: Array | null, + listOfStrings?: Array | null, + parameters: (ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary), + readonly user?: { + readonly id?: number, + readonly name?: string | null, + }, + }, + ): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/complex/\${id}\`, + body: requestBody, + mediaType: 'application/json-patch+json', + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/DefaultsService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class DefaultsService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterString This is a simple string with default value + * @param parameterNumber This is a simple number with default value + * @param parameterBoolean This is a simple boolean with default value + * @param parameterEnum This is a simple enum with default value + * @param parameterModel This is a simple model with default value + * @throws ApiError + */ + public async callWithDefaultParameters( + parameterString: string | null = 'Hello World!', + parameterNumber: number | null = 123, + parameterBoolean: boolean | null = true, + parameterEnum: 'Success' | 'Warning' | 'Error' = 'Success', + parameterModel: ModelWithString | null = { + \\"prop\\": \\"Hello World!\\" + }, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + return result.body; + } + + /** + * @param parameterString This is a simple string that is optional with default value + * @param parameterNumber This is a simple number that is optional with default value + * @param parameterBoolean This is a simple boolean that is optional with default value + * @param parameterEnum This is a simple enum that is optional with default value + * @param parameterModel This is a simple model that is optional with default value + * @throws ApiError + */ + public async callWithDefaultOptionalParameters( + parameterString: string = 'Hello World!', + parameterNumber: number = 123, + parameterBoolean: boolean = true, + parameterEnum: 'Success' | 'Warning' | 'Error' = 'Success', + parameterModel: ModelWithString = { + \\"prop\\": \\"Hello World!\\" + }, + ): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterString': parameterString, + 'parameterNumber': parameterNumber, + 'parameterBoolean': parameterBoolean, + 'parameterEnum': parameterEnum, + 'parameterModel': parameterModel, + }, + }); + return result.body; + } + + /** + * @param parameterStringWithNoDefault This is a string with no default + * @param parameterOptionalStringWithDefault This is a optional string with default + * @param parameterOptionalStringWithEmptyDefault This is a optional string with empty default + * @param parameterOptionalStringWithNoDefault This is a optional string with no default + * @param parameterStringWithDefault This is a string with default + * @param parameterStringWithEmptyDefault This is a string with empty default + * @throws ApiError + */ + public async callToTestOrderOfParams( + parameterStringWithNoDefault: string, + parameterOptionalStringWithDefault: string = 'Hello World!', + parameterOptionalStringWithEmptyDefault: string = '', + parameterOptionalStringWithNoDefault?: string, + parameterStringWithDefault: string = 'Hello World!', + parameterStringWithEmptyDefault: string = '', + ): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/defaults\`, + query: { + 'parameterStringWithNoDefault': parameterStringWithNoDefault, + 'parameterOptionalStringWithDefault': parameterOptionalStringWithDefault, + 'parameterOptionalStringWithEmptyDefault': parameterOptionalStringWithEmptyDefault, + 'parameterOptionalStringWithNoDefault': parameterOptionalStringWithNoDefault, + 'parameterStringWithDefault': parameterStringWithDefault, + 'parameterStringWithEmptyDefault': parameterStringWithEmptyDefault, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/DuplicateService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class DuplicateService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @throws ApiError + */ + public async duplicateName(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName1(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName2(): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async duplicateName3(): Promise { + const result = await this.httpRequest.request({ + method: 'DELETE', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/duplicate\`, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/HeaderService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class HeaderService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns string Successful response + * @throws ApiError + */ + public async callWithResultFromHeader(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/header\`, + responseHeader: 'operation-location', + errors: { + 400: \`400 server error\`, + 500: \`500 server error\`, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/MultipartService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class MultipartService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns any OK + * @throws ApiError + */ + public async multipartResponse(): Promise<{ + file?: string, + metadata?: { + foo?: string, + bar?: string, + }, + }> { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/multipart\`, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/NoContentService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class NoContentService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns void + * @throws ApiError + */ + public async callWithNoContentResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/no-content\`, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/ParametersService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ParametersService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterHeader This is the parameter that goes into the header + * @param parameterQuery This is the parameter that goes into the query params + * @param parameterForm This is the parameter that goes into the form data + * @param parameterCookie This is the parameter that goes into the cookie + * @param parameterPath This is the parameter that goes into the path + * @param requestBody This is the parameter that goes into the body + * @throws ApiError + */ + public async callWithParameters( + parameterHeader: string | null, + parameterQuery: string | null, + parameterForm: string | null, + parameterCookie: string | null, + parameterPath: string | null, + requestBody: ModelWithString | null, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\${parameterPath}\`, + cookies: { + 'parameterCookie': parameterCookie, + }, + headers: { + 'parameterHeader': parameterHeader, + }, + query: { + 'parameterQuery': parameterQuery, + }, + formData: { + 'parameterForm': parameterForm, + }, + body: requestBody, + mediaType: 'application/json', + }); + return result.body; + } + + /** + * @param parameterHeader This is the parameter that goes into the request header + * @param parameterQuery This is the parameter that goes into the request query params + * @param parameterForm This is the parameter that goes into the request form data + * @param parameterCookie This is the parameter that goes into the cookie + * @param requestBody This is the parameter that goes into the body + * @param parameterPath1 This is the parameter that goes into the path + * @param parameterPath2 This is the parameter that goes into the path + * @param parameterPath3 This is the parameter that goes into the path + * @param _default This is the parameter with a reserved keyword + * @throws ApiError + */ + public async callWithWeirdParameterNames( + parameterHeader: string | null, + parameterQuery: string | null, + parameterForm: string | null, + parameterCookie: string | null, + requestBody: ModelWithString | null, + parameterPath1?: string, + parameterPath2?: string, + parameterPath3?: string, + _default?: string, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\${parameterPath1}/\${parameterPath2}/\${parameterPath3}\`, + cookies: { + 'PARAMETER-COOKIE': parameterCookie, + }, + headers: { + 'parameter.header': parameterHeader, + }, + query: { + 'parameter-query': parameterQuery, + 'default': _default, + }, + formData: { + 'parameter_form': parameterForm, + }, + body: requestBody, + mediaType: 'application/json', + }); + return result.body; + } + + /** + * @param requestBody This is a required parameter + * @param parameter This is an optional parameter + * @throws ApiError + */ + public async getCallWithOptionalParam( + requestBody: ModelWithString, + parameter?: string, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\`, + query: { + 'parameter': parameter, + }, + body: requestBody, + mediaType: 'application/json', + }); + return result.body; + } + + /** + * @param parameter This is a required parameter + * @param requestBody This is an optional parameter + * @throws ApiError + */ + public async postCallWithOptionalParam( + parameter: string, + requestBody?: ModelWithString, + ): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/parameters/\`, + query: { + 'parameter': parameter, + }, + body: requestBody, + mediaType: 'application/json', + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/RequestBodyService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class RequestBodyService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param requestBody A reusable request body + * @throws ApiError + */ + public async postRequestBodyService( + requestBody?: ModelWithString, + ): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/requestBody/\`, + body: requestBody, + mediaType: 'application/json', + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/ResponseService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelThatExtends } from '../models/ModelThatExtends'; +import type { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends'; +import type { ModelWithString } from '../models/ModelWithString'; +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ResponseService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @returns ModelWithString + * @throws ApiError + */ + public async callWithResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + }); + return result.body; + } + + /** + * @returns ModelWithString Message for default response + * @throws ApiError + */ + public async callWithDuplicateResponses(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + errors: { + 500: \`Message for 500 error\`, + 501: \`Message for 501 error\`, + 502: \`Message for 502 error\`, + }, + }); + return result.body; + } + + /** + * @returns any Message for 200 response + * @returns ModelWithString Message for default response + * @returns ModelThatExtends Message for 201 response + * @returns ModelThatExtendsExtends Message for 202 response + * @throws ApiError + */ + public async callWithResponses(): Promise<{ + readonly '@namespace.string'?: string, + readonly '@namespace.integer'?: number, + readonly value?: Array, + } | ModelWithString | ModelThatExtends | ModelThatExtendsExtends> { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/response\`, + errors: { + 500: \`Message for 500 error\`, + 501: \`Message for 501 error\`, + 502: \`Message for 502 error\`, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/SimpleService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class SimpleService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @throws ApiError + */ + public async getCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async putCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'PUT', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async postCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async deleteCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'DELETE', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async optionsCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'OPTIONS', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async headCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'HEAD', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + + /** + * @throws ApiError + */ + public async patchCallWithoutParametersAndResponse(): Promise { + const result = await this.httpRequest.request({ + method: 'PATCH', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/simple\`, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/TypesService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class TypesService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param parameterArray This is an array parameter + * @param parameterDictionary This is a dictionary parameter + * @param parameterEnum This is an enum parameter + * @param parameterNumber This is a number parameter + * @param parameterString This is a string parameter + * @param parameterBoolean This is a boolean parameter + * @param parameterObject This is an object parameter + * @param id This is a number parameter + * @returns number Response is a simple number + * @returns string Response is a simple string + * @returns boolean Response is a simple boolean + * @returns any Response is a simple object + * @throws ApiError + */ + public async types( + parameterArray: Array | null, + parameterDictionary: any, + parameterEnum: 'Success' | 'Warning' | 'Error' | null, + parameterNumber: number = 123, + parameterString: string | null = 'default', + parameterBoolean: boolean | null = true, + parameterObject: any = null, + id?: number, + ): Promise { + const result = await this.httpRequest.request({ + method: 'GET', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/types\`, + query: { + 'parameterArray': parameterArray, + 'parameterDictionary': parameterDictionary, + 'parameterEnum': parameterEnum, + 'parameterNumber': parameterNumber, + 'parameterString': parameterString, + 'parameterBoolean': parameterBoolean, + 'parameterObject': parameterObject, + }, + }); + return result.body; + } + +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/services/UploadService.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class UploadService { + private httpRequest: BaseHttpRequest; + + constructor(httpRequest: BaseHttpRequest) { + this.httpRequest = httpRequest; + } + + /** + * @param file Supply a file reference for upload + * @returns boolean + * @throws ApiError + */ + public async uploadFile( + file: Blob, + ): Promise { + const result = await this.httpRequest.request({ + method: 'POST', + path: \`/api/v\${this.httpRequest.openApiConfig.VERSION}/upload\`, + formData: { + 'file': file, + }, + }); + return result.body; + } + +}" +`; diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 0047c137c..44da68af4 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -63,17 +63,16 @@ import type { ApiRequestOptions } from './ApiRequestOptions'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; -type Config = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; +export type OpenAPIConfig = { + BASE?: string; + VERSION?: string; + WITH_CREDENTIALS?: boolean; TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; HEADERS?: Headers | Resolver; } - -export const OpenAPI: Config = { +export const OpenAPI: OpenAPIConfig = { BASE: 'http://localhost:3000/base', VERSION: '1.0', WITH_CREDENTIALS: false, @@ -81,7 +80,8 @@ export const OpenAPI: Config = { USERNAME: undefined, PASSWORD: undefined, HEADERS: undefined, -};" +}; +" `; exports[`v2 should generate: ./test/generated/v2/core/request.ts 1`] = ` @@ -91,6 +91,7 @@ exports[`v2 should generate: ./test/generated/v2/core/request.ts 1`] = ` import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; import { OpenAPI } from './OpenAPI'; function isDefined(value: T | null | undefined): value is Exclude { @@ -129,9 +130,9 @@ function getQueryString(params: Record): string { return ''; } -function getUrl(options: ApiRequestOptions): string { +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { const path = options.path.replace(/[:]/g, '_'); - const url = \`\${OpenAPI.BASE}\${path}\`; + const url = \`\${config.BASE}\${path}\`; if (options.query) { return \`\${url}\${getQueryString(options.query)}\`; @@ -159,11 +160,11 @@ async function resolve(options: ApiRequestOptions, resolver?: T | Resolver return resolver; } -async function getHeaders(options: ApiRequestOptions): Promise { - const token = await resolve(options, OpenAPI.TOKEN); - const username = await resolve(options, OpenAPI.USERNAME); - const password = await resolve(options, OpenAPI.PASSWORD); - const defaultHeaders = await resolve(options, OpenAPI.HEADERS); +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); const headers = new Headers({ Accept: 'application/json', @@ -210,13 +211,13 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return undefined; } -async function sendRequest(options: ApiRequestOptions, url: string): Promise { +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { const request: RequestInit = { method: options.method, - headers: await getHeaders(options), + headers: await getHeaders(options, config), body: getRequestBody(options), }; - if (OpenAPI.WITH_CREDENTIALS) { + if (config.WITH_CREDENTIALS) { request.credentials = 'include'; } return await fetch(url, request); @@ -278,8 +279,8 @@ function catchErrors(options: ApiRequestOptions, result: ApiResult): void { * @throws ApiError */ export async function request(options: ApiRequestOptions): Promise { - const url = getUrl(options); - const response = await sendRequest(options, url); + const url = getUrl(options, OpenAPI); + const response = await sendRequest(options, OpenAPI, url); const responseBody = await getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -293,7 +294,8 @@ export async function request(options: ApiRequestOptions): Promise { catchErrors(options, result); return result; -}" +} +" `; exports[`v2 should generate: ./test/generated/v2/index.ts 1`] = ` @@ -399,6 +401,7 @@ export { ParametersService } from './services/ParametersService'; export { ResponseService } from './services/ResponseService'; export { SimpleService } from './services/SimpleService'; export { TypesService } from './services/TypesService'; + " `; @@ -2416,17 +2419,16 @@ import type { ApiRequestOptions } from './ApiRequestOptions'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; -type Config = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; +export type OpenAPIConfig = { + BASE?: string; + VERSION?: string; + WITH_CREDENTIALS?: boolean; TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; HEADERS?: Headers | Resolver; } - -export const OpenAPI: Config = { +export const OpenAPI: OpenAPIConfig = { BASE: 'http://localhost:3000/base', VERSION: '1.0', WITH_CREDENTIALS: false, @@ -2434,7 +2436,8 @@ export const OpenAPI: Config = { USERNAME: undefined, PASSWORD: undefined, HEADERS: undefined, -};" +}; +" `; exports[`v3 should generate: ./test/generated/v3/core/request.ts 1`] = ` @@ -2444,6 +2447,7 @@ exports[`v3 should generate: ./test/generated/v3/core/request.ts 1`] = ` import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; import { OpenAPI } from './OpenAPI'; function isDefined(value: T | null | undefined): value is Exclude { @@ -2482,9 +2486,9 @@ function getQueryString(params: Record): string { return ''; } -function getUrl(options: ApiRequestOptions): string { +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { const path = options.path.replace(/[:]/g, '_'); - const url = \`\${OpenAPI.BASE}\${path}\`; + const url = \`\${config.BASE}\${path}\`; if (options.query) { return \`\${url}\${getQueryString(options.query)}\`; @@ -2512,11 +2516,11 @@ async function resolve(options: ApiRequestOptions, resolver?: T | Resolver return resolver; } -async function getHeaders(options: ApiRequestOptions): Promise { - const token = await resolve(options, OpenAPI.TOKEN); - const username = await resolve(options, OpenAPI.USERNAME); - const password = await resolve(options, OpenAPI.PASSWORD); - const defaultHeaders = await resolve(options, OpenAPI.HEADERS); +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); const headers = new Headers({ Accept: 'application/json', @@ -2563,13 +2567,13 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return undefined; } -async function sendRequest(options: ApiRequestOptions, url: string): Promise { +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { const request: RequestInit = { method: options.method, - headers: await getHeaders(options), + headers: await getHeaders(options, config), body: getRequestBody(options), }; - if (OpenAPI.WITH_CREDENTIALS) { + if (config.WITH_CREDENTIALS) { request.credentials = 'include'; } return await fetch(url, request); @@ -2631,8 +2635,8 @@ function catchErrors(options: ApiRequestOptions, result: ApiResult): void { * @throws ApiError */ export async function request(options: ApiRequestOptions): Promise { - const url = getUrl(options); - const response = await sendRequest(options, url); + const url = getUrl(options, OpenAPI); + const response = await sendRequest(options, OpenAPI, url); const responseBody = await getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); @@ -2646,7 +2650,8 @@ export async function request(options: ApiRequestOptions): Promise { catchErrors(options, result); return result; -}" +} +" `; exports[`v3 should generate: ./test/generated/v3/index.ts 1`] = ` @@ -2765,6 +2770,7 @@ export { ResponseService } from './services/ResponseService'; export { SimpleService } from './services/SimpleService'; export { TypesService } from './services/TypesService'; export { UploadService } from './services/UploadService'; + " `; diff --git a/test/e2e/scripts/generate.js b/test/e2e/scripts/generate.js index 426ebb213..a8b534591 100644 --- a/test/e2e/scripts/generate.js +++ b/test/e2e/scripts/generate.js @@ -2,13 +2,14 @@ const OpenAPI = require('../../../dist'); -async function generate(dir, version, client, useOptions = false, useUnionTypes = false) { +async function generate(dir, version, client, useOptions = false, useUnionTypes = false, exportClient = false) { await OpenAPI.generate({ input: `./test/spec/${version}.json`, output: `./test/e2e/generated/${dir}/`, httpClient: client, useOptions, useUnionTypes, + exportClient, }); } diff --git a/test/e2e/v2.babel.spec.js b/test/e2e/v2.babel.spec.js index e08e420d7..ca3bd1925 100644 --- a/test/e2e/v2.babel.spec.js +++ b/test/e2e/v2.babel.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v2.fetch', () => { - beforeAll(async () => { await generate('v2/babel', 'v2', 'fetch', true, true); await copy('v2/babel'); @@ -37,9 +36,49 @@ describe('v2.fetch', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v2.fetch with client', () => { + beforeAll(async () => { + await generate('v2/babel_client', 'v2', 'fetch', true, true, true); + await copy('v2/babel_client'); + compileWithBabel('v2/babel_client'); + await server.start('v2/babel_client'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient(); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/e2e/v2.fetch.spec.js b/test/e2e/v2.fetch.spec.js index 3eb58162f..412392577 100644 --- a/test/e2e/v2.fetch.spec.js +++ b/test/e2e/v2.fetch.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v2.fetch', () => { - beforeAll(async () => { await generate('v2/fetch', 'v2', 'fetch'); await copy('v2/fetch'); @@ -37,9 +36,49 @@ describe('v2.fetch', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v2.fetch with client', () => { + beforeAll(async () => { + await generate('v2/fetch_client', 'v2', 'fetch', false, false, true); + await copy('v2/fetch_client'); + compileWithTypescript('v2/fetch_client'); + await server.start('v2/fetch_client'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient(); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/e2e/v2.node.spec.js b/test/e2e/v2.node.spec.js index 12d1d85b1..c3cab8f5f 100644 --- a/test/e2e/v2.node.spec.js +++ b/test/e2e/v2.node.spec.js @@ -5,7 +5,6 @@ const compileWithTypescript = require('./scripts/compileWithTypescript'); const server = require('./scripts/server'); describe('v2.node', () => { - beforeAll(async () => { await generate('v2/node', 'v2', 'node'); compileWithTypescript('v2/node'); @@ -18,7 +17,7 @@ describe('v2.node', () => { it('requests token', async () => { const { OpenAPI, SimpleService } = require('./generated/v2/node/index.js'); - const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN') + const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); @@ -30,11 +29,44 @@ describe('v2.node', () => { const result = await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, }); expect(result).toBeDefined(); }); +}); + +describe('v2.node with client', () => { + beforeAll(async () => { + await generate('v2/node_client', 'v2', 'node', false, false, true); + compileWithTypescript('v2/node_client'); + await server.start('v2/node_client'); + }, 30000); + + afterAll(async () => { + await server.stop(); + }); + + it('requests token', async () => { + const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN'); + const { AppClient } = require('./generated/v2/node_client/index.js'); + const client = new AppClient({ TOKEN: tokenRequest }); + const result = await client.simple.getCallWithoutParametersAndResponse(); + expect(tokenRequest.mock.calls.length).toBe(1); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + it('complexService', async () => { + const { AppClient } = require('./generated/v2/node_client/index.js'); + const client = new AppClient(); + const result = await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, + }); + expect(result).toBeDefined(); + }); }); diff --git a/test/e2e/v2.xhr.spec.js b/test/e2e/v2.xhr.spec.js index 6791cc243..09b1c219b 100644 --- a/test/e2e/v2.xhr.spec.js +++ b/test/e2e/v2.xhr.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v2.xhr', () => { - beforeAll(async () => { await generate('v2/xhr', 'v2', 'xhr'); await copy('v2/xhr'); @@ -37,9 +36,49 @@ describe('v2.xhr', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v2.xhr with client', () => { + beforeAll(async () => { + await generate('v2/xhr_client', 'v2', 'xhr', false, false, true); + await copy('v2/xhr_client'); + compileWithTypescript('v2/xhr_client'); + await server.start('v2/xhr_client'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient(); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/e2e/v3.babel.spec.js b/test/e2e/v3.babel.spec.js index e5ea0f841..3a5a9c538 100644 --- a/test/e2e/v3.babel.spec.js +++ b/test/e2e/v3.babel.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v3.fetch', () => { - beforeAll(async () => { await generate('v3/babel', 'v3', 'fetch', true, true); await copy('v3/babel'); @@ -50,9 +49,58 @@ describe('v3.fetch', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v3.fetch with client', () => { + beforeAll(async () => { + await generate('v3/babel', 'v3', 'fetch', true, true, true); + await copy('v3/babel'); + compileWithBabel('v3/babel'); + await server.start('v3/babel'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest, USERNAME: undefined, PASSWORD: undefined }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password' }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({}); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/e2e/v3.fetch.spec.js b/test/e2e/v3.fetch.spec.js index 435fd57dd..cd2a1996a 100644 --- a/test/e2e/v3.fetch.spec.js +++ b/test/e2e/v3.fetch.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v3.fetch', () => { - beforeAll(async () => { await generate('v3/fetch', 'v3', 'fetch'); await copy('v3/fetch'); @@ -50,9 +49,58 @@ describe('v3.fetch', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v3.fetch with client', () => { + beforeAll(async () => { + await generate('v3/fetch_client', 'v3', 'fetch', false, false, true); + await copy('v3/fetch_client'); + compileWithTypescript('v3/fetch_client'); + await server.start('v3/fetch_client'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest, USERNAME: undefined, PASSWORD: undefined }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password' }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({}); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/e2e/v3.node.spec.js b/test/e2e/v3.node.spec.js index 88626d73e..42ac11acc 100644 --- a/test/e2e/v3.node.spec.js +++ b/test/e2e/v3.node.spec.js @@ -5,7 +5,6 @@ const compileWithTypescript = require('./scripts/compileWithTypescript'); const server = require('./scripts/server'); describe('v3.node', () => { - beforeAll(async () => { await generate('v3/node', 'v3', 'node'); compileWithTypescript('v3/node'); @@ -18,7 +17,7 @@ describe('v3.node', () => { it('requests token', async () => { const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js'); - const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN') + const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; OpenAPI.USERNAME = undefined; OpenAPI.PASSWORD = undefined; @@ -41,11 +40,51 @@ describe('v3.node', () => { const result = await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, }); expect(result).toBeDefined(); }); +}); + +describe('v3.node with client', () => { + beforeAll(async () => { + await generate('v3/node_client', 'v3', 'node', false, false, true); + compileWithTypescript('v3/node_client'); + await server.start('v3/node_client'); + }, 30000); + + afterAll(async () => { + await server.stop(); + }); + + it('requests token', async () => { + const { AppClient } = require('./generated/v3/node_client/index.js'); + const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN'); + const client = new AppClient({ TOKEN: tokenRequest, username: undefined, password: undefined }); + const result = await client.simple.getCallWithoutParametersAndResponse(); + expect(tokenRequest.mock.calls.length).toBe(1); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('uses credentials', async () => { + const { AppClient } = require('./generated/v3/node_client/index.js'); + const client = new AppClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password' }); + const result = await client.simple.getCallWithoutParametersAndResponse(); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + it('complexService', async () => { + const { AppClient } = require('./generated/v3/node_client/index.js'); + const client = new AppClient(); + const result = await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, + }); + expect(result).toBeDefined(); + }); }); diff --git a/test/e2e/v3.xhr.spec.js b/test/e2e/v3.xhr.spec.js index 88e0ff227..4bcdabf60 100644 --- a/test/e2e/v3.xhr.spec.js +++ b/test/e2e/v3.xhr.spec.js @@ -7,7 +7,6 @@ const server = require('./scripts/server'); const browser = require('./scripts/browser'); describe('v3.xhr', () => { - beforeAll(async () => { await generate('v3/xhr', 'v3', 'xhr'); await copy('v3/xhr'); @@ -50,9 +49,58 @@ describe('v3.xhr', () => { return await ComplexService.complexTypes({ first: { second: { - third: 'Hello World!' - } - } + third: 'Hello World!', + }, + }, + }); + }); + expect(result).toBeDefined(); + }); +}); + +describe('v3.xhr with client', () => { + beforeAll(async () => { + await generate('v3/xhr_client', 'v3', 'xhr', false, false, true); + await copy('v3/xhr_client'); + compileWithTypescript('v3/xhr_client'); + await server.start('v3/xhr_client'); + await browser.start(); + }, 30000); + + afterAll(async () => { + await server.stop(); + await browser.stop(); + }); + + it('requests token', async () => { + await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN')); + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: window.tokenRequest, USERNAME: undefined, PASSWORD: undefined }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); + }); + + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password' }); + return await client.simple.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + + it('complexService', async () => { + const result = await browser.evaluate(async () => { + const { AppClient } = window.api; + const client = new AppClient({}); + return await client.complex.complexTypes({ + first: { + second: { + third: 'Hello World!', + }, + }, }); }); expect(result).toBeDefined(); diff --git a/test/index.client.spec.js b/test/index.client.spec.js new file mode 100644 index 000000000..fdce7fff1 --- /dev/null +++ b/test/index.client.spec.js @@ -0,0 +1,51 @@ +'use strict'; + +const OpenAPI = require('../dist'); +const glob = require('glob'); +const fs = require('fs'); + +describe('v2', () => { + it('should generate with exportClient', async () => { + await OpenAPI.generate({ + input: './test/spec/v2.json', + output: './test/generated/v2_client', + httpClient: OpenAPI.HttpClient.FETCH, + useOptions: false, + useUnionTypes: false, + exportCore: true, + exportSchemas: true, + exportModels: true, + exportServices: true, + exportClient: true, + clientName: 'TestClient', + }); + + glob.sync('./test/generated/v2_client/**/*.ts').forEach(file => { + const content = fs.readFileSync(file, 'utf8').toString(); + expect(content).toMatchSnapshot(file); + }); + }); +}); + +describe('v3', () => { + it('should generate with exportClient', async () => { + await OpenAPI.generate({ + input: './test/spec/v3.json', + output: './test/generated/v3_client', + httpClient: OpenAPI.HttpClient.FETCH, + useOptions: false, + useUnionTypes: false, + exportCore: true, + exportSchemas: true, + exportModels: true, + exportServices: true, + exportClient: true, + clientName: 'TestClient', + }); + + glob.sync('./test/generated/v3_client/**/*.ts').forEach(file => { + const content = fs.readFileSync(file, 'utf8').toString(); + expect(content).toMatchSnapshot(file); + }); + }); +}); diff --git a/test/index.spec.js b/test/index.spec.js index 946e6fa15..2f8169282 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -16,6 +16,7 @@ describe('v2', () => { exportSchemas: true, exportModels: true, exportServices: true, + exportClient: false, }); glob.sync('./test/generated/v2/**/*.ts').forEach(file => { @@ -37,6 +38,7 @@ describe('v3', () => { exportSchemas: true, exportModels: true, exportServices: true, + exportClient: false, }); glob.sync('./test/generated/v3/**/*.ts').forEach(file => { From 9c9fae22946d5df7ccc8e216872492c5b956cf0c Mon Sep 17 00:00:00 2001 From: lub0v-parsable Date: Thu, 5 Aug 2021 10:09:50 -0700 Subject: [PATCH 2/2] PE-2152 - add multiple http clients --- .gitignore | 1 + package.json | 4 +- .../{ConcreteHttpRequest.hbs => request.hbs} | 0 src/utils/registerHandlebarTemplates.spec.ts | 2 +- src/utils/registerHandlebarTemplates.ts | 16 +- src/utils/writeAppClient.spec.ts | 7 +- src/utils/writeClient.spec.ts | 2 +- src/utils/writeClientCore.spec.ts | 13 +- src/utils/writeClientCore.ts | 6 +- src/utils/writeClientIndex.spec.ts | 7 +- src/utils/writeClientModels.spec.ts | 7 +- src/utils/writeClientSchemas.spec.ts | 7 +- src/utils/writeClientServices.spec.ts | 7 +- test/__snapshots__/index.client.spec.js.snap | 970 +++++++++++++++++- 14 files changed, 1005 insertions(+), 44 deletions(-) rename src/templates/core/{ConcreteHttpRequest.hbs => request.hbs} (100%) diff --git a/.gitignore b/.gitignore index 2b7422568..5507b7a60 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ test/e2e/generated samples/generated samples/swagger-codegen-cli-v2.jar samples/swagger-codegen-cli-v3.jar +example diff --git a/package.json b/package.json index 9b56ef5e4..65101289d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "openapi-typescript-codegen", - "version": "0.9.3", + "name": "@parsable/openapi-typescript-codegen", + "version": "0.0.1-alpha-2", "description": "Library that generates Typescript clients based on the OpenAPI specification.", "author": "Ferdi Koomen", "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", diff --git a/src/templates/core/ConcreteHttpRequest.hbs b/src/templates/core/request.hbs similarity index 100% rename from src/templates/core/ConcreteHttpRequest.hbs rename to src/templates/core/request.hbs diff --git a/src/utils/registerHandlebarTemplates.spec.ts b/src/utils/registerHandlebarTemplates.spec.ts index f5a90aa42..ff254cc00 100644 --- a/src/utils/registerHandlebarTemplates.spec.ts +++ b/src/utils/registerHandlebarTemplates.spec.ts @@ -16,7 +16,7 @@ describe('registerHandlebarTemplates', () => { expect(templates.core.apiError).toBeDefined(); expect(templates.core.apiRequestOptions).toBeDefined(); expect(templates.core.apiResult).toBeDefined(); - expect(templates.core.concreteHttpRequest).toBeDefined(); + expect(templates.core.request).toBeDefined(); expect(templates.core.baseHttpRequest).toBeDefined(); expect(templates.client).toBeDefined(); }); diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index 0b0c45913..44762d5a1 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -5,7 +5,6 @@ import templateCoreApiError from '../templates/core/ApiError.hbs'; import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs'; import templateCoreApiResult from '../templates/core/ApiResult.hbs'; import templateCoreBaseHttpClient from '../templates/core/BaseHttpRequest.hbs'; -import templateCoreConcreteHttpClient from '../templates/core/ConcreteHttpRequest.hbs'; import fetchGetHeaders from '../templates/core/fetch/getHeaders.hbs'; import fetchGetRequestBody from '../templates/core/fetch/getRequestBody.hbs'; import fetchGetResponseBody from '../templates/core/fetch/getResponseBody.hbs'; @@ -30,6 +29,7 @@ import nodeGetResponseHeader from '../templates/core/node/getResponseHeader.hbs' 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 xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs'; import xhrGetRequestBody from '../templates/core/xhr/getRequestBody.hbs'; import xhrGetResponseBody from '../templates/core/xhr/getResponseBody.hbs'; @@ -84,7 +84,12 @@ export interface Templates { apiRequestOptions: Handlebars.TemplateDelegate; apiResult: Handlebars.TemplateDelegate; baseHttpRequest: Handlebars.TemplateDelegate; - concreteHttpRequest: Handlebars.TemplateDelegate; + request: Handlebars.TemplateDelegate; + httpRequest: { + fetch: Handlebars.TemplateDelegate; + node: Handlebars.TemplateDelegate; + xhr: Handlebars.TemplateDelegate; + }; }; } @@ -110,7 +115,12 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp apiRequestOptions: Handlebars.template(templateCoreApiRequestOptions), apiResult: Handlebars.template(templateCoreApiResult), baseHttpRequest: Handlebars.template(templateCoreBaseHttpClient), - concreteHttpRequest: Handlebars.template(templateCoreConcreteHttpClient), + request: Handlebars.template(templateCoreRequest), + httpRequest: { + fetch: Handlebars.template(fetchRequest), + node: Handlebars.template(nodeRequest), + xhr: Handlebars.template(xhrRequest), + }, }, }; diff --git a/src/utils/writeAppClient.spec.ts b/src/utils/writeAppClient.spec.ts index 0e2085c54..abafed8cf 100644 --- a/src/utils/writeAppClient.spec.ts +++ b/src/utils/writeAppClient.spec.ts @@ -29,7 +29,12 @@ describe('writeAppClient', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'concreteHttpRequest', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index 7f9d77810..649ceab4b 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -29,7 +29,7 @@ describe('writeClient', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'concreteHttpRequest', }, }; diff --git a/src/utils/writeClientCore.spec.ts b/src/utils/writeClientCore.spec.ts index e39d81214..ea2d4de2b 100644 --- a/src/utils/writeClientCore.spec.ts +++ b/src/utils/writeClientCore.spec.ts @@ -28,7 +28,12 @@ describe('writeClientCore', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'request', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; @@ -39,7 +44,7 @@ describe('writeClientCore', () => { expect(writeFile).toBeCalledWith('/ApiError.ts', 'apiError'); expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', 'apiRequestOptions'); expect(writeFile).toBeCalledWith('/ApiResult.ts', 'apiResult'); - expect(writeFile).toBeCalledWith('/request.ts', 'concreteHttpRequest'); + expect(writeFile).toBeCalledWith('/request.ts', 'request'); }); it('should write to filesystem when exportClient true', async () => { @@ -50,6 +55,8 @@ describe('writeClientCore', () => { expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', 'apiRequestOptions'); expect(writeFile).toBeCalledWith('/ApiResult.ts', 'apiResult'); expect(writeFile).toBeCalledWith('/BaseHttpRequest.ts', 'baseHttpRequest'); - expect(writeFile).toBeCalledWith('/FetchHttpRequest.ts', 'concreteHttpRequest'); + expect(writeFile).toBeCalledWith('/FetchHttpRequest.ts', 'fetchRequest'); + expect(writeFile).toBeCalledWith('/NodeHttpRequest.ts', 'nodeRequest'); + expect(writeFile).toBeCalledWith('/XhrHttpRequest.ts', 'xhrRequest'); }); }); diff --git a/src/utils/writeClientCore.ts b/src/utils/writeClientCore.ts index 21a4baa4e..11db70c2c 100644 --- a/src/utils/writeClientCore.ts +++ b/src/utils/writeClientCore.ts @@ -29,9 +29,11 @@ export async function writeClientCore(client: Client, templates: Templates, outp await writeFile(resolve(outputPath, 'ApiResult.ts'), templates.core.apiResult({})); if (exportClient) { await writeFile(resolve(outputPath, 'BaseHttpRequest.ts'), templates.core.baseHttpRequest({})); - await writeFile(resolve(outputPath, `${getHttpRequestName(httpClient)}.ts`), templates.core.concreteHttpRequest(context)); + for (const client of Object.values(HttpClient)) { + await writeFile(resolve(outputPath, `${getHttpRequestName(client)}.ts`), templates.core.httpRequest[client](context)); + } } else { - await writeFile(resolve(outputPath, `request.ts`), templates.core.concreteHttpRequest(context)); + await writeFile(resolve(outputPath, `request.ts`), templates.core.request(context)); } if (request) { diff --git a/src/utils/writeClientIndex.spec.ts b/src/utils/writeClientIndex.spec.ts index be821193b..f657b7926 100644 --- a/src/utils/writeClientIndex.spec.ts +++ b/src/utils/writeClientIndex.spec.ts @@ -29,7 +29,12 @@ describe('writeClientIndex', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpClient', - concreteHttpRequest: () => 'concreteHttpClient', + request: () => 'request', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; diff --git a/src/utils/writeClientModels.spec.ts b/src/utils/writeClientModels.spec.ts index 44227dc12..75c547f40 100644 --- a/src/utils/writeClientModels.spec.ts +++ b/src/utils/writeClientModels.spec.ts @@ -42,7 +42,12 @@ describe('writeClientModels', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'request', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; diff --git a/src/utils/writeClientSchemas.spec.ts b/src/utils/writeClientSchemas.spec.ts index 1dd4e9196..f2bab8bed 100644 --- a/src/utils/writeClientSchemas.spec.ts +++ b/src/utils/writeClientSchemas.spec.ts @@ -42,7 +42,12 @@ describe('writeClientSchemas', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'request', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; diff --git a/src/utils/writeClientServices.spec.ts b/src/utils/writeClientServices.spec.ts index 91c105121..4230aca8b 100644 --- a/src/utils/writeClientServices.spec.ts +++ b/src/utils/writeClientServices.spec.ts @@ -30,7 +30,12 @@ describe('writeClientServices', () => { apiRequestOptions: () => 'apiRequestOptions', apiResult: () => 'apiResult', baseHttpRequest: () => 'baseHttpRequest', - concreteHttpRequest: () => 'concreteHttpRequest', + request: () => 'request', + httpRequest: { + fetch: () => 'fetchRequest', + node: () => 'nodeRequest', + xhr: () => 'xhrRequest', + }, }, }; diff --git a/test/__snapshots__/index.client.spec.js.snap b/test/__snapshots__/index.client.spec.js.snap index b92bed6d9..5bebae462 100644 --- a/test/__snapshots__/index.client.spec.js.snap +++ b/test/__snapshots__/index.client.spec.js.snap @@ -350,23 +350,481 @@ export class FetchHttpRequest extends BaseHttpRequest { " `; +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/NodeHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +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'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +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; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = Buffer.from(\`\${username}:\${password}\`).toString('base64'); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBinary(options.body)) { + headers.append('Content-Type', 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBinary(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + const request: RequestInit = { + method: options.method, + headers: await getHeaders(options, config), + body: getRequestBody(options), + }; + return await fetch(url, request); +} + +function getResponseHeader(response: Response, responseHeader?: string): string | null { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +async function getResponseBody(response: Response): Promise { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} + +export class NodeHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using node-fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; + } +} +" +`; + exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/OpenAPI.ts 1`] = ` "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ import type { ApiRequestOptions } from './ApiRequestOptions'; -type Resolver = (options: ApiRequestOptions) => Promise; -type Headers = Record; +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE?: string; + VERSION?: string; + WITH_CREDENTIALS?: boolean; + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; +} +" +`; + +exports[`v2 should generate with exportClient: ./test/generated/v2_client/core/XhrHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +function isBlob(value: any): value is Blob { + return value instanceof Blob; +} + +function isSuccess(status: number): boolean { + return status >= 200 && status < 300; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = btoa(\`\${username}:\${password}\`); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBlob(options.body)) { + headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): any { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + + const xhr = new XMLHttpRequest(); + xhr.open(options.method, url, true); + xhr.withCredentials = config.WITH_CREDENTIALS ?? false; + + const headers = await getHeaders(options, config); + headers.forEach((value: string, key: string) => { + xhr.setRequestHeader(key, value); + }); + + return new Promise(resolve => { + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + resolve(xhr); + } + }; + xhr.send(getRequestBody(options)); + }); +} + +function getResponseHeader(xhr: XMLHttpRequest, responseHeader?: string): string | null { + if (responseHeader) { + const content = xhr.getResponseHeader(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +function getResponseBody(xhr: XMLHttpRequest): any { + try { + const contentType = xhr.getResponseHeader('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return JSON.parse(xhr.responseText); + } else { + return xhr.responseText; + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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, + } -export type OpenAPIConfig = { - BASE?: string; - VERSION?: string; - WITH_CREDENTIALS?: boolean; - TOKEN?: string | Resolver; - USERNAME?: string | Resolver; - PASSWORD?: string | Resolver; - HEADERS?: Headers | Resolver; + const error = errors[result.status]; + if (error) { + throw new ApiError(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} + +export class XhrHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using XHR client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; + + catchErrors(options, result); + return result; + } } " `; @@ -2600,23 +3058,247 @@ import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import type { OpenAPIConfig } from './OpenAPI'; -export class BaseHttpRequest { - readonly openApiConfig: OpenAPIConfig; +export class BaseHttpRequest { + readonly openApiConfig: OpenAPIConfig; + + constructor(openApiConfig: OpenAPIConfig) { + this.openApiConfig = openApiConfig; + } + + async request(options: ApiRequestOptions): Promise { + throw new Error('Not Implemented'); + } +}" +`; + +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/FetchHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +function isBlob(value: any): value is Blob { + return value instanceof Blob; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = btoa(\`\${username}:\${password}\`); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBlob(options.body)) { + headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + const request: RequestInit = { + method: options.method, + headers: await getHeaders(options, config), + body: getRequestBody(options), + }; + if (config.WITH_CREDENTIALS) { + request.credentials = 'include'; + } + return await fetch(url, request); +} + +function getResponseHeader(response: Response, responseHeader?: string): string | null { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +async function getResponseBody(response: Response): Promise { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} +export class FetchHttpRequest extends BaseHttpRequest { constructor(openApiConfig: OpenAPIConfig) { - this.openApiConfig = openApiConfig; + super(openApiConfig); } + /** + * Request using fetch client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ async request(options: ApiRequestOptions): Promise { - throw new Error('Not Implemented'); + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + 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, + }; + + catchErrors(options, result); + return result; } -}" +} +" `; -exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/FetchHttpRequest.ts 1`] = ` +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/NodeHttpRequest.ts 1`] = ` "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +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'; import type { ApiResult } from './ApiResult'; @@ -2635,8 +3317,11 @@ function isStringWithValue(value: any): value is string { return isString(value) && value !== ''; } -function isBlob(value: any): value is Blob { - return value instanceof Blob; +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; } function getQueryString(params: Record): string { @@ -2706,15 +3391,15 @@ async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Pr } if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = btoa(\`\${username}:\${password}\`); + const credentials = Buffer.from(\`\${username}:\${password}\`).toString('base64'); headers.append('Authorization', \`Basic \${credentials}\`); } if (options.body) { if (options.mediaType) { headers.append('Content-Type', options.mediaType); - } else if (isBlob(options.body)) { - headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isBinary(options.body)) { + headers.append('Content-Type', 'application/octet-stream'); } else if (isString(options.body)) { headers.append('Content-Type', 'text/plain'); } else { @@ -2731,7 +3416,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)) { + } else if (isString(options.body) || isBinary(options.body)) { return options.body; } else { return JSON.stringify(options.body); @@ -2746,9 +3431,6 @@ async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, ur headers: await getHeaders(options, config), body: getRequestBody(options), }; - if (config.WITH_CREDENTIALS) { - request.credentials = 'include'; - } return await fetch(url, request); } @@ -2801,13 +3483,13 @@ function catchErrors(options: ApiRequestOptions, result: ApiResult): void { } } -export class FetchHttpRequest extends BaseHttpRequest { +export class NodeHttpRequest extends BaseHttpRequest { constructor(openApiConfig: OpenAPIConfig) { super(openApiConfig); } /** - * Request using fetch client + * Request using node-fetch client * @param options The request options from the the service * @returns ApiResult * @throws ApiError @@ -2854,6 +3536,240 @@ export type OpenAPIConfig = { " `; +exports[`v3 should generate with exportClient: ./test/generated/v3_client/core/XhrHttpRequest.ts 1`] = ` +"/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import type { OpenAPIConfig } from './OpenAPI'; +import { BaseHttpRequest } from './BaseHttpRequest'; + + +function isDefined(value: T | null | undefined): value is Exclude { + return value !== undefined && value !== null; +} + +function isString(value: any): value is string { + return typeof value === 'string'; +} + +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} + +function isBlob(value: any): value is Blob { + return value instanceof Blob; +} + +function isSuccess(status: number): boolean { + return status >= 200 && status < 300; +} + +function getQueryString(params: Record): string { + const qs: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(value => { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + }); + } else { + qs.push(\`\${encodeURIComponent(key)}=\${encodeURIComponent(String(value))}\`); + } + } + }); + if (qs.length > 0) { + return \`?\${qs.join('&')}\`; + } + return ''; +} + +function getUrl(options: ApiRequestOptions, config: OpenAPIConfig): string { + const path = options.path.replace(/[:]/g, '_'); + const url = \`\${config.BASE}\${path}\`; + + if (options.query) { + return \`\${url}\${getQueryString(options.query)}\`; + } + return url; +} + +function getFormData(params: Record): FormData { + const formData = new FormData(); + Object.keys(params).forEach(key => { + const value = params[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); + return formData; +} + +type Resolver = (options: ApiRequestOptions) => Promise; + +async function resolve(options: ApiRequestOptions, resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +} + +async function getHeaders(options: ApiRequestOptions, config: OpenAPIConfig): Promise { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const defaultHeaders = await resolve(options, config.HEADERS); + + const headers = new Headers({ + Accept: 'application/json', + ...defaultHeaders, + ...options.headers, + }); + + if (isStringWithValue(token)) { + headers.append('Authorization', \`Bearer \${token}\`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = btoa(\`\${username}:\${password}\`); + headers.append('Authorization', \`Basic \${credentials}\`); + } + + if (options.body) { + if (options.mediaType) { + headers.append('Content-Type', options.mediaType); + } else if (isBlob(options.body)) { + headers.append('Content-Type', options.body.type || 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} + +function getRequestBody(options: ApiRequestOptions): any { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} + +async function sendRequest(options: ApiRequestOptions, config: OpenAPIConfig, url: string): Promise { + + const xhr = new XMLHttpRequest(); + xhr.open(options.method, url, true); + xhr.withCredentials = config.WITH_CREDENTIALS ?? false; + + const headers = await getHeaders(options, config); + headers.forEach((value: string, key: string) => { + xhr.setRequestHeader(key, value); + }); + + return new Promise(resolve => { + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + resolve(xhr); + } + }; + xhr.send(getRequestBody(options)); + }); +} + +function getResponseHeader(xhr: XMLHttpRequest, responseHeader?: string): string | null { + if (responseHeader) { + const content = xhr.getResponseHeader(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} + +function getResponseBody(xhr: XMLHttpRequest): any { + try { + const contentType = xhr.getResponseHeader('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return JSON.parse(xhr.responseText); + } else { + return xhr.responseText; + } + } + } catch (error) { + console.error(error); + } + return null; +} + +function catchErrors(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(result, error); + } + + if (!result.ok) { + throw new ApiError(result, 'Generic Error'); + } +} + +export class XhrHttpRequest extends BaseHttpRequest { + constructor(openApiConfig: OpenAPIConfig) { + super(openApiConfig); + } + + /** + * Request using XHR client + * @param options The request options from the the service + * @returns ApiResult + * @throws ApiError + */ + async request(options: ApiRequestOptions): Promise { + const url = getUrl(options, this.openApiConfig); + const response = await sendRequest(options, this.openApiConfig, url); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; + + catchErrors(options, result); + return result; + } +} +" +`; + exports[`v3 should generate with exportClient: ./test/generated/v3_client/index.ts 1`] = ` "/* istanbul ignore file */ /* tslint:disable */