Updated PR with XHR support, added E2E tests and added documentation

This commit is contained in:
Ferdi Koomen 2020-10-13 11:02:54 +02:00
parent 673dee075b
commit a5a8c0ba47
22 changed files with 170 additions and 40 deletions

View File

@ -339,6 +339,20 @@ import { OpenAPI } from './generated';
OpenAPI.TOKEN = 'some-bearer-token';
```
Alternatively, we also support an async method that provides the token for each request.
You can simply assign this method to the same `TOKEN `property in the global OpenAPI object.
```typescript
import { OpenAPI } from './generated';
const getToken = async () => {
// Some code that requests a token...
return 'SOME_TOKEN';
}
OpenAPI.TOKEN = getToken;
```
### Compare to other generators
Depending on which swagger generator you use, you will see different output.

View File

@ -1,6 +1,6 @@
{
"name": "@beatgig/openapi-typescript-codegen",
"version": "0.5.0-async-token.2",
"name": "openapi-typescript-codegen",
"version": "0.5.1",
"description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",

View File

@ -3,9 +3,8 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
Accept: 'application/json',
...options.headers,
});
const token = typeof OpenAPI.TOKEN === 'function' ? await OpenAPI.TOKEN() : OpenAPI.TOKEN;
const token = await getToken();
if (isDefined(token) && token !== '') {
headers.append('Authorization', `Bearer ${token}`);
}

View File

@ -23,6 +23,9 @@ import { OpenAPI } from './OpenAPI';
{{>functions/getFormData}}
{{>functions/getToken}}
{{>fetch/getHeaders}}

View File

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

View File

@ -1,11 +1,12 @@
function getHeaders(options: ApiRequestOptions): Headers {
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
const token = await getToken();
if (isDefined(token) && token !== '') {
headers.append('Authorization', `Bearer ${token}`);
}
if (options.body) {

View File

@ -27,6 +27,9 @@ import { OpenAPI } from './OpenAPI';
{{>functions/getFormData}}
{{>functions/getToken}}
{{>node/getHeaders}}

View File

@ -1,7 +1,7 @@
async function sendRequest(options: ApiRequestOptions, url: string): Promise<Response> {
const request: RequestInit = {
method: options.method,
headers: getHeaders(options),
headers: await getHeaders(options),
body: getRequestBody(options),
};
return await fetch(url, request);

View File

@ -1,11 +1,12 @@
function getHeaders(options: ApiRequestOptions): Headers {
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
const token = await getToken();
if (isDefined(token) && token !== '') {
headers.append('Authorization', `Bearer ${token}`);
}
if (options.body) {

View File

@ -26,6 +26,9 @@ import { OpenAPI } from './OpenAPI';
{{>functions/getFormData}}
{{>functions/getToken}}
{{>fetch/getHeaders}}

View File

@ -1,21 +1,21 @@
function sendRequest(options: ApiRequestOptions, url: string): Promise<XMLHttpRequest> {
async function sendRequest(options: ApiRequestOptions, url: string): Promise<XMLHttpRequest> {
const xhr = new XMLHttpRequest();
xhr.open(options.method, url, true);
xhr.withCredentials = OpenAPI.WITH_CREDENTIALS;
const headers = await getHeaders(options);
headers.forEach((value: string, key: string) => {
xhr.setRequestHeader(key, value);
});
return new Promise<XMLHttpRequest>((resolve, reject) => {
try {
const xhr = new XMLHttpRequest();
xhr.open(options.method, url, true);
xhr.withCredentials = OpenAPI.WITH_CREDENTIALS;
const headers = getHeaders(options);
headers.forEach((value: string, key: string) => {
xhr.setRequestHeader(key, value);
});
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
resolve(xhr);
}
};
xhr.send(getRequestBody(options));
} catch (error) {
reject(error);

View File

@ -12,6 +12,7 @@ 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';
@ -131,6 +132,7 @@ 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));

View File

@ -61,7 +61,7 @@ interface Config {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
TOKEN: string;
TOKEN: string | (() => Promise<string>);
}
export const OpenAPI: Config = {
@ -134,14 +134,22 @@ function getFormData(params: Record<string, any>): FormData {
return formData;
}
function getHeaders(options: ApiRequestOptions): Headers {
async function getToken(): Promise<string> {
if (typeof OpenAPI.TOKEN === 'function') {
return OpenAPI.TOKEN();
}
return OpenAPI.TOKEN;
}
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', \`Bearer \${OpenAPI.TOKEN}\`);
const token = await getToken();
if (isDefined(token) && token !== '') {
headers.append('Authorization', \`Bearer \${token}\`);
}
if (options.body) {
@ -173,7 +181,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
async function sendRequest(options: ApiRequestOptions, url: string): Promise<Response> {
const request: RequestInit = {
method: options.method,
headers: getHeaders(options),
headers: await getHeaders(options),
body: getRequestBody(options),
};
return await fetch(url, request);
@ -2096,7 +2104,7 @@ interface Config {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
TOKEN: string;
TOKEN: string | (() => Promise<string>);
}
export const OpenAPI: Config = {
@ -2169,14 +2177,22 @@ function getFormData(params: Record<string, any>): FormData {
return formData;
}
function getHeaders(options: ApiRequestOptions): Headers {
async function getToken(): Promise<string> {
if (typeof OpenAPI.TOKEN === 'function') {
return OpenAPI.TOKEN();
}
return OpenAPI.TOKEN;
}
async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', \`Bearer \${OpenAPI.TOKEN}\`);
const token = await getToken();
if (isDefined(token) && token !== '') {
headers.append('Authorization', \`Bearer \${token}\`);
}
if (options.body) {
@ -2208,7 +2224,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
async function sendRequest(options: ApiRequestOptions, url: string): Promise<Response> {
const request: RequestInit = {
method: options.method,
headers: getHeaders(options),
headers: await getHeaders(options),
body: getRequestBody(options),
};
return await fetch(url, request);

View File

@ -27,8 +27,8 @@ async function stop() {
await browser.close();
}
async function evaluate(fn) {
return await page.evaluate(fn);
async function evaluate(fn, ...args) {
return await page.evaluate(fn, args);
}
module.exports = {

View File

@ -21,6 +21,18 @@ describe('v2.fetch', () => {
await browser.stop();
});
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const result = await browser.evaluate(async () => {
return await window.api.ComplexService.complexTypes({

View File

@ -21,6 +21,18 @@ describe('v2.fetch', () => {
await browser.stop();
});
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const result = await browser.evaluate(async () => {
return await window.api.ComplexService.complexTypes({

View File

@ -17,8 +17,17 @@ describe('v2.node', () => {
await server.stop();
});
it('requests token', async () => {
const { OpenAPI, SimpleService } = require('./generated/v2/node/index.js');
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN')
OpenAPI.TOKEN = tokenRequest;
const result = await SimpleService.getCallWithoutParametersAndResponse();
expect(tokenRequest.mock.calls.length).toBe(1);
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const {ComplexService} = require('./generated/v2/node/index.js');
const { ComplexService } = require('./generated/v2/node/index.js');
const result = await ComplexService.complexTypes({
first: {
second: {

View File

@ -21,6 +21,18 @@ describe('v2.xhr', () => {
await browser.stop();
});
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const result = await browser.evaluate(async () => {
return await window.api.ComplexService.complexTypes({

View File

@ -21,8 +21,16 @@ describe('v3.fetch', () => {
await browser.stop();
});
it('runs', async () => {
expect(true).toBeTruthy();
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {

View File

@ -21,8 +21,16 @@ describe('v3.fetch', () => {
await browser.stop();
});
it('runs', async () => {
expect(true).toBeTruthy();
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {

View File

@ -17,8 +17,17 @@ describe('v3.node', () => {
await server.stop();
});
it('requests token', async () => {
const { OpenAPI, SimpleService } = require('./generated/v2/node/index.js');
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN')
OpenAPI.TOKEN = tokenRequest;
const result = await SimpleService.getCallWithoutParametersAndResponse();
expect(tokenRequest.mock.calls.length).toBe(1);
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const {ComplexService} = require('./generated/v3/node/index.js');
const { ComplexService } = require('./generated/v3/node/index.js');
const result = await ComplexService.complexTypes({
first: {
second: {

View File

@ -21,6 +21,18 @@ describe('v3.xhr', () => {
await browser.stop();
});
it('requests token', async () => {
const result = await browser.evaluate(async () => {
window.api.OpenAPI.TOKEN = new Promise(resolve => {
setTimeout(() => {
resolve('MY_TOKEN');
}, 500);
});
return await window.api.SimpleService.getCallWithoutParametersAndResponse();
});
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
});
it('complexService', async () => {
const result = await browser.evaluate(async () => {
return await window.api.ComplexService.complexTypes({