Modified http lib, added config & middleware definition to ts-fetch

This commit is contained in:
Tino Fuhrmann 2018-08-09 21:02:43 +02:00
parent f90e1b59f5
commit 3e745b71f2
15 changed files with 332 additions and 25 deletions

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1 @@
readme

View File

@ -0,0 +1,38 @@
import {HttpLibrary} from './http/http';
import {Middleware} from './middleware';
export interface ConfigurationParameters {
basePath?: string; // override base path
httpApi?: HttpLibrary; // override for fetch implementation
middleware?: Middleware[]; // middleware to apply before/after fetch requests
username?: string; // parameter for basic security
password?: string; // parameter for basic security
apiKey?: string | ((name: string) => string); // parameter for apiKey security
accessToken?: string | ((name: string, scopes?: string[]) => string); // parameter for oauth2 security
}
export class Configuration {
basePath: string;
httpApi: HttpLibrary;
middleware: Middleware[];
username?: string;
password?: string;
apiKey?: (name: string) => string;
accessToken?: (name: string, scopes?: string[]) => string;
constructor(conf: ConfigurationParameters = {}) {
this.basePath = conf.basePath !== undefined ? conf.basePath : BASE_PATH;
this.fetchApi = conf.fetchApi || window.fetch.bind(window);
this.middleware = conf.middleware || [];
this.username = conf.username;
this.password = conf.password;
const { apiKey, accessToken } = conf;
if (apiKey) {
this.apiKey = typeof apiKey === 'function' ? apiKey : () => apiKey;
}
if (accessToken) {
this.accessToken = typeof accessToken === 'function' ? accessToken : () => accessToken;
}
}
}

View File

@ -0,0 +1,51 @@
export enum HttpMethod {
GET = "GET",
HEAD = "HEAD",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
CONNECT = "CONNECT",
OPTIONS = "OPTIONS",
TRACE = "TRACE",
PATCH = "PATCH"
}
export interface FormEntry {
contentType: string;
value: string | Blob;
}
export type FormData = { [key: string]: FormEntry };
export class RequestContext {
public headers: { [key: string]: string } = {};
public body: string | FormData = "";
public constructor(public url: string, public httpMethod: HttpMethod) {
}
public addCookie(name: string, value: string): void {
if (!this.headers["Cookie"]) {
this.headers["Cookie"] = "";
}
this.headers["Cookie"] += name + "=" + value + "; ";
}
public setHeader(key: string, value: string): void {
this.headers[key] = value;
}
}
export class ResponseContext {
public constructor(public httpStatusCode: number,
public headers: { [key: string]: string }, public body: string) {
}
}
export interface HttpLibrary {
send(request: RequestContext): Promise<ResponseContext>;
}

View File

@ -0,0 +1,38 @@
import {HttpLibrary, RequestContext, ResponseContext} from './http';
import * as e6p from 'es6-promise'
e6p.polyfill();
import 'isomorphic-fetch';
export class IsomorphicFetchHttpLibrary implements HttpLibrary {
public send(request: RequestContext): Promise<ResponseContext> {
let method = request.httpMethod.toString();
let body: string | FormData = "";
if (typeof request.body === "string") {
body = request.body;
} else {
body = new FormData();
for (const key in request.body) {
body.append(key, request.body[key].value);
}
}
return fetch(request.url, {
method: method,
body: body,
headers: request.headers,
credentials: "same-origin"
}).then((resp) => {
// hack
let headers = (resp.headers as any)._headers;
for (let key in headers) {
headers[key] = (headers[key] as Array<string>).join("; ");
}
return resp.text().then((body) => {
return new ResponseContext(resp.status, headers, body)
});
});
}
}

View File

@ -0,0 +1,6 @@
import {RequestContext, ResponseContext} from './http/http';
export interface Middleware {
pre?(context: RequestContext): Promise<RequestContext>;
post?(context: ResponseContext): Promise<ResponseContext>;
}

View File

@ -0,0 +1,28 @@
{
"name": "",
"version": "",
"description": "OpenAPI client for ",
"author": "OpenAPI-Generator Contributors",
"keywords": [
"fetch",
"typescript",
"openapi-client",
"openapi-generator",
""
],
"license": "Unlicense",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"scripts" : {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {
"es6-promise": "^4.2.4",
"isomorphic-fetch": "^2.2.1",
"@types/isomorphic-fetch": "0.0.34"
},
"devDependencies": {
"typescript": "^2.9.2"
}
}

View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"strict": true,
/* Basic Options */
"target": "es5",
"module": "commonjs",
"declaration": true,
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
"removeComments": true,
"sourceMap": true,
"outDir": "./dist",
"noLib": false,
"declaration": true,
"lib": [ "es6", "dom" ]
},
"exclude": [
"node_modules"
],
"filesGlob": [
"./**/*.ts",
]
}

View File

@ -116,8 +116,14 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("package.mustache", "", "package.json"));
supportingFiles.add(new SupportingFile("tsconfig.mustache", "", "tsconfig.json"));
// http
supportingFiles.add(new SupportingFile("http" + File.separator + "http.mustache", "http", "http.ts"));
supportingFiles.add(new SupportingFile("http" + File.separator + "isomorphic-fetch.mustache", "http", "isomorphic-fetch.ts"));
supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.ts"));
supportingFiles.add(new SupportingFile("middleware.mustache", "", "middleware.ts"));
}

View File

@ -0,0 +1,41 @@
import {HttpLibrary} from './http/http';
import {Middleware} from './middleware';
import {IsomorphicFetchHttpLibrary} from "./http/isomorphic-fetch";
export const BASE_PATH = "{{{basePath}}}".replace(/\/+$/, "");
export interface ConfigurationParameters {
basePath?: string; // override base path
httpApi?: HttpLibrary; // override for fetch implementation
middleware?: Middleware[]; // middleware to apply before/after fetch requests
username?: string; // parameter for basic security
password?: string; // parameter for basic security
apiKey?: string | ((name: string) => string); // parameter for apiKey security
accessToken?: string | ((name: string, scopes?: string[]) => string); // parameter for oauth2 security
}
export class Configuration {
basePath: string;
httpApi: HttpLibrary;
middleware: Middleware[];
username?: string;
password?: string;
apiKey?: (name: string) => string;
accessToken?: (name: string, scopes?: string[]) => string;
constructor(conf: ConfigurationParameters = {}) {
this.basePath = conf.basePath !== undefined ? conf.basePath : BASE_PATH;
this.httpApi = conf.httpApi || new IsomorphicFetchHttpLibrary(); // TODO: replace with window.fetch?
this.middleware = conf.middleware || [];
this.username = conf.username;
this.password = conf.password;
const { apiKey, accessToken } = conf;
if (apiKey) {
this.apiKey = typeof apiKey === 'function' ? apiKey : () => apiKey;
}
if (accessToken) {
this.accessToken = typeof accessToken === 'function' ? accessToken : () => accessToken;
}
}
}

View File

@ -1,3 +1,6 @@
// TODO: evaluate if we can easily get rid of this library
import * as FormData from "form-data";
export enum HttpMethod {
GET = "GET",
HEAD = "HEAD",
@ -11,21 +14,58 @@ export enum HttpMethod {
}
export interface FormEntry {
contentType: string;
contentDisposition: string;
value: string | Blob;
}
export type FormData = { [key: string]: FormEntry };
export class HttpException extends Error {
public constructor(msg: string) {
super(msg);
}
}
export class Request {
public headers: { [key: string]: string } = {};
public body: string | FormData = "";
export class RequestContext {
private headers: { [key: string]: string } = {};
private body: string | FormData = "";
public constructor(public url: string, public httpMethod: HttpMethod) {
public constructor(private url: string, private httpMethod: HttpMethod) {
}
public getUrl(): string {
return this.url;
}
public setUrl(url: string) {
this.url = url;
}
public setBody(body: string | FormData) {
// HTTP-Spec 1.1 Section 4.3
if (this.httpMethod === HttpMethod.GET) {
throw new HttpException("Body should not be included in GET-Requests!");
}
// TODO: other http methods
// post is fine either formData or string
this.body = body;
}
public getHttpMethod(): HttpMethod {
return this.httpMethod;
}
public getHeaders(): { [key: string]: string } {
return this.headers;
}
public getBody(): string | FormData {
return this.body;
}
public addCookie(name: string, value: string): void {
if (!this.headers["Cookie"]) {
this.headers["Cookie"] = "";
@ -38,7 +78,7 @@ export class Request {
}
}
export class Response {
export class ResponseContext {
public constructor(public httpStatusCode: number,
public headers: { [key: string]: string }, public body: string) {
@ -47,5 +87,5 @@ export class Response {
}
export interface HttpLibrary {
send(request: Request): Promise<Response>;
send(request: RequestContext): Promise<ResponseContext>;
}

View File

@ -1,26 +1,18 @@
import {HttpLibrary, Request, Response} from './http';
import {HttpLibrary, RequestContext, ResponseContext} from './http';
import * as e6p from 'es6-promise'
e6p.polyfill();
import 'isomorphic-fetch';
export class IsomorphicFetchHttpLibrary implements HttpLibrary {
public send(request: Request): Promise<Response> {
let method = request.httpMethod.toString();
let body: string | FormData = "";
if (typeof request.body === "string") {
body = request.body;
} else {
body = new FormData();
for (const key in request.body) {
body.append(key, request.body[key].value);
}
}
public send(request: RequestContext): Promise<ResponseContext> {
let method = request.getHttpMethod().toString();
let body = request.getBody();
return fetch(request.url, {
return fetch(request.getUrl(), {
method: method,
body: body,
headers: request.headers,
body: body as any,
headers: request.getHeaders(),
credentials: "same-origin"
}).then((resp) => {
// hack
@ -30,7 +22,7 @@ export class IsomorphicFetchHttpLibrary implements HttpLibrary {
}
return resp.text().then((body) => {
return new Response(resp.status, headers, body)
return new ResponseContext(resp.status, headers, body)
});
});

View File

@ -0,0 +1,6 @@
import {RequestContext, ResponseContext} from './http/http';
export interface Middleware {
pre?(context: RequestContext): Promise<RequestContext>;
post?(context: ResponseContext): Promise<ResponseContext>;
}

View File

@ -21,9 +21,16 @@
"es6-promise": "^4.2.4",
"isomorphic-fetch": "^2.2.1",
"@types/isomorphic-fetch": "0.0.34"
"form-data": "^2.3.2",
"@types/form-data": "^2.2.1",
},
"devDependencies": {
"typescript": "^2.9.2"
"ts-node": "^7.0.0",
"typescript": "^2.9.2",
"@types/chai": "^4.1.4",
"@types/mocha": "^5.2.5",
"chai": "^4.1.2",
"mocha": "^5.2.0"
}{{#npmRepository}},{{/npmRepository}}
{{#npmRepository}}
"publishConfig":{