diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..c24b7182 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/templates diff --git a/src/index.ts b/src/index.ts index b59d8f46..e25140d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,9 +24,9 @@ export enum HttpClient { * given language it will generate the client, including the typed models, validation schemas, * service layer, etc. * @param input The relative location of the OpenAPI spec. - * @param output The relative location of the output directory - * @param language: The language that should be generated (Typescript or Javascript) - * @param httpClient: The selected httpClient (fetch or XHR) + * @param output The relative location of the output directory. + * @param language: The language that should be generated (Typescript or Javascript). + * @param httpClient: The selected httpClient (fetch or XHR). */ export function generate(input: string, output: string, language: Language = Language.TYPESCRIPT, httpClient: HttpClient = HttpClient.FETCH): void { const inputPath = path.resolve(process.cwd(), input); @@ -52,13 +52,13 @@ export function generate(input: string, output: string, language: Language = Lan // Generate and write version 2 client if (openApiVersion === OpenApiVersion.V2) { const clientV2 = parseV2(openApi); - writeClient(clientV2, language, templates, outputPath); + writeClient(clientV2, language, httpClient, templates, outputPath); } // Generate and write version 3 client if (openApiVersion === OpenApiVersion.V3) { const clientV3 = parseV3(openApi); - writeClient(clientV3, language, templates, outputPath); + writeClient(clientV3, language, httpClient, templates, outputPath); } } } catch (e) { diff --git a/src/templates/typescript/core/ApiError.ts b/src/templates/typescript/core/ApiError.ts index a5d86214..375350de 100644 --- a/src/templates/typescript/core/ApiError.ts +++ b/src/templates/typescript/core/ApiError.ts @@ -7,6 +7,7 @@ import { isSuccess } from './isSuccess'; import { Result } from './Result'; export class ApiError extends Error { + public readonly url: string; public readonly status: number; public readonly statusText: string; @@ -41,20 +42,13 @@ export namespace ApiError { */ export function catchGenericError(result: Result): void { switch (result.status) { - case 400: - throw new ApiError(result, ApiError.Message.BAD_REQUEST); - case 401: - throw new ApiError(result, ApiError.Message.UNAUTHORIZED); - case 403: - throw new ApiError(result, ApiError.Message.FORBIDDEN); - case 404: - throw new ApiError(result, ApiError.Message.NOT_FOUND); - case 500: - throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR); - case 502: - throw new ApiError(result, ApiError.Message.BAD_GATEWAY); - case 503: - throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE); + case 400: throw new ApiError(result, ApiError.Message.BAD_REQUEST); + case 401: throw new ApiError(result, ApiError.Message.UNAUTHORIZED); + case 403: throw new ApiError(result, ApiError.Message.FORBIDDEN); + case 404: throw new ApiError(result, ApiError.Message.NOT_FOUND); + case 500: throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR); + case 502: throw new ApiError(result, ApiError.Message.BAD_GATEWAY); + case 503: throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE); } if (!isSuccess(result.status)) { diff --git a/src/templates/typescript/core/RequestOptions.ts b/src/templates/typescript/core/RequestOptions.ts index 7816bf98..08c22fb5 100644 --- a/src/templates/typescript/core/RequestOptions.ts +++ b/src/templates/typescript/core/RequestOptions.ts @@ -4,6 +4,7 @@ /* prettier-ignore */ export interface RequestOptions { + type: 'fetch' | 'xhr'; method: string; path: string; headers?: { [key: string]: any }; diff --git a/src/templates/typescript/core/request.ts b/src/templates/typescript/core/request.ts index 84ce4eb8..1c1c2657 100644 --- a/src/templates/typescript/core/request.ts +++ b/src/templates/typescript/core/request.ts @@ -8,6 +8,7 @@ import { getQueryString } from './getQueryString'; import { OpenAPI } from './OpenAPI'; import { RequestOptions } from './RequestOptions'; import { requestUsingFetch } from './requestUsingFetch'; +import { requestUsingXHR } from './requestUsingXHR'; import { Result } from './Result'; /** @@ -16,6 +17,7 @@ import { Result } from './Result'; * @returns Result object (see above) */ export async function request(options: Readonly): Promise> { + // Create the request URL let url = `${OpenAPI.BASE}${options.path}`; @@ -46,6 +48,7 @@ export async function request(options: Readonly): Promi if (options.formData) { request.body = getFormData(options.formData); } else if (options.body) { + // If this is blob data, then pass it directly to the body and set content type. // Otherwise we just convert request data to JSON string (needed for fetch api) if (options.body instanceof Blob) { @@ -60,7 +63,12 @@ export async function request(options: Readonly): Promi } try { - return await requestUsingFetch(url, request); + switch (options.type) { + case 'fetch': + return await requestUsingFetch(url, request); + case 'xhr': + return await requestUsingXHR(url, request); + } } catch (error) { return { url, diff --git a/src/templates/typescript/core/requestUsingFetch.ts b/src/templates/typescript/core/requestUsingFetch.ts index ef3a1c5a..27d217c9 100644 --- a/src/templates/typescript/core/requestUsingFetch.ts +++ b/src/templates/typescript/core/requestUsingFetch.ts @@ -13,6 +13,7 @@ import { Result } from './Result'; * @param request The request object, containing method, headers, body, etc. */ export async function requestUsingFetch(url: string, request: Readonly): Promise> { + // Fetch response using fetch API. const response = await fetch(url, request); diff --git a/src/templates/typescript/core/requestUsingXHR.ts b/src/templates/typescript/core/requestUsingXHR.ts index acf30e30..79a74d1d 100644 --- a/src/templates/typescript/core/requestUsingXHR.ts +++ b/src/templates/typescript/core/requestUsingXHR.ts @@ -31,6 +31,7 @@ export async function requestUsingXHR(url: string, request: Readonly { if (xhr.readyState === XMLHttpRequest.DONE) { + // Create result object. const result: Result = { url, diff --git a/src/templates/typescript/service.hbs b/src/templates/typescript/service.hbs index 03993de6..b41d59c1 100644 --- a/src/templates/typescript/service.hbs +++ b/src/templates/typescript/service.hbs @@ -14,6 +14,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { Result } from '../core/Result'; export class {{{name}}} { + {{#each operations}} /** {{#if deprecated}} @@ -30,10 +31,12 @@ export class {{{name}}} { * @param {{{name}}} {{{description}}} {{/each}} {{/if}} + * @return {{{result}}} */ public static async {{{name}}}({{#each parameters}}{{{name}}}{{#unless required}}?{{/unless}}: {{{type}}}{{#if nullable}} | null{{/if}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{{result}}}> { const result: Result<{{{result}}}> = await request({ + type: '{{{../httpClient}}}', method: '{{{method}}}', path: `{{{path}}}`, {{#if parametersHeader}} diff --git a/src/utils/readHandlebarsTemplates.ts b/src/utils/readHandlebarsTemplates.ts index 18555d83..1c83c365 100644 --- a/src/utils/readHandlebarsTemplates.ts +++ b/src/utils/readHandlebarsTemplates.ts @@ -18,11 +18,13 @@ export interface Templates { export function readHandlebarsTemplates(language: Language): Templates { try { registerHandlebarHelpers(); + const templates: Templates = { index: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/index.hbs`)), model: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/model.hbs`)), service: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/service.hbs`)), }; + Handlebars.registerPartial({ exportGeneric: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/exportGeneric.hbs`)), exportReference: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/exportReference.hbs`)), @@ -45,6 +47,7 @@ export function readHandlebarsTemplates(language: Language): Templates { typeForReference: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/typeForReference.hbs`)), typeForGeneric: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/typeForGeneric.hbs`)), }); + return templates; } catch (e) { throw e; diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index 20c42e8e..8e9e449e 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -6,18 +6,19 @@ import * as mkdirp from 'mkdirp'; import * as rimraf from 'rimraf'; import { Templates } from './readHandlebarsTemplates'; import { writeClientIndex } from './writeClientIndex'; -import { Language } from '../index'; +import { HttpClient, Language } from '../index'; import * as fs from 'fs'; import * as glob from 'glob'; /** - * Write our OpenAPI client, using the given templates at the given output path - * @param client: Client object with all the models, services, etc. - * @param language: The output language (Typescript or javascript). - * @param templates: Templates wrapper with all loaded Handlebars templates. + * Write our OpenAPI client, using the given templates at the given output path. + * @param client Client object with all the models, services, etc. + * @param language The language that should be generated (Typescript or Javascript). + * @param httpClient The selected httpClient (fetch or XHR). + * @param templates Templates wrapper with all loaded Handlebars templates. * @param outputPath */ -export function writeClient(client: Client, language: Language, templates: Templates, outputPath: string): void { +export function writeClient(client: Client, language: Language, httpClient: HttpClient, templates: Templates, outputPath: string): void { const outputPathCore = path.resolve(outputPath, 'core'); const outputPathModels = path.resolve(outputPath, 'models'); const outputPathServices = path.resolve(outputPath, 'services'); @@ -52,8 +53,8 @@ export function writeClient(client: Client, language: Language, templates: Templ // Write the client files try { writeClientIndex(client, language, templates, outputPath); - writeClientModels(client.models, language, templates, outputPathModels); - writeClientServices(client.services, language, templates, outputPathServices); + writeClientModels(client.models, language, httpClient, templates, outputPathModels); + writeClientServices(client.services, language, httpClient, templates, outputPathServices); } catch (e) { throw e; } diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index 80a7e793..902c52a4 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -11,10 +11,10 @@ import { Templates } from './readHandlebarsTemplates'; * Generate the OpenAPI client index file using the Handlebar template and write it to disk. * The index file just contains all the exports you need to use the client as a standalone * library. But yuo can also import individual models and services directly. - * @param client: Client object, containing, models, schemas and services. - * @param language: The output language (Typescript or javascript). - * @param templates: The loaded handlebar templates. - * @param outputPath: + * @param client Client object, containing, models, schemas and services. + * @param language The output language (Typescript or javascript). + * @param templates The loaded handlebar templates. + * @param outputPath */ export function writeClientIndex(client: Client, language: Language, templates: Templates, outputPath: string): void { const fileName = getFileName('index', language); diff --git a/src/utils/writeClientModels.spec.ts b/src/utils/writeClientModels.spec.ts index 740e44bf..6bb2af26 100644 --- a/src/utils/writeClientModels.spec.ts +++ b/src/utils/writeClientModels.spec.ts @@ -1,7 +1,7 @@ import { writeClientModels } from './writeClientModels'; import * as fs from 'fs'; import { Model } from '../client/interfaces/Model'; -import { Language } from '../index'; +import { HttpClient, Language } from '../index'; import { Templates } from './readHandlebarsTemplates'; jest.mock('fs'); @@ -33,7 +33,7 @@ describe('writeClientModels', () => { model: () => 'dummy', service: () => 'dummy', }; - writeClientModels(models, Language.TYPESCRIPT, templates, '/'); + writeClientModels(models, Language.TYPESCRIPT, HttpClient.FETCH, templates, '/'); expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy'); }); }); diff --git a/src/utils/writeClientModels.ts b/src/utils/writeClientModels.ts index 6d7c108c..2e70d7a9 100644 --- a/src/utils/writeClientModels.ts +++ b/src/utils/writeClientModels.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import { Model } from '../client/interfaces/Model'; import * as path from 'path'; -import { Language } from '../index'; +import { HttpClient, Language } from '../index'; import { getFileName } from './getFileName'; import { exportModel } from './exportModel'; import { Templates } from './readHandlebarsTemplates'; @@ -9,17 +9,22 @@ import { format } from './format'; /** * Generate Models using the Handlebar template and write to disk. - * @param models: Array of Models to write. - * @param language: The output language (Typescript or javascript). - * @param templates: The loaded handlebar templates. - * @param outputPath: + * @param models Array of Models to write. + * @param language The output language (Typescript or javascript). + * @param httpClient The selected httpClient (fetch or XHR). + * @param templates The loaded handlebar templates. + * @param outputPath */ -export function writeClientModels(models: Map, language: Language, templates: Templates, outputPath: string): void { +export function writeClientModels(models: Map, language: Language, httpClient: HttpClient, templates: Templates, outputPath: string): void { models.forEach(model => { const fileName = getFileName(model.name, language); try { const templateData = exportModel(model); - const templateResult = templates.model(templateData); + const templateResult = templates.model({ + language, + httpClient, + ...templateData, + }); fs.writeFileSync(path.resolve(outputPath, fileName), format(templateResult)); } catch (e) { throw new Error(`Could not write model: "${fileName}"`); diff --git a/src/utils/writeClientServices.spec.ts b/src/utils/writeClientServices.spec.ts index 6b3a8833..cf16df65 100644 --- a/src/utils/writeClientServices.spec.ts +++ b/src/utils/writeClientServices.spec.ts @@ -1,7 +1,7 @@ import { writeClientServices } from './writeClientServices'; import * as fs from 'fs'; import { Service } from '../client/interfaces/Service'; -import { Language } from '../index'; +import { HttpClient, Language } from '../index'; import { Templates } from './readHandlebarsTemplates'; jest.mock('fs'); @@ -21,7 +21,7 @@ describe('writeClientServices', () => { model: () => 'dummy', service: () => 'dummy', }; - writeClientServices(services, Language.TYPESCRIPT, templates, '/'); + writeClientServices(services, Language.TYPESCRIPT, HttpClient.FETCH, templates, '/'); expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy'); }); }); diff --git a/src/utils/writeClientServices.ts b/src/utils/writeClientServices.ts index 63efd5e8..d8f865f7 100644 --- a/src/utils/writeClientServices.ts +++ b/src/utils/writeClientServices.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { Service } from '../client/interfaces/Service'; -import { Language } from '../index'; +import { HttpClient, Language } from '../index'; import { getFileName } from './getFileName'; import { exportService } from './exportService'; import { Templates } from './readHandlebarsTemplates'; @@ -9,17 +9,22 @@ import { format } from './format'; /** * Generate Services using the Handlebar template and write to disk. - * @param services: Array of Services to write. - * @param language: The output language (Typescript or javascript). - * @param templates: The loaded handlebar templates. - * @param outputPath: + * @param services Array of Services to write. + * @param language The output language (Typescript or javascript). + * @param httpClient The selected httpClient (fetch or XHR). + * @param templates The loaded handlebar templates. + * @param outputPath */ -export function writeClientServices(services: Map, language: Language, templates: Templates, outputPath: string): void { +export function writeClientServices(services: Map, language: Language, httpClient: HttpClient, templates: Templates, outputPath: string): void { services.forEach(service => { const fileName = getFileName(service.name, language); try { const templateData = exportService(service); - const templateResult = templates.service(templateData); + const templateResult = templates.service({ + language, + httpClient, + ...templateData, + }); fs.writeFileSync(path.resolve(outputPath, fileName), format(templateResult)); } catch (e) { throw new Error(`Could not write service: "${fileName}"`); diff --git a/test/index.js b/test/index.js index c8c07def..81e21875 100755 --- a/test/index.js +++ b/test/index.js @@ -25,12 +25,12 @@ OpenAPI.generate( // OpenAPI.HttpClient.FETCH, // ); // -OpenAPI.generate( - './test/mock/v2/test-sites.json', - './test/tmp/v2/ts/test-sites', - OpenAPI.Language.TYPESCRIPT, - OpenAPI.HttpClient.FETCH, -); +// OpenAPI.generate( +// './test/mock/v2/test-sites.json', +// './test/tmp/v2/ts/test-sites', +// OpenAPI.Language.TYPESCRIPT, +// OpenAPI.HttpClient.FETCH, +// ); // // OpenAPI.generate( // './test/mock/v2/test-petstore.yaml', diff --git a/test/mock/v2/spec.json b/test/mock/v2/spec.json index 5108c7d3..9908d14c 100644 --- a/test/mock/v2/spec.json +++ b/test/mock/v2/spec.json @@ -10,6 +10,14 @@ "http" ], "paths": { + "/api/v{api-version}/dummy": { + "get": { + "tags": [ + "Service" + ], + "operationId": "GetCallWithoutParametersAndResponse" + } + } }, "definitions": { "SimpleInteger": {