From fe6a3be63ec5dd9f299917e9f82f084c3422eb32 Mon Sep 17 00:00:00 2001 From: Ferdi Koomen Date: Sun, 15 Nov 2020 12:08:31 +0100 Subject: [PATCH] - Fixed #411 --- src/templates/core/OpenAPI.hbs | 13 ++- src/templates/core/fetch/getHeaders.hbs | 13 ++- src/templates/core/fetch/request.hbs | 5 +- src/templates/core/functions/getToken.hbs | 6 -- .../core/functions/isStringWithValue.hbs | 3 + src/templates/core/functions/resolve.hbs | 8 ++ src/templates/core/node/getHeaders.hbs | 13 ++- src/templates/core/node/request.hbs | 5 +- src/templates/core/xhr/getHeaders.hbs | 13 ++- src/templates/core/xhr/request.hbs | 5 +- src/utils/registerHandlebarTemplates.ts | 6 +- src/utils/writeClient.ts | 12 ++- test/__snapshots__/index.spec.js.snap | 80 +++++++++++++++---- test/e2e/v3.babel.spec.js | 13 +++ test/e2e/v3.fetch.spec.js | 13 +++ test/e2e/v3.node.spec.js | 11 +++ test/e2e/v3.xhr.spec.js | 13 +++ 17 files changed, 193 insertions(+), 39 deletions(-) delete mode 100644 src/templates/core/functions/getToken.hbs create mode 100644 src/templates/core/functions/isStringWithValue.hbs create mode 100644 src/templates/core/functions/resolve.hbs diff --git a/src/templates/core/OpenAPI.hbs b/src/templates/core/OpenAPI.hbs index aa32b24d..6e7361e0 100644 --- a/src/templates/core/OpenAPI.hbs +++ b/src/templates/core/OpenAPI.hbs @@ -1,15 +1,24 @@ {{>header}} +type Resolver = () => Promise; +type Headers = Record; + interface Config { BASE: string; VERSION: string; WITH_CREDENTIALS: boolean; - TOKEN: string | (() => Promise); + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; } export const OpenAPI: Config = { BASE: '{{{server}}}', VERSION: '{{{version}}}', WITH_CREDENTIALS: false, - TOKEN: '', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, }; diff --git a/src/templates/core/fetch/getHeaders.hbs b/src/templates/core/fetch/getHeaders.hbs index f49610d0..046a4c84 100644 --- a/src/templates/core/fetch/getHeaders.hbs +++ b/src/templates/core/fetch/getHeaders.hbs @@ -1,14 +1,23 @@ async function getHeaders(options: ApiRequestOptions): Promise { const headers = new Headers({ Accept: 'application/json', + ...OpenAPI.HEADERS, ...options.headers, }); - const token = await getToken(); - if (isDefined(token) && token !== '') { + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + 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 (isBlob(options.body)) { headers.append('Content-Type', options.body.type || 'application/octet-stream'); diff --git a/src/templates/core/fetch/request.hbs b/src/templates/core/fetch/request.hbs index 322ddb56..5e6f47e2 100644 --- a/src/templates/core/fetch/request.hbs +++ b/src/templates/core/fetch/request.hbs @@ -11,6 +11,9 @@ import { OpenAPI } from './OpenAPI'; {{>functions/isString}} +{{>functions/isStringWithValue}} + + {{>functions/isBlob}} @@ -23,7 +26,7 @@ import { OpenAPI } from './OpenAPI'; {{>functions/getFormData}} -{{>functions/getToken}} +{{>functions/resolve}} {{>fetch/getHeaders}} diff --git a/src/templates/core/functions/getToken.hbs b/src/templates/core/functions/getToken.hbs deleted file mode 100644 index 7619004c..00000000 --- a/src/templates/core/functions/getToken.hbs +++ /dev/null @@ -1,6 +0,0 @@ -async function getToken(): Promise { - if (typeof OpenAPI.TOKEN === 'function') { - return OpenAPI.TOKEN(); - } - return OpenAPI.TOKEN; -} diff --git a/src/templates/core/functions/isStringWithValue.hbs b/src/templates/core/functions/isStringWithValue.hbs new file mode 100644 index 00000000..3ea45353 --- /dev/null +++ b/src/templates/core/functions/isStringWithValue.hbs @@ -0,0 +1,3 @@ +function isStringWithValue(value: any): value is string { + return isString(value) && value !== ''; +} diff --git a/src/templates/core/functions/resolve.hbs b/src/templates/core/functions/resolve.hbs new file mode 100644 index 00000000..412c0b4e --- /dev/null +++ b/src/templates/core/functions/resolve.hbs @@ -0,0 +1,8 @@ +type Resolver = () => Promise; + +async function resolve(resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(); + } + return resolver; +} diff --git a/src/templates/core/node/getHeaders.hbs b/src/templates/core/node/getHeaders.hbs index b8d4dc77..e4ee40b2 100644 --- a/src/templates/core/node/getHeaders.hbs +++ b/src/templates/core/node/getHeaders.hbs @@ -1,14 +1,23 @@ async function getHeaders(options: ApiRequestOptions): Promise { const headers = new Headers({ Accept: 'application/json', + ...OpenAPI.HEADERS, ...options.headers, }); - const token = await getToken(); - if (isDefined(token) && token !== '') { + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + 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 (isBinary(options.body)) { headers.append('Content-Type', 'application/octet-stream'); diff --git a/src/templates/core/node/request.hbs b/src/templates/core/node/request.hbs index ee5e3338..2c3a5247 100644 --- a/src/templates/core/node/request.hbs +++ b/src/templates/core/node/request.hbs @@ -15,6 +15,9 @@ import { OpenAPI } from './OpenAPI'; {{>functions/isString}} +{{>functions/isStringWithValue}} + + {{>functions/isBinary}} @@ -27,7 +30,7 @@ import { OpenAPI } from './OpenAPI'; {{>functions/getFormData}} -{{>functions/getToken}} +{{>functions/resolve}} {{>node/getHeaders}} diff --git a/src/templates/core/xhr/getHeaders.hbs b/src/templates/core/xhr/getHeaders.hbs index f49610d0..046a4c84 100644 --- a/src/templates/core/xhr/getHeaders.hbs +++ b/src/templates/core/xhr/getHeaders.hbs @@ -1,14 +1,23 @@ async function getHeaders(options: ApiRequestOptions): Promise { const headers = new Headers({ Accept: 'application/json', + ...OpenAPI.HEADERS, ...options.headers, }); - const token = await getToken(); - if (isDefined(token) && token !== '') { + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + 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 (isBlob(options.body)) { headers.append('Content-Type', options.body.type || 'application/octet-stream'); diff --git a/src/templates/core/xhr/request.hbs b/src/templates/core/xhr/request.hbs index 4d21325a..88f05c6d 100644 --- a/src/templates/core/xhr/request.hbs +++ b/src/templates/core/xhr/request.hbs @@ -11,6 +11,9 @@ import { OpenAPI } from './OpenAPI'; {{>functions/isString}} +{{>functions/isStringWithValue}} + + {{>functions/isBlob}} @@ -26,7 +29,7 @@ import { OpenAPI } from './OpenAPI'; {{>functions/getFormData}} -{{>functions/getToken}} +{{>functions/resolve}} {{>fetch/getHeaders}} diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index e555eb1b..7bd3a0e0 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -12,13 +12,14 @@ import fetchSendRequest from '../templates/core/fetch/sendRequest.hbs'; import functionCatchErrors from '../templates/core/functions/catchErrors.hbs'; import functionGetFormData from '../templates/core/functions/getFormData.hbs'; import functionGetQueryString from '../templates/core/functions/getQueryString.hbs'; -import functionGetToken from '../templates/core/functions/getToken.hbs'; import functionGetUrl from '../templates/core/functions/getUrl.hbs'; import functionIsBinary from '../templates/core/functions/isBinary.hbs'; import functionIsBlob from '../templates/core/functions/isBlob.hbs'; import functionIsDefined from '../templates/core/functions/isDefined.hbs'; import functionIsString from '../templates/core/functions/isString.hbs'; +import functionIsStringWithValue from '../templates/core/functions/isStringWithValue.hbs'; import functionIsSuccess from '../templates/core/functions/isSuccess.hbs'; +import functionResolve from '../templates/core/functions/resolve.hbs'; import nodeGetHeaders from '../templates/core/node/getHeaders.hbs'; import nodeGetRequestBody from '../templates/core/node/getRequestBody.hbs'; import nodeGetResponseBody from '../templates/core/node/getResponseBody.hbs'; @@ -132,14 +133,15 @@ export function registerHandlebarTemplates(): Templates { // Generic functions used in 'request' file @see src/templates/core/request.hbs for more info Handlebars.registerPartial('functions/catchErrors', Handlebars.template(functionCatchErrors)); Handlebars.registerPartial('functions/getFormData', Handlebars.template(functionGetFormData)); - Handlebars.registerPartial('functions/getToken', Handlebars.template(functionGetToken)); Handlebars.registerPartial('functions/getQueryString', Handlebars.template(functionGetQueryString)); Handlebars.registerPartial('functions/getUrl', Handlebars.template(functionGetUrl)); Handlebars.registerPartial('functions/isBinary', Handlebars.template(functionIsBinary)); Handlebars.registerPartial('functions/isBlob', Handlebars.template(functionIsBlob)); Handlebars.registerPartial('functions/isDefined', Handlebars.template(functionIsDefined)); Handlebars.registerPartial('functions/isString', Handlebars.template(functionIsString)); + Handlebars.registerPartial('functions/isStringWithValue', Handlebars.template(functionIsStringWithValue)); Handlebars.registerPartial('functions/isSuccess', Handlebars.template(functionIsSuccess)); + Handlebars.registerPartial('functions/resolve', Handlebars.template(functionResolve)); // Specific files for the fetch client implementation Handlebars.registerPartial('fetch/getHeaders', Handlebars.template(fetchGetHeaders)); diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index 95625b3e..e20d47a4 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -46,28 +46,32 @@ export async function writeClient( throw new Error(`Output folder is not a subdirectory of the current working directory`); } - await rmdir(outputPath); - await mkdir(outputPath); - if (exportCore) { + await rmdir(outputPathCore); await mkdir(outputPathCore); await writeClientCore(client, templates, outputPathCore, httpClient); } if (exportServices) { + await rmdir(outputPathServices); await mkdir(outputPathServices); await writeClientServices(client.services, templates, outputPathServices, httpClient, useUnionTypes, useOptions); } if (exportSchemas) { + await rmdir(outputPathSchemas); await mkdir(outputPathSchemas); await writeClientSchemas(client.models, templates, outputPathSchemas, httpClient, useUnionTypes); } if (exportModels) { + await rmdir(outputPathModels); await mkdir(outputPathModels); await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes); } - await writeClientIndex(client, templates, outputPath, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas); + if (exportCore || exportServices || exportSchemas || exportModels) { + await mkdir(outputPath); + await writeClientIndex(client, templates, outputPath, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas); + } } diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index b03efe7c..484bfec0 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -57,18 +57,27 @@ exports[`v2 should generate: ./test/generated/v2/core/OpenAPI.ts 1`] = ` "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +type Resolver = () => Promise; +type Headers = Record; + interface Config { BASE: string; VERSION: string; WITH_CREDENTIALS: boolean; - TOKEN: string | (() => Promise); + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; } export const OpenAPI: Config = { BASE: 'http://localhost:3000/base', VERSION: '1.0', WITH_CREDENTIALS: false, - TOKEN: '', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, };" `; @@ -89,6 +98,10 @@ 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; } @@ -134,24 +147,35 @@ function getFormData(params: Record): FormData { return formData; } -async function getToken(): Promise { - if (typeof OpenAPI.TOKEN === 'function') { - return OpenAPI.TOKEN(); +type Resolver = () => Promise; + +async function resolve(resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(); } - return OpenAPI.TOKEN; + return resolver; } async function getHeaders(options: ApiRequestOptions): Promise { const headers = new Headers({ Accept: 'application/json', + ...OpenAPI.HEADERS, ...options.headers, }); - const token = await getToken(); - if (isDefined(token) && token !== '') { + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + 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 (isBlob(options.body)) { headers.append('Content-Type', options.body.type || 'application/octet-stream'); @@ -2354,18 +2378,27 @@ exports[`v3 should generate: ./test/generated/v3/core/OpenAPI.ts 1`] = ` "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +type Resolver = () => Promise; +type Headers = Record; + interface Config { BASE: string; VERSION: string; WITH_CREDENTIALS: boolean; - TOKEN: string | (() => Promise); + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; } export const OpenAPI: Config = { BASE: 'http://localhost:3000/base', VERSION: '1.0', WITH_CREDENTIALS: false, - TOKEN: '', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, };" `; @@ -2386,6 +2419,10 @@ 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; } @@ -2431,24 +2468,35 @@ function getFormData(params: Record): FormData { return formData; } -async function getToken(): Promise { - if (typeof OpenAPI.TOKEN === 'function') { - return OpenAPI.TOKEN(); +type Resolver = () => Promise; + +async function resolve(resolver?: T | Resolver): Promise { + if (typeof resolver === 'function') { + return (resolver as Resolver)(); } - return OpenAPI.TOKEN; + return resolver; } async function getHeaders(options: ApiRequestOptions): Promise { const headers = new Headers({ Accept: 'application/json', + ...OpenAPI.HEADERS, ...options.headers, }); - const token = await getToken(); - if (isDefined(token) && token !== '') { + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + 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 (isBlob(options.body)) { headers.append('Content-Type', options.body.type || 'application/octet-stream'); diff --git a/test/e2e/v3.babel.spec.js b/test/e2e/v3.babel.spec.js index 41ecb4cd..e5ea0f84 100644 --- a/test/e2e/v3.babel.spec.js +++ b/test/e2e/v3.babel.spec.js @@ -26,11 +26,24 @@ describe('v3.fetch', () => { const result = await browser.evaluate(async () => { const { OpenAPI, SimpleService } = window.api; OpenAPI.TOKEN = window.tokenRequest; + OpenAPI.USERNAME = undefined; + OpenAPI.PASSWORD = undefined; return await SimpleService.getCallWithoutParametersAndResponse(); }); expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { OpenAPI, SimpleService } = window.api; + OpenAPI.TOKEN = undefined; + OpenAPI.USERNAME = 'username'; + OpenAPI.PASSWORD = 'password'; + return await SimpleService.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + it('complexService', async () => { const result = await browser.evaluate(async () => { const { ComplexService } = window.api; diff --git a/test/e2e/v3.fetch.spec.js b/test/e2e/v3.fetch.spec.js index 84d08ab3..435fd57d 100644 --- a/test/e2e/v3.fetch.spec.js +++ b/test/e2e/v3.fetch.spec.js @@ -26,11 +26,24 @@ describe('v3.fetch', () => { const result = await browser.evaluate(async () => { const { OpenAPI, SimpleService } = window.api; OpenAPI.TOKEN = window.tokenRequest; + OpenAPI.USERNAME = undefined; + OpenAPI.PASSWORD = undefined; return await SimpleService.getCallWithoutParametersAndResponse(); }); expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { OpenAPI, SimpleService } = window.api; + OpenAPI.TOKEN = undefined; + OpenAPI.USERNAME = 'username'; + OpenAPI.PASSWORD = 'password'; + return await SimpleService.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + it('complexService', async () => { const result = await browser.evaluate(async () => { const { ComplexService } = window.api; diff --git a/test/e2e/v3.node.spec.js b/test/e2e/v3.node.spec.js index 10102a54..88626d73 100644 --- a/test/e2e/v3.node.spec.js +++ b/test/e2e/v3.node.spec.js @@ -20,11 +20,22 @@ describe('v3.node', () => { const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js'); const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN') OpenAPI.TOKEN = tokenRequest; + OpenAPI.USERNAME = undefined; + OpenAPI.PASSWORD = undefined; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); + it('uses credentials', async () => { + const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js'); + OpenAPI.TOKEN = undefined; + OpenAPI.USERNAME = 'username'; + OpenAPI.PASSWORD = 'password'; + const result = await SimpleService.getCallWithoutParametersAndResponse(); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + it('complexService', async () => { const { ComplexService } = require('./generated/v3/node/index.js'); const result = await ComplexService.complexTypes({ diff --git a/test/e2e/v3.xhr.spec.js b/test/e2e/v3.xhr.spec.js index ae605a55..88e0ff22 100644 --- a/test/e2e/v3.xhr.spec.js +++ b/test/e2e/v3.xhr.spec.js @@ -26,11 +26,24 @@ describe('v3.xhr', () => { const result = await browser.evaluate(async () => { const { OpenAPI, SimpleService } = window.api; OpenAPI.TOKEN = window.tokenRequest; + OpenAPI.USERNAME = undefined; + OpenAPI.PASSWORD = undefined; return await SimpleService.getCallWithoutParametersAndResponse(); }); expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); + it('uses credentials', async () => { + const result = await browser.evaluate(async () => { + const { OpenAPI, SimpleService } = window.api; + OpenAPI.TOKEN = undefined; + OpenAPI.USERNAME = 'username'; + OpenAPI.PASSWORD = 'password'; + return await SimpleService.getCallWithoutParametersAndResponse(); + }); + expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + it('complexService', async () => { const result = await browser.evaluate(async () => { const { ComplexService } = window.api;