- Working on better File type handeling

This commit is contained in:
Ferdi Koomen 2020-09-27 18:38:58 +02:00
parent 9ff09b6786
commit 43e382f115
28 changed files with 146 additions and 61 deletions

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.5.0-alpha",
"version": "0.5.0-beta",
"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

@ -1,5 +1,5 @@
export enum PrimaryType {
FILE = 'File',
FILE = 'any', // File or Buffer?
OBJECT = 'any',
ARRAY = 'any',
BOOLEAN = 'boolean',

View File

@ -2,7 +2,7 @@ import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('File');
expect(getMappedType('File')).toEqual('any');
expect(getMappedType('String')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');

View File

@ -1,5 +1,5 @@
export enum PrimaryType {
FILE = 'File',
FILE = 'any', // File or Buffer?
OBJECT = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',

View File

@ -2,7 +2,7 @@ import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('File');
expect(getMappedType('File')).toEqual('any');
expect(getMappedType('String')).toEqual('string');
expect(getMappedType('date')).toEqual('string');
expect(getMappedType('date-time')).toEqual('string');

View File

@ -9,11 +9,9 @@ function getHeaders(options: ApiRequestOptions): Headers {
}
if (options.body) {
if (options.body instanceof Blob) {
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else if (typeof options.body === 'string') {
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');

View File

@ -3,9 +3,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
return getFormData(options.formData);
}
if (options.body) {
if (options.body instanceof Blob) {
return options.body;
} else if (typeof options.body === 'string') {
if (isString(options.body) || isBlob(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);

View File

@ -1,7 +1,7 @@
function getResponseHeader(response: Response, responseHeader?: string): string | null {
if (responseHeader) {
const content = response.headers.get(responseHeader);
if (typeof content === 'string') {
if (isString(content)) {
return content;
}
}

View File

@ -8,6 +8,12 @@ import { OpenAPI } from './OpenAPI';
{{>functions/isDefined}}
{{>functions/isString}}
{{>functions/isBlob}}
{{>functions/getQueryString}}
@ -17,7 +23,7 @@ import { OpenAPI } from './OpenAPI';
{{>functions/getFormData}}
{{>functions/getHeaders}}
{{>fetch/getHeaders}}
{{>fetch/getRequestBody}}

View File

@ -0,0 +1,6 @@
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;
}

View File

@ -0,0 +1,3 @@
function isBlob(value: any): value is Blob {
return value instanceof Blob;
}

View File

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

View File

@ -0,0 +1,21 @@
function getHeaders(options: ApiRequestOptions): Headers {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
}
if (options.body) {
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;
}

View File

@ -3,9 +3,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
return getFormData(options.formData);
}
if (options.body) {
if (options.body instanceof ArrayBuffer) {
return options.body;
} else if (typeof options.body === 'string') {
if (isString(options.body) || isBinary(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);

View File

@ -1,7 +1,7 @@
function getResponseHeader(response: Response, responseHeader?: string): string | null {
if (responseHeader) {
const content = response.headers.get(responseHeader);
if (typeof content === 'string') {
if (isString(content)) {
return content;
}
}

View File

@ -1,15 +1,23 @@
{{>header}}
import * as 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 { OpenAPI } from './OpenAPI';
import fetch, { Headers, RequestInit, Response, BodyInit } from 'node-fetch';
import * as FormData from 'form-data';
{{>functions/isDefined}}
{{>functions/isString}}
{{>functions/isBinary}}
{{>functions/getQueryString}}
@ -19,7 +27,7 @@ import * as FormData from 'form-data';
{{>functions/getFormData}}
{{>functions/getHeaders}}
{{>node/getHeaders}}
{{>node/getRequestBody}}

View File

@ -0,0 +1,21 @@
function getHeaders(options: ApiRequestOptions): Headers {
const headers = new Headers({
Accept: 'application/json',
...options.headers,
});
if (isDefined(OpenAPI.TOKEN) && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
}
if (options.body) {
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;
}

View File

@ -3,9 +3,7 @@ function getRequestBody(options: ApiRequestOptions): any {
return getFormData(options.formData);
}
if (options.body) {
if (options.body instanceof Blob) {
return options.body;
} else if (typeof options.body === 'string') {
if (isString(options.body) || isBlob(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);

View File

@ -1,7 +1,7 @@
function getResponseHeader(xhr: XMLHttpRequest, responseHeader?: string): string | null {
if (responseHeader) {
const content = xhr.getResponseHeader(responseHeader);
if (typeof content === 'string') {
if (isString(content)) {
return content;
}
}

View File

@ -8,6 +8,12 @@ import { OpenAPI } from './OpenAPI';
{{>functions/isDefined}}
{{>functions/isString}}
{{>functions/isBlob}}
{{>functions/isSuccess}}
@ -20,7 +26,7 @@ import { OpenAPI } from './OpenAPI';
{{>functions/getFormData}}
{{>functions/getHeaders}}
{{>fetch/getHeaders}}
{{>xhr/getRequestBody}}

View File

@ -3,6 +3,7 @@ import * as Handlebars from 'handlebars/runtime';
import templateCoreApiError from '../templates/core/ApiError.hbs';
import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs';
import templateCoreApiResult from '../templates/core/ApiResult.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';
import fetchGetResponseHeader from '../templates/core/fetch/getResponseHeader.hbs';
@ -10,11 +11,14 @@ import fetchRequest from '../templates/core/fetch/request.hbs';
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 functionGetHeaders from '../templates/core/functions/getHeaders.hbs';
import functionGetQueryString from '../templates/core/functions/getQueryString.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 functionIsSuccess from '../templates/core/functions/isSuccess.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';
import nodeGetResponseHeader from '../templates/core/node/getResponseHeader.hbs';
@ -22,6 +26,7 @@ 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';
@ -124,13 +129,16 @@ 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/getHeaders', Handlebars.template(functionGetHeaders));
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/isSuccess', Handlebars.template(functionIsSuccess));
// Specific files for the fetch client implementation
Handlebars.registerPartial('fetch/getHeaders', Handlebars.template(fetchGetHeaders));
Handlebars.registerPartial('fetch/getRequestBody', Handlebars.template(fetchGetRequestBody));
Handlebars.registerPartial('fetch/getResponseBody', Handlebars.template(fetchGetResponseBody));
Handlebars.registerPartial('fetch/getResponseHeader', Handlebars.template(fetchGetResponseHeader));
@ -138,6 +146,7 @@ export function registerHandlebarTemplates(): Templates {
Handlebars.registerPartial('fetch/request', Handlebars.template(fetchRequest));
// Specific files for the xhr client implementation
Handlebars.registerPartial('xhr/getHeaders', Handlebars.template(xhrGetHeaders));
Handlebars.registerPartial('xhr/getRequestBody', Handlebars.template(xhrGetRequestBody));
Handlebars.registerPartial('xhr/getResponseBody', Handlebars.template(xhrGetResponseBody));
Handlebars.registerPartial('xhr/getResponseHeader', Handlebars.template(xhrGetResponseHeader));
@ -145,6 +154,7 @@ export function registerHandlebarTemplates(): Templates {
Handlebars.registerPartial('xhr/request', Handlebars.template(xhrRequest));
// Specific files for the node client implementation
Handlebars.registerPartial('node/getHeaders', Handlebars.template(nodeGetHeaders));
Handlebars.registerPartial('node/getRequestBody', Handlebars.template(nodeGetRequestBody));
Handlebars.registerPartial('node/getResponseBody', Handlebars.template(nodeGetResponseBody));
Handlebars.registerPartial('node/getResponseHeader', Handlebars.template(nodeGetResponseHeader));

View File

@ -85,6 +85,14 @@ function isDefined<T>(value: T | null | undefined): value is Exclude<T, null | u
return value !== undefined && value !== null;
}
function isString(value: any): value is string {
return typeof value === 'string';
}
function isBlob(value: any): value is Blob {
return value instanceof Blob;
}
function getQueryString(params: Record<string, any>): string {
const qs: string[] = [];
Object.keys(params).forEach(key => {
@ -137,11 +145,9 @@ function getHeaders(options: ApiRequestOptions): Headers {
}
if (options.body) {
if (options.body instanceof Blob) {
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else if (typeof options.body === 'string') {
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');
@ -155,9 +161,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
return getFormData(options.formData);
}
if (options.body) {
if (options.body instanceof Blob) {
return options.body;
} else if (typeof options.body === 'string') {
if (isString(options.body) || isBlob(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);
@ -178,7 +182,7 @@ async function sendRequest(options: ApiRequestOptions, url: string): Promise<Res
function getResponseHeader(response: Response, responseHeader?: string): string | null {
if (responseHeader) {
const content = response.headers.get(responseHeader);
if (typeof content === 'string') {
if (isString(content)) {
return content;
}
}
@ -598,7 +602,7 @@ import type { ModelWithString } from './ModelWithString';
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
propWithFile?: Array<File>;
propWithFile?: Array<any>;
propWithNumber?: Array<number>;
}
"
@ -931,7 +935,7 @@ exports[`v2 should generate: ./test/generated/v2/models/SimpleFile.ts 1`] = `
/**
* This is a simple file
*/
export type SimpleFile = File;"
export type SimpleFile = any;"
`;
exports[`v2 should generate: ./test/generated/v2/models/SimpleInteger.ts 1`] = `
@ -1477,7 +1481,7 @@ exports[`v2 should generate: ./test/generated/v2/schemas/$SimpleFile.ts 1`] = `
/* tslint:disable */
/* eslint-disable */
export const $SimpleFile = {
type: 'File',
type: 'any',
};"
`;
@ -2114,6 +2118,14 @@ function isDefined<T>(value: T | null | undefined): value is Exclude<T, null | u
return value !== undefined && value !== null;
}
function isString(value: any): value is string {
return typeof value === 'string';
}
function isBlob(value: any): value is Blob {
return value instanceof Blob;
}
function getQueryString(params: Record<string, any>): string {
const qs: string[] = [];
Object.keys(params).forEach(key => {
@ -2166,11 +2178,9 @@ function getHeaders(options: ApiRequestOptions): Headers {
}
if (options.body) {
if (options.body instanceof Blob) {
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else if (typeof options.body === 'string') {
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');
@ -2184,9 +2194,7 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
return getFormData(options.formData);
}
if (options.body) {
if (options.body instanceof Blob) {
return options.body;
} else if (typeof options.body === 'string') {
if (isString(options.body) || isBlob(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);
@ -2207,7 +2215,7 @@ async function sendRequest(options: ApiRequestOptions, url: string): Promise<Res
function getResponseHeader(response: Response, responseHeader?: string): string | null {
if (responseHeader) {
const content = response.headers.get(responseHeader);
if (typeof content === 'string') {
if (isString(content)) {
return content;
}
}
@ -2652,7 +2660,7 @@ import type { ModelWithString } from './ModelWithString';
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
propWithFile?: Array<File>;
propWithFile?: Array<any>;
propWithNumber?: Array<number>;
}
"
@ -3004,7 +3012,7 @@ exports[`v3 should generate: ./test/generated/v3/models/SimpleFile.ts 1`] = `
/**
* This is a simple file
*/
export type SimpleFile = File;"
export type SimpleFile = any;"
`;
exports[`v3 should generate: ./test/generated/v3/models/SimpleInteger.ts 1`] = `
@ -3584,7 +3592,7 @@ exports[`v3 should generate: ./test/generated/v3/schemas/$SimpleFile.ts 1`] = `
/* tslint:disable */
/* eslint-disable */
export const $SimpleFile = {
type: 'File',
type: 'any',
};"
`;
@ -4293,7 +4301,7 @@ export class UploadService {
* @throws ApiError
*/
public static async uploadFile(
file: File,
file: any,
): Promise<boolean> {
const result = await __request({
method: 'POST',

View File

@ -4,13 +4,14 @@ const ts = require('typescript');
const path = require('path');
const os = require('os');
function compile(dir) {
function compile(dir, isBrowser) {
const baseDir = `./test/e2e/generated/${dir}/`;
const tsconfig = {
compilerOptions: {
target: 'es6',
module: 'es6',
moduleResolution: 'node',
lib: isBrowser ? ['es6', 'dom'] : ['es6']
},
include: ['./index.ts'],
};

View File

@ -11,7 +11,7 @@ describe('v2.fetch', () => {
beforeAll(async () => {
await generate('v2/fetch', 'v2', 'fetch');
await copy('v2/fetch');
compile('v2/fetch');
compile('v2/fetch', true);
await server.start('v2/fetch');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v2.xhr', () => {
beforeAll(async () => {
await generate('v2/xhr', 'v2', 'xhr');
await copy('v2/xhr');
compile('v2/xhr');
compile('v2/xhr', true);
await server.start('v2/xhr');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v3.fetch', () => {
beforeAll(async () => {
await generate('v3/fetch', 'v3', 'fetch');
await copy('v3/fetch');
compile('v3/fetch');
compile('v3/fetch', true);
await server.start('v3/fetch');
await browser.start();
}, 30000);

View File

@ -11,7 +11,7 @@ describe('v3.xhr', () => {
beforeAll(async () => {
await generate('v3/xhr', 'v3', 'xhr');
await copy('v3/xhr');
compile('v3/xhr');
compile('v3/xhr', true);
await server.start('v3/xhr');
await browser.start();
}, 30000);

View File

@ -1,10 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist",
"target": "es2017",
"target": "es6",
"module": "es6",
"moduleResolution": "node",
"lib": ["es2017", "dom"],
"lib": ["es6"],
"types": ["jest", "node"],
"declaration": false,
"declarationMap": false,