Skip to content

Commit 1d0a324

Browse files
TiFubodograumannmacjohnny
authored
[TypeScript] Rewritten TypeScript client generator supporting fetch & jquery (#6341)
* Added http module draft * Added generic enum * Modified http lib, added config & middleware definition to ts-fetch * Added model generation with imports * Added auth module * Added servers * Added sample for typescript client * WIP: Models & API * Updated auth * WIP: api modeling * Implemented RequestFactory and Processor completely * Implemented fetch client * Ignore dist folder in typescript client sample * Added middleware to fetch * Restructured TypeScript generator * Reverted: http library.send returns string again * Removed TODOs * Added pom.xml files to TypeScript PetStore client samples * Removed tabs from TypeScriptClientCodegen * Added ts client codegen to root pom.xml and travis * Added server variable configuration to ts-refactor * [TS-Refactor] Added tests for Object Serializer * Added simple test for PetApi * Fixed ObjectSerializer test * Added handling for different http status codes and test for deletePet * Removed tabs in TypeScriptClientCodegen * Removed tabs in DefaultCodegen * Additional tests for pet store api * Fixed file uploads * Made api call configuration separately settable * Use string union for enums * Remove tab * Restructured module layout * Use observables internally * Added promise based middleware * Made discriminator and attributeTypeMap readonly * Configure discriminator correctly * Set discriminator value automatically * Fixed date-time and date handling * Added comments & license info * Added comments * Ignore openapi-generator-cli/bin * Removed accidentally created generated code * Fixed compilation issues in TypeScriptClientCodegen * Added typescript to docs/generators * Updated docs * Added gitignore and git_push * Added jquery library * Added pom.xmls, fixed packagejsons and hopefully webppack * Removed tabs in TypeScriptClientCodegen * Fixed a couple issues with pom.xml * Ensured up to date * Fixed missing fetch definition in TS default tests * Updated typescript docs * Refactor typescript merge master (#4319) Merge master into ts-refactor * Typescript refactor: stub rxjs (#4424) * Remove unused supportsES6 field from codegen * Add a new switch for RXJS * Remove redundant npm dependency on rxjs4 types * Fix return type of PromiseMiddleware methods * Install webpack dependency to run jquery tests * Update form-data to 2.5 which includes typings * Add missing dependency on node typings * Fix test artifact name typo * Stub rxjs when it is not explicitly enabled * Typescript refactor: Platform select for browser and node (#4500) * Use string form of filename parameter This works for the form-data library and is also compatible with the browser FormData object. * Add new option to select platform node or browser When no platform is selected, a default is chosen by the framework option and likewise the file data type option is implied by the platform. * Remove redundant import of node dns module * Only use form-data library for node platform * Generate npm package from npmName option * Use method convertPropertyToBooleanAndWriteBack * Generate typescript samples with ensure-up-to-date * Removed tab from DefaultCodegen * Readded missing change * Mark typescript client codegen as experimental * Removed whitespace * [TS-Refactor] Top-level exports for fetch & jquery (#6138) * Added top-level exports * Updated generator README * Updated typescript generator docs * Allow browsers File type for files (#5521) * Allow passing file parameters as File objects * Add test for jquery upload * Use HttpFile object for node platform * Regenerate samples This is by far the most common use case. A `File` object already contains the name attribute. This commit allows that information to be used directly. When sending a `Blob`, in most browsers the `File` constructor can be used to assign a file name. In all other browsers the alternative is ```typescript Object.assign(data, { name: "foobar.txt" }); ``` That is why we explicitely pass the name as third parameter to `FormData.append`. This `Object.assign` method also works for `Buffer` objects in node. If one really does not want to touch the data object in the browser it is possible to define another reference to the data with ```typescript new Blob([data], { type: data.type }) ``` or in node via ```typescript Buffer.from(data) ``` * [TS-Refactor] Added options for npm version, repository, name and updated readme (#6139) * Added options for npm version, repository, name and updated readme * Removed `this` where not required * Updated typescript docs * Typescript refactor fixes (#6027) Fixes a handful of issues identified in OpenAPITools/openapi-generator#802 (comment) List of changes * Clean: Remove redundant cliOption definition * Remove redundant directory structure in templates If we need to have different index.ts files for the different frameworks, we can mostly do that in the one mustache file. In the cases where that is not possible, we can still add a new override file later. * Use File.separator consistently * Only export selected api type * Simplify promise polyfill import The behaviour should be the same, according to the es6-promise docs. Previously tsc would report the error: > error TS2307: Cannot find module 'es6-promise'. * Import HttpFile in all models * Export server configurations * Use undefined as default body value The empty string is not interpreted as "no body" by the browser fetch api and thus leads to an exception during get requests * Improve codestyle: prefer guards to nesting * Remove verbose debug output This should not be commited, because every developer has very different requirements what debug information he needs to see. * Fix: Use cleaned model names for imports * Fix: do not call toString on undefined * Fix typo in doc comment * Introduce RequestBody type and remove method check * Support media types other than json (#6177) List of changes: * Add */* as fallback to accept header * Use more sophisticated media type selection * Handle object stringify in ObjectSerializer * Parse response with ObejctSerializer * Fix: Correctly extract response headers in browser * Create HttpFile objects from responses * Handle binary responses * Clean up dependencies and replace isomorphic-fetch Instead of isomorphic-fetch, which is unmaintained, we directly use node-fetch and whatwg-fetch polyfills. * Updated versions in ts-default/jquery and ts docs * Replaced isSuccessCode with is2xx * [TypeScript-Refactor] Use OAIv3 spec and fix bugs in JQuery Blob download (#6416) * Change to OAIv3 spec for TS-Refactor * Moved samples to oaiv3 folder * Updated package-lock * Update pom to use OAIv3 paths for Typescript-refactor * Renamed ts-refactor samples & tests in pom.xmls * Fixed compile issues in ts-refactor jquery http test * Fixed bugs in blob handling of jquery * [Typescript] Support http bearer authentication with token provider (#6425) * Add http bearer security * Update typescript to 3.9 * Fix: Use Authorization header for basic and bearer * Allow asynchronous tokenProvider in bearer auth * Add TS-Rewrite-Jquery tests node_modules to travis caching * Remove NoAuthentication * Added file to generate TS samples on Windows * Exclude btoa in browser * Regen samples * Remove outdated ToDo comments * Document and optimize `getReturnType` in TSClientCodegen * Added option to generate objects for operation function arguments * Upgrade typescript docs * Updated generators * Updated samples * Updated docs * Readded pom.xml * [Typescript] Support InversifyJS (#6489) * Add config option to enable InversifyJS * Add pascal case lambda for mustache * Generate a class for each auth method * Add service identifiers and service binder helper * Split Configuration into interface and factory This way we don't need to import the factory everywhere to do typechecking. * Define minimal interface for ServerConfiguration * Add annotations for inversify when enabled * Always expose list of server configurations * Add samples and defalt tests for useInversify * Simplify sample generation script * Fix: Add object_params arg description to help * Fix: Properly enable inversify with bool property * Build tests in pom instead of prepublish Otherwise running `npm install`, when the build failed was impossible. * Update dependencies for inversify tests * Test basic api service resolution * Remove Promise and Observable prefix from exports * Fix, RxJS: Import Observable in object params api * Add ioc service identifier for object param api * Add hint about unimpeded development * Simplify api service binder syntax * Remove default tests for inversify * Add wrapper for easy promise based http libraries This wrapper allows defining and injecting http libraries that do not need to know anything about observables, especially when useRxJS is not enabled. I will employ this in the tests for InversifyJS. Not sure if we should also use this wrapper internally. * Add named injects for remaining auth parameters * Directly inject promise services without RxJS * Add tests for api service binder * Add convenience method to bind all api services * Fix: Rename inversify test artifact * Run bin/utils/copy-to-website.sh * Restore changes to CONTRIBUTING.md from PR #6489 Co-authored-by: Bodo Graumann <mail@bodograumann.de> Co-authored-by: Esteban Gehring <esteban.gehring@bithost.ch>
0 parents  commit 1d0a324

32 files changed

+2181
-0
lines changed

.gitignore.mustache

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

README.mustache

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## {{npmName}}@{{npmVersion}}
2+
3+
This generator creates TypeScript/JavaScript client that utilizes {{framework}}.
4+
5+
### Building
6+
7+
To build and compile the typescript sources to javascript use:
8+
```
9+
npm install
10+
npm run build
11+
```
12+
13+
### Publishing
14+
15+
First build the package then run ```npm publish```
16+
17+
### Consuming
18+
19+
navigate to the folder of your consuming project and run one of the following commands.
20+
21+
_published:_
22+
23+
```
24+
npm install {{npmName}}@{{npmVersion}} --save
25+
```
26+
27+
_unPublished (not recommended):_
28+
29+
```
30+
npm install PATH_TO_GENERATED_PACKAGE --save

api/api.mustache

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// TODO: better import syntax?
2+
import { BaseAPIRequestFactory, RequiredError } from './baseapi';
3+
import {Configuration} from '../configuration';
4+
import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http';
5+
{{#platforms}}
6+
{{#node}}
7+
import * as FormData from "form-data";
8+
{{/node}}
9+
{{/platforms}}
10+
import {ObjectSerializer} from '../models/ObjectSerializer';
11+
import {ApiException} from './exception';
12+
import {isCodeInRange} from '../util';
13+
{{#useInversify}}
14+
import { injectable } from "inversify";
15+
{{/useInversify}}
16+
17+
{{#imports}}
18+
import { {{classname}} } from '..{{filename}}';
19+
{{/imports}}
20+
{{#operations}}
21+
22+
/**
23+
* {{#description}}{{{description}}}{{/description}}{{^description}}no description{{/description}}
24+
*/
25+
{{#useInversify}}
26+
@injectable()
27+
{{/useInversify}}
28+
export class {{classname}}RequestFactory extends BaseAPIRequestFactory {
29+
30+
{{#operation}}
31+
/**
32+
{{#notes}}
33+
* {{&notes}}
34+
{{/notes}}
35+
{{#summary}}
36+
* {{&summary}}
37+
{{/summary}}
38+
{{#allParams}}
39+
* @param {{paramName}} {{description}}
40+
{{/allParams}}
41+
*/
42+
public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<RequestContext> {
43+
let config = options || this.configuration;
44+
{{#allParams}}
45+
46+
{{#required}}
47+
// verify required parameter '{{paramName}}' is not null or undefined
48+
if ({{paramName}} === null || {{paramName}} === undefined) {
49+
throw new RequiredError('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.');
50+
}
51+
52+
{{/required}}
53+
{{/allParams}}
54+
55+
// Path Params
56+
const localVarPath = '{{{path}}}'{{#pathParams}}
57+
.replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}};
58+
59+
// Make Request Context
60+
const requestContext = config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}});
61+
requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8")
62+
63+
// Query Params
64+
{{#queryParams}}
65+
if ({{paramName}} !== undefined) {
66+
requestContext.setQueryParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"));
67+
}
68+
{{/queryParams}}
69+
70+
// Header Params
71+
{{#headerParams}}
72+
requestContext.setHeaderParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"));
73+
{{/headerParams}}
74+
75+
// Form Params
76+
{{#hasFormParams}}
77+
let localVarFormParams = new FormData();
78+
{{/hasFormParams}}
79+
80+
{{#formParams}}
81+
{{#isListContainer}}
82+
if ({{paramName}}) {
83+
{{#isCollectionFormatMulti}}
84+
{{paramName}}.forEach((element) => {
85+
localVarFormParams.append('{{baseName}}', element as any);
86+
})
87+
{{/isCollectionFormatMulti}}
88+
{{^isCollectionFormatMulti}}
89+
// TODO: replace .append with .set
90+
localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]));
91+
{{/isCollectionFormatMulti}}
92+
}
93+
{{/isListContainer}}
94+
{{^isListContainer}}
95+
if ({{paramName}} !== undefined) {
96+
// TODO: replace .append with .set
97+
{{^isFile}}
98+
localVarFormParams.append('{{baseName}}', {{paramName}} as any);
99+
{{/isFile}}
100+
{{#isFile}}
101+
{{#platforms}}
102+
{{#node}}
103+
localVarFormParams.append('{{baseName}}', {{paramName}}.data, {{paramName}}.name);
104+
{{/node}}
105+
{{#browser}}
106+
localVarFormParams.append('{{baseName}}', {{paramName}}, {{paramName}}.name);
107+
{{/browser}}
108+
{{/platforms}}
109+
{{/isFile}}
110+
}
111+
{{/isListContainer}}
112+
{{/formParams}}
113+
{{#hasFormParams}}
114+
requestContext.setBody(localVarFormParams);
115+
{{/hasFormParams}}
116+
117+
// Body Params
118+
{{#bodyParam}}
119+
const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}}
120+
"{{{mediaType}}}"{{#hasMore}},{{/hasMore}}
121+
{{/consumes}}]);
122+
requestContext.setHeaderParam("Content-Type", contentType);
123+
const serializedBody = ObjectSerializer.stringify(
124+
ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"),
125+
contentType
126+
);
127+
requestContext.setBody(serializedBody);
128+
{{/bodyParam}}
129+
130+
{{#hasAuthMethods}}
131+
let authMethod = null;
132+
{{/hasAuthMethods}}
133+
// Apply auth methods
134+
{{#authMethods}}
135+
authMethod = config.authMethods["{{name}}"]
136+
if (authMethod) {
137+
await authMethod.applySecurityAuthentication(requestContext);
138+
}
139+
{{/authMethods}}
140+
141+
return requestContext;
142+
}
143+
144+
{{/operation}}
145+
}
146+
{{/operations}}
147+
148+
149+
{{#operations}}
150+
151+
{{#useInversify}}
152+
@injectable()
153+
{{/useInversify}}
154+
export class {{classname}}ResponseProcessor {
155+
156+
{{#operation}}
157+
/**
158+
* Unwraps the actual response sent by the server from the response context and deserializes the response content
159+
* to the expected objects
160+
*
161+
* @params response Response returned by the server for a request to {{nickname}}
162+
* @throws ApiException if the response code was not in [200, 299]
163+
*/
164+
public async {{nickname}}(response: ResponseContext): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}> {
165+
const contentType = ObjectSerializer.normalizeMediaType(response.headers["content-type"]);
166+
{{#responses}}
167+
if (isCodeInRange("{{code}}", response.httpStatusCode)) {
168+
{{#dataType}}
169+
{{#isBinary}}
170+
const body: {{{dataType}}} = await response.getBodyAsFile() as any as {{{returnType}}};
171+
{{/isBinary}}
172+
{{^isBinary}}
173+
const body: {{{dataType}}} = ObjectSerializer.deserialize(
174+
ObjectSerializer.parse(await response.body.text(), contentType),
175+
"{{{dataType}}}", "{{returnFormat}}"
176+
) as {{{dataType}}};
177+
{{/isBinary}}
178+
{{#is2xx}}
179+
return body;
180+
{{/is2xx}}
181+
{{^is2xx}}
182+
throw new ApiException<{{{dataType}}}>({{code}}, body);
183+
{{/is2xx}}
184+
{{/dataType}}
185+
{{^dataType}}
186+
{{#is2xx}}
187+
return;
188+
{{/is2xx}}
189+
{{^is2xx}}
190+
throw new ApiException<string>(response.httpStatusCode, "{{message}}");
191+
{{/is2xx}}
192+
{{/dataType}}
193+
}
194+
{{/responses}}
195+
196+
// Work around for missing responses in specification, e.g. for petstore.yaml
197+
if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) {
198+
{{#returnType}}
199+
{{#isBinary}}
200+
const body: {{{returnType}}} = await response.getBodyAsFile() as any as {{{returnType}}};
201+
{{/isBinary}}
202+
{{^isBinary}}
203+
const body: {{{returnType}}} = ObjectSerializer.deserialize(
204+
ObjectSerializer.parse(await response.body.text(), contentType),
205+
"{{{returnType}}}", "{{returnFormat}}"
206+
) as {{{returnType}}};
207+
{{/isBinary}}
208+
return body;
209+
{{/returnType}}
210+
{{^returnType}}
211+
return;
212+
{{/returnType}}
213+
}
214+
215+
let body = response.body || "";
216+
throw new ApiException<string>(response.httpStatusCode, "Unknown API Status Code!\nBody: \"" + body + "\"");
217+
}
218+
219+
{{/operation}}
220+
}
221+
{{/operations}}

api/baseapi.mustache

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Configuration } from '../configuration'
2+
{{#useInversify}}
3+
import { injectable, inject } from "inversify";
4+
import { AbstractConfiguration } from "../services/configuration";
5+
{{/useInversify}}
6+
7+
/**
8+
*
9+
* @export
10+
*/
11+
export const COLLECTION_FORMATS = {
12+
csv: ",",
13+
ssv: " ",
14+
tsv: "\t",
15+
pipes: "|",
16+
};
17+
18+
19+
/**
20+
*
21+
* @export
22+
* @class BaseAPI
23+
*/
24+
{{#useInversify}}
25+
@injectable()
26+
{{/useInversify}}
27+
export class BaseAPIRequestFactory {
28+
29+
constructor({{#useInversify}}@inject(AbstractConfiguration) {{/useInversify}}protected configuration: Configuration) {
30+
}
31+
};
32+
33+
/**
34+
*
35+
* @export
36+
* @class RequiredError
37+
* @extends {Error}
38+
*/
39+
export class RequiredError extends Error {
40+
name: "RequiredError" = "RequiredError";
41+
constructor(public field: string, msg?: string) {
42+
super(msg);
43+
}
44+
}

api/exception.mustache

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Represents an error caused by an api call i.e. it has attributes for a HTTP status code
3+
* and the returned body object.
4+
*
5+
* Example
6+
* API returns a ErrorMessageObject whenever HTTP status code is not in [200, 299]
7+
* => ApiException(404, someErrorMessageObject)
8+
*
9+
*/
10+
export class ApiException<T> extends Error {
11+
public constructor(public code: number, public body: T) {
12+
super("HTTP-Code: " + code + "\nMessage: " + JSON.stringify(body))
13+
}
14+
}

api/middleware.mustache

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {RequestContext, ResponseContext} from './http/http';
2+
import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rxjsStub'{{/useRxJS}};
3+
4+
/**
5+
* Defines the contract for a middleware intercepting requests before
6+
* they are sent (but after the RequestContext was created)
7+
* and before the ResponseContext is unwrapped.
8+
*
9+
*/
10+
export interface Middleware {
11+
/**
12+
* Modifies the request before the request is sent.
13+
*
14+
* @param context RequestContext of a request which is about to be sent to the server
15+
* @returns an observable of the updated request context
16+
*
17+
*/
18+
pre(context: RequestContext): Observable<RequestContext>;
19+
/**
20+
* Modifies the returned response before it is deserialized.
21+
*
22+
* @param context ResponseContext of a sent request
23+
* @returns an observable of the modified response context
24+
*/
25+
post(context: ResponseContext): Observable<ResponseContext>;
26+
}
27+
28+
export class PromiseMiddlewareWrapper implements Middleware {
29+
30+
public constructor(private middleware: PromiseMiddleware) {
31+
32+
}
33+
34+
pre(context: RequestContext): Observable<RequestContext> {
35+
return from(this.middleware.pre(context));
36+
}
37+
38+
post(context: ResponseContext): Observable<ResponseContext> {
39+
return from(this.middleware.post(context));
40+
}
41+
42+
}
43+
44+
/**
45+
* Defines the contract for a middleware intercepting requests before
46+
* they are sent (but after the RequestContext was created)
47+
* and before the ResponseContext is unwrapped.
48+
*
49+
*/
50+
export interface PromiseMiddleware {
51+
/**
52+
* Modifies the request before the request is sent.
53+
*
54+
* @param context RequestContext of a request which is about to be sent to the server
55+
* @returns an observable of the updated request context
56+
*
57+
*/
58+
pre(context: RequestContext): Promise<RequestContext>;
59+
/**
60+
* Modifies the returned response before it is deserialized.
61+
*
62+
* @param context ResponseContext of a sent request
63+
* @returns an observable of the modified response context
64+
*/
65+
post(context: ResponseContext): Promise<ResponseContext>;
66+
}

0 commit comments

Comments
 (0)