This commit is contained in:
Ferdi Koomen 2020-11-15 12:08:31 +01:00
parent bf30cbfb85
commit fe6a3be63e
17 changed files with 193 additions and 39 deletions

View File

@ -1,15 +1,24 @@
{{>header}}
type Resolver<T> = () => Promise<T>;
type Headers = Record<string, string>;
interface Config {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
TOKEN: string | (() => Promise<string>);
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;
HEADERS?: Headers | Resolver<Headers>;
}
export const OpenAPI: Config = {
BASE: '{{{server}}}',
VERSION: '{{{version}}}',
WITH_CREDENTIALS: false,
TOKEN: '',
TOKEN: undefined,
USERNAME: undefined,
PASSWORD: undefined,
HEADERS: undefined,
};

View File

@ -1,14 +1,23 @@
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
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');

View File

@ -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}}

View File

@ -1,6 +0,0 @@
async function getToken(): Promise<string> {
if (typeof OpenAPI.TOKEN === 'function') {
return OpenAPI.TOKEN();
}
return OpenAPI.TOKEN;
}

View File

@ -0,0 +1,3 @@
function isStringWithValue(value: any): value is string {
return isString(value) && value !== '';
}

View File

@ -0,0 +1,8 @@
type Resolver<T> = () => Promise<T>;
async function resolve<T>(resolver?: T | Resolver<T>): Promise<T | undefined> {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)();
}
return resolver;
}

View File

@ -1,14 +1,23 @@
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
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');

View File

@ -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}}

View File

@ -1,14 +1,23 @@
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
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');

View File

@ -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}}

View File

@ -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));

View File

@ -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);
}
}

View File

@ -57,18 +57,27 @@ exports[`v2 should generate: ./test/generated/v2/core/OpenAPI.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
type Resolver<T> = () => Promise<T>;
type Headers = Record<string, string>;
interface Config {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
TOKEN: string | (() => Promise<string>);
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;
HEADERS?: Headers | Resolver<Headers>;
}
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<string, any>): FormData {
return formData;
}
async function getToken(): Promise<string> {
if (typeof OpenAPI.TOKEN === 'function') {
return OpenAPI.TOKEN();
type Resolver<T> = () => Promise<T>;
async function resolve<T>(resolver?: T | Resolver<T>): Promise<T | undefined> {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)();
}
return OpenAPI.TOKEN;
return resolver;
}
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
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<T> = () => Promise<T>;
type Headers = Record<string, string>;
interface Config {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
TOKEN: string | (() => Promise<string>);
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;
HEADERS?: Headers | Resolver<Headers>;
}
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<string, any>): FormData {
return formData;
}
async function getToken(): Promise<string> {
if (typeof OpenAPI.TOKEN === 'function') {
return OpenAPI.TOKEN();
type Resolver<T> = () => Promise<T>;
async function resolve<T>(resolver?: T | Resolver<T>): Promise<T | undefined> {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)();
}
return OpenAPI.TOKEN;
return resolver;
}
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
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');

View File

@ -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;

View File

@ -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;

View File

@ -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({

View File

@ -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;