From 1d0a3246fa2f38c4e93fec946cabb10872b40b77 Mon Sep 17 00:00:00 2001 From: Tino Fuhrmann Date: Mon, 15 Jun 2020 08:12:39 +0200 Subject: [PATCH 01/15] [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 https://github.com/OpenAPITools/openapi-generator/issues/802#issuecomment-617262139 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 Co-authored-by: Esteban Gehring --- .gitignore.mustache | 1 + README.mustache | 30 ++++ api/api.mustache | 221 ++++++++++++++++++++++++++ api/baseapi.mustache | 44 ++++++ api/exception.mustache | 14 ++ api/middleware.mustache | 66 ++++++++ auth/auth.mustache | 161 +++++++++++++++++++ configuration.mustache | 73 +++++++++ git_push.sh.mustache | 52 +++++++ http/http.mustache | 258 +++++++++++++++++++++++++++++++ http/isomorphic-fetch.mustache | 53 +++++++ http/jquery.mustache | 87 +++++++++++ http/servers.mustache | 55 +++++++ index.mustache | 41 +++++ licenseInfo.mustache | 11 ++ model/ObjectSerializer.mustache | 240 ++++++++++++++++++++++++++++ model/model.mustache | 79 ++++++++++ model/models_all.mustache | 5 + package.mustache | 61 ++++++++ rxjsStub.mustache | 27 ++++ services/ObjectParamAPI.mustache | 35 +++++ services/ObservableAPI.mustache | 23 +++ services/PromiseAPI.mustache | 22 +++ services/api.mustache | 23 +++ services/configuration.mustache | 21 +++ services/http.mustache | 20 +++ services/index.mustache | 165 ++++++++++++++++++++ tsconfig.mustache | 40 +++++ types/ObjectParamAPI.mustache | 63 ++++++++ types/ObservableAPI.mustache | 93 +++++++++++ types/PromiseAPI.mustache | 69 +++++++++ util.mustache | 28 ++++ 32 files changed, 2181 insertions(+) create mode 100644 .gitignore.mustache create mode 100644 README.mustache create mode 100644 api/api.mustache create mode 100644 api/baseapi.mustache create mode 100644 api/exception.mustache create mode 100644 api/middleware.mustache create mode 100644 auth/auth.mustache create mode 100644 configuration.mustache create mode 100755 git_push.sh.mustache create mode 100644 http/http.mustache create mode 100644 http/isomorphic-fetch.mustache create mode 100644 http/jquery.mustache create mode 100644 http/servers.mustache create mode 100644 index.mustache create mode 100644 licenseInfo.mustache create mode 100644 model/ObjectSerializer.mustache create mode 100644 model/model.mustache create mode 100644 model/models_all.mustache create mode 100644 package.mustache create mode 100644 rxjsStub.mustache create mode 100644 services/ObjectParamAPI.mustache create mode 100644 services/ObservableAPI.mustache create mode 100644 services/PromiseAPI.mustache create mode 100644 services/api.mustache create mode 100644 services/configuration.mustache create mode 100644 services/http.mustache create mode 100644 services/index.mustache create mode 100644 tsconfig.mustache create mode 100644 types/ObjectParamAPI.mustache create mode 100644 types/ObservableAPI.mustache create mode 100644 types/PromiseAPI.mustache create mode 100644 util.mustache diff --git a/.gitignore.mustache b/.gitignore.mustache new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/.gitignore.mustache @@ -0,0 +1 @@ +dist diff --git a/README.mustache b/README.mustache new file mode 100644 index 000000000000..d1c61395920d --- /dev/null +++ b/README.mustache @@ -0,0 +1,30 @@ +## {{npmName}}@{{npmVersion}} + +This generator creates TypeScript/JavaScript client that utilizes {{framework}}. + +### Building + +To build and compile the typescript sources to javascript use: +``` +npm install +npm run build +``` + +### Publishing + +First build the package then run ```npm publish``` + +### Consuming + +navigate to the folder of your consuming project and run one of the following commands. + +_published:_ + +``` +npm install {{npmName}}@{{npmVersion}} --save +``` + +_unPublished (not recommended):_ + +``` +npm install PATH_TO_GENERATED_PACKAGE --save diff --git a/api/api.mustache b/api/api.mustache new file mode 100644 index 000000000000..e4bb60616ff2 --- /dev/null +++ b/api/api.mustache @@ -0,0 +1,221 @@ +// TODO: better import syntax? +import { BaseAPIRequestFactory, RequiredError } from './baseapi'; +import {Configuration} from '../configuration'; +import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http'; +{{#platforms}} +{{#node}} +import * as FormData from "form-data"; +{{/node}} +{{/platforms}} +import {ObjectSerializer} from '../models/ObjectSerializer'; +import {ApiException} from './exception'; +import {isCodeInRange} from '../util'; +{{#useInversify}} +import { injectable } from "inversify"; +{{/useInversify}} + +{{#imports}} +import { {{classname}} } from '..{{filename}}'; +{{/imports}} +{{#operations}} + +/** + * {{#description}}{{{description}}}{{/description}}{{^description}}no description{{/description}} + */ +{{#useInversify}} +@injectable() +{{/useInversify}} +export class {{classname}}RequestFactory extends BaseAPIRequestFactory { + + {{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + */ + public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise { + let config = options || this.configuration; + {{#allParams}} + + {{#required}} + // verify required parameter '{{paramName}}' is not null or undefined + if ({{paramName}} === null || {{paramName}} === undefined) { + throw new RequiredError('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.'); + } + + {{/required}} + {{/allParams}} + + // Path Params + const localVarPath = '{{{path}}}'{{#pathParams}} + .replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}}; + + // Make Request Context + const requestContext = config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}}); + requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8") + + // Query Params + {{#queryParams}} + if ({{paramName}} !== undefined) { + requestContext.setQueryParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); + } + {{/queryParams}} + + // Header Params + {{#headerParams}} + requestContext.setHeaderParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); + {{/headerParams}} + + // Form Params + {{#hasFormParams}} + let localVarFormParams = new FormData(); + {{/hasFormParams}} + + {{#formParams}} + {{#isListContainer}} + if ({{paramName}}) { + {{#isCollectionFormatMulti}} + {{paramName}}.forEach((element) => { + localVarFormParams.append('{{baseName}}', element as any); + }) + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + // TODO: replace .append with .set + localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"])); + {{/isCollectionFormatMulti}} + } + {{/isListContainer}} + {{^isListContainer}} + if ({{paramName}} !== undefined) { + // TODO: replace .append with .set + {{^isFile}} + localVarFormParams.append('{{baseName}}', {{paramName}} as any); + {{/isFile}} + {{#isFile}} + {{#platforms}} + {{#node}} + localVarFormParams.append('{{baseName}}', {{paramName}}.data, {{paramName}}.name); + {{/node}} + {{#browser}} + localVarFormParams.append('{{baseName}}', {{paramName}}, {{paramName}}.name); + {{/browser}} + {{/platforms}} + {{/isFile}} + } + {{/isListContainer}} + {{/formParams}} + {{#hasFormParams}} + requestContext.setBody(localVarFormParams); + {{/hasFormParams}} + + // Body Params + {{#bodyParam}} + const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}} + "{{{mediaType}}}"{{#hasMore}},{{/hasMore}} + {{/consumes}}]); + requestContext.setHeaderParam("Content-Type", contentType); + const serializedBody = ObjectSerializer.stringify( + ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"), + contentType + ); + requestContext.setBody(serializedBody); + {{/bodyParam}} + + {{#hasAuthMethods}} + let authMethod = null; + {{/hasAuthMethods}} + // Apply auth methods + {{#authMethods}} + authMethod = config.authMethods["{{name}}"] + if (authMethod) { + await authMethod.applySecurityAuthentication(requestContext); + } + {{/authMethods}} + + return requestContext; + } + + {{/operation}} +} +{{/operations}} + + +{{#operations}} + +{{#useInversify}} +@injectable() +{{/useInversify}} +export class {{classname}}ResponseProcessor { + + {{#operation}} + /** + * Unwraps the actual response sent by the server from the response context and deserializes the response content + * to the expected objects + * + * @params response Response returned by the server for a request to {{nickname}} + * @throws ApiException if the response code was not in [200, 299] + */ + public async {{nickname}}(response: ResponseContext): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}> { + const contentType = ObjectSerializer.normalizeMediaType(response.headers["content-type"]); + {{#responses}} + if (isCodeInRange("{{code}}", response.httpStatusCode)) { + {{#dataType}} + {{#isBinary}} + const body: {{{dataType}}} = await response.getBodyAsFile() as any as {{{returnType}}}; + {{/isBinary}} + {{^isBinary}} + const body: {{{dataType}}} = ObjectSerializer.deserialize( + ObjectSerializer.parse(await response.body.text(), contentType), + "{{{dataType}}}", "{{returnFormat}}" + ) as {{{dataType}}}; + {{/isBinary}} + {{#is2xx}} + return body; + {{/is2xx}} + {{^is2xx}} + throw new ApiException<{{{dataType}}}>({{code}}, body); + {{/is2xx}} + {{/dataType}} + {{^dataType}} + {{#is2xx}} + return; + {{/is2xx}} + {{^is2xx}} + throw new ApiException(response.httpStatusCode, "{{message}}"); + {{/is2xx}} + {{/dataType}} + } + {{/responses}} + + // Work around for missing responses in specification, e.g. for petstore.yaml + if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) { + {{#returnType}} + {{#isBinary}} + const body: {{{returnType}}} = await response.getBodyAsFile() as any as {{{returnType}}}; + {{/isBinary}} + {{^isBinary}} + const body: {{{returnType}}} = ObjectSerializer.deserialize( + ObjectSerializer.parse(await response.body.text(), contentType), + "{{{returnType}}}", "{{returnFormat}}" + ) as {{{returnType}}}; + {{/isBinary}} + return body; + {{/returnType}} + {{^returnType}} + return; + {{/returnType}} + } + + let body = response.body || ""; + throw new ApiException(response.httpStatusCode, "Unknown API Status Code!\nBody: \"" + body + "\""); + } + + {{/operation}} +} +{{/operations}} diff --git a/api/baseapi.mustache b/api/baseapi.mustache new file mode 100644 index 000000000000..545fe4aacffc --- /dev/null +++ b/api/baseapi.mustache @@ -0,0 +1,44 @@ +import { Configuration } from '../configuration' +{{#useInversify}} +import { injectable, inject } from "inversify"; +import { AbstractConfiguration } from "../services/configuration"; +{{/useInversify}} + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + + +/** + * + * @export + * @class BaseAPI + */ +{{#useInversify}} +@injectable() +{{/useInversify}} +export class BaseAPIRequestFactory { + + constructor({{#useInversify}}@inject(AbstractConfiguration) {{/useInversify}}protected configuration: Configuration) { + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} diff --git a/api/exception.mustache b/api/exception.mustache new file mode 100644 index 000000000000..b76dca5aa4bc --- /dev/null +++ b/api/exception.mustache @@ -0,0 +1,14 @@ +/** + * Represents an error caused by an api call i.e. it has attributes for a HTTP status code + * and the returned body object. + * + * Example + * API returns a ErrorMessageObject whenever HTTP status code is not in [200, 299] + * => ApiException(404, someErrorMessageObject) + * + */ +export class ApiException extends Error { + public constructor(public code: number, public body: T) { + super("HTTP-Code: " + code + "\nMessage: " + JSON.stringify(body)) + } +} \ No newline at end of file diff --git a/api/middleware.mustache b/api/middleware.mustache new file mode 100644 index 000000000000..1c5929a2d005 --- /dev/null +++ b/api/middleware.mustache @@ -0,0 +1,66 @@ +import {RequestContext, ResponseContext} from './http/http'; +import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rxjsStub'{{/useRxJS}}; + +/** + * Defines the contract for a middleware intercepting requests before + * they are sent (but after the RequestContext was created) + * and before the ResponseContext is unwrapped. + * + */ +export interface Middleware { + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ + pre(context: RequestContext): Observable; + /** + * Modifies the returned response before it is deserialized. + * + * @param context ResponseContext of a sent request + * @returns an observable of the modified response context + */ + post(context: ResponseContext): Observable; +} + +export class PromiseMiddlewareWrapper implements Middleware { + + public constructor(private middleware: PromiseMiddleware) { + + } + + pre(context: RequestContext): Observable { + return from(this.middleware.pre(context)); + } + + post(context: ResponseContext): Observable { + return from(this.middleware.post(context)); + } + +} + +/** + * Defines the contract for a middleware intercepting requests before + * they are sent (but after the RequestContext was created) + * and before the ResponseContext is unwrapped. + * + */ +export interface PromiseMiddleware { + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ + pre(context: RequestContext): Promise; + /** + * Modifies the returned response before it is deserialized. + * + * @param context ResponseContext of a sent request + * @returns an observable of the modified response context + */ + post(context: ResponseContext): Promise; +} \ No newline at end of file diff --git a/auth/auth.mustache b/auth/auth.mustache new file mode 100644 index 000000000000..8e8d33d86d35 --- /dev/null +++ b/auth/auth.mustache @@ -0,0 +1,161 @@ +// typings for btoa are incorrect +{{#platforms}} +{{#node}} +//@ts-ignore +import * as btoa from "btoa"; +{{/node}} +{{/platforms}} +import { RequestContext } from "../http/http"; +{{#useInversify}} +import { injectable, inject, named } from "inversify"; +import { AbstractTokenProvider } from "../services/configuration"; +{{/useInversify}} + +/** + * Interface authentication schemes. + */ +export interface SecurityAuthentication { + /* + * @return returns the name of the security authentication as specified in OAI + */ + getName(): string; + + /** + * Applies the authentication scheme to the request context + * + * @params context the request context which should use this authentication scheme + */ + applySecurityAuthentication(context: RequestContext): void | Promise; +} + +{{#useInversify}} +export const AuthApiKey = Symbol("auth.api_key"); +export const AuthUsername = Symbol("auth.username"); +export const AuthPassword = Symbol("auth.password"); + +{{/useInversify}} +export interface TokenProvider { + getToken(): Promise | string; +} + +{{#authMethods}} +/** + * Applies {{type}} authentication to the request context. + */ +{{#useInversify}} +@injectable() +{{/useInversify}} +export class {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication implements SecurityAuthentication { + {{#isApiKey}} + /** + * Configures this api key authentication with the necessary properties + * + * @param apiKey: The api key to be used for every request + */ + public constructor({{#useInversify}}@inject(AuthApiKey) @named("{{name}}") {{/useInversify}}private apiKey: string) {} + {{/isApiKey}} + {{#isBasicBasic}} + /** + * Configures the http authentication with the required details. + * + * @param username username for http basic authentication + * @param password password for http basic authentication + */ + public constructor( + {{#useInversify}}@inject(AuthUsername) @named("{{name}}") {{/useInversify}}private username: string, + {{#useInversify}}@inject(AuthPassword) @named("{{name}}") {{/useInversify}}private password: string + ) {} + {{/isBasicBasic}} + {{#isBasicBearer}} + /** + * Configures the http authentication with the required details. + * + * @param tokenProvider service that can provide the up-to-date token when needed + */ + public constructor({{#useInversify}}@inject(AbstractTokenProvider) @named("{{name}}") {{/useInversify}}private tokenProvider: TokenProvider) {} + {{/isBasicBearer}} + {{#isOAuth}} + // TODO: How to handle oauth2 authentication! + public constructor() {} + {{/isOAuth}} + + public getName(): string { + return "{{name}}"; + } + + public {{#isBasicBearer}}async {{/isBasicBearer}}applySecurityAuthentication(context: RequestContext) { + {{#isApiKey}} + context.{{#isKeyInHeader}}setHeaderParam{{/isKeyInHeader}}{{#isKeyInQuery}}addCookie{{/isKeyInQuery}}{{#isKeyInCookie}}setQueryParam{{/isKeyInCookie}}("{{keyParamName}}", this.apiKey); + {{/isApiKey}} + {{#isBasicBasic}} + let comb = this.username + ":" + this.password; + context.setHeaderParam("Authorization", "Basic " + btoa(comb)); + {{/isBasicBasic}} + {{#isBasicBearer}} + context.setHeaderParam("Authorization", "Bearer " + await this.tokenProvider.getToken()); + {{/isBasicBearer}} + {{#isOAuth}} + // TODO + {{/isOAuth}} + } +} + +{{/authMethods}} + +export type AuthMethods = { + {{#authMethods}} + "{{name}}"?: SecurityAuthentication{{#hasMore}},{{/hasMore}} + {{/authMethods}} +} +{{#useInversify}} + +export const authMethodServices = { + {{#authMethods}} + "{{name}}": {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication{{#hasMore}},{{/hasMore}} + {{/authMethods}} +} +{{/useInversify}} + +export type ApiKeyConfiguration = string; +export type HttpBasicConfiguration = { "username": string, "password": string }; +export type HttpBearerConfiguration = { tokenProvider: TokenProvider }; +export type OAuth2Configuration = string; + +export type AuthMethodsConfiguration = { + {{#authMethods}} + "{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{#hasMore}},{{/hasMore}} + {{/authMethods}} +} + +/** + * Creates the authentication methods from a swagger description. + * + */ +export function configureAuthMethods(config: AuthMethodsConfiguration | undefined): AuthMethods { + let authMethods: AuthMethods = {} + + if (!config) { + return authMethods; + } + + {{#authMethods}} + if (config["{{name}}"]) { + authMethods["{{name}}"] = new {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication( + {{#isApiKey}} + config["{{name}}"] + {{/isApiKey}} + {{#isBasicBasic}} + config["{{name}}"]["username"], + config["{{name}}"]["password"] + {{/isBasicBasic}} + {{#isBasicBearer}} + config["{{name}}"]["tokenProvider"] + {{/isBasicBearer}} + {{#isOAuth}} + {{/isOAuth}} + ); + } + + {{/authMethods}} + return authMethods; +} \ No newline at end of file diff --git a/configuration.mustache b/configuration.mustache new file mode 100644 index 000000000000..539e9b06138d --- /dev/null +++ b/configuration.mustache @@ -0,0 +1,73 @@ +import { HttpLibrary } from "./http/http"; +import { Middleware, PromiseMiddleware, PromiseMiddlewareWrapper } from "./middleware"; +{{#frameworks}} +{{#fetch-api}} +import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorphic-fetch"; +{{/fetch-api}} +{{#jquery}} +import { JQueryHttpLibrary as DefaultHttpLibrary } from "./http/jquery"; +{{/jquery}} +{{/frameworks}} +import { BaseServerConfiguration, server1 } from "./servers"; +import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth"; + +export interface Configuration { + readonly baseServer: BaseServerConfiguration; + readonly httpApi: HttpLibrary; + readonly middleware: Middleware[]; + readonly authMethods: AuthMethods; +} + + +/** + * Interface with which a configuration object can be configured. + */ +export interface ConfigurationParameters { + /** + * Default server to use + */ + baseServer?: BaseServerConfiguration; + /** + * HTTP library to use e.g. IsomorphicFetch + */ + httpApi?: HttpLibrary; + /** + * The middlewares which will be applied to requests and responses + */ + middleware?: Middleware[]; + /** + * Configures all middlewares using the promise api instead of observables (which Middleware uses) + */ + promiseMiddleware?: PromiseMiddleware[]; + /** + * Configuration for the available authentication methods + */ + authMethods?: AuthMethodsConfiguration +} + +/** + * Configuration factory function + * + * If a property is not included in conf, a default is used: + * - baseServer: server1 + * - httpApi: IsomorphicFetchHttpLibrary + * - middleware: [] + * - promiseMiddleware: [] + * - authMethods: {} + * + * @param conf partial configuration + */ +export function createConfiguration(conf: ConfigurationParameters = {}): Configuration { + const configuration: Configuration = { + baseServer: conf.baseServer !== undefined ? conf.baseServer : server1, + httpApi: conf.httpApi || new DefaultHttpLibrary(), + middleware: conf.middleware || [], + authMethods: configureAuthMethods(conf.authMethods) + }; + if (conf.promiseMiddleware) { + conf.promiseMiddleware.forEach( + m => configuration.middleware.push(new PromiseMiddlewareWrapper(m)) + ); + } + return configuration; +} \ No newline at end of file diff --git a/git_push.sh.mustache b/git_push.sh.mustache new file mode 100755 index 000000000000..8a32e53995d6 --- /dev/null +++ b/git_push.sh.mustache @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/http/http.mustache b/http/http.mustache new file mode 100644 index 000000000000..dffa0dc544e6 --- /dev/null +++ b/http/http.mustache @@ -0,0 +1,258 @@ +{{#platforms}} +{{#node}} +// TODO: evaluate if we can easily get rid of this library +import * as FormData from "form-data"; +{{/node}} +{{/platforms}} +// typings of url-parse are incorrect... +// @ts-ignore +import * as URLParse from "url-parse"; +import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; + +{{#frameworks}} +{{#fetch-api}} +export * from './isomorphic-fetch'; +{{/fetch-api}} +{{#jquery}} +export * from './jquery'; +{{/jquery}} +{{/frameworks}} + +/** + * Represents an HTTP method. + */ +export enum HttpMethod { + GET = "GET", + HEAD = "HEAD", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + CONNECT = "CONNECT", + OPTIONS = "OPTIONS", + TRACE = "TRACE", + PATCH = "PATCH" +} + +/** + * Represents an HTTP file which will be transferred from or to a server. + */ +{{#platforms}} +{{#node}} +export type HttpFile = { + data: {{{fileContentDataType}}}, + name: string +}; +{{/node}} +{{#browser}} +export type HttpFile = {{{fileContentDataType}}} & { readonly name: string }; +{{/browser}} +{{/platforms}} + + +export class HttpException extends Error { + public constructor(msg: string) { + super(msg); + } +} + +/** + * Represents the body of an outgoing HTTP request. + */ +export type RequestBody = undefined | string | FormData; + +/** + * Represents an HTTP request context + */ +export class RequestContext { + private headers: { [key: string]: string } = {}; + private body: RequestBody = undefined; + private url: URLParse; + + /** + * Creates the request context using a http method and request resource url + * + * @param url url of the requested resource + * @param httpMethod http method + */ + public constructor(url: string, private httpMethod: HttpMethod) { + this.url = URLParse(url, true); + } + + /* + * Returns the url set in the constructor including the query string + * + */ + public getUrl(): string { + return this.url.toString(); + } + + /** + * Replaces the url set in the constructor with this url. + * + */ + public setUrl(url: string) { + this.url = URLParse(url, true); + } + + /** + * Sets the body of the http request either as a string or FormData + * + * Note that setting a body on a HTTP GET, HEAD, DELETE, CONNECT or TRACE + * request is discouraged. + * https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.7.3.1 + * + * @param body the body of the request + */ + public setBody(body: RequestBody) { + this.body = body; + } + + public getHttpMethod(): HttpMethod { + return this.httpMethod; + } + + public getHeaders(): { [key: string]: string } { + return this.headers; + } + + public getBody(): RequestBody { + return this.body; + } + + public setQueryParam(name: string, value: string) { + let queryObj = this.url.query; + queryObj[name] = value; + this.url.set("query", queryObj); + } + + /** + * Sets a cookie with the name and value. NO check for duplicate cookies is performed + * + */ + public addCookie(name: string, value: string): void { + if (!this.headers["Cookie"]) { + this.headers["Cookie"] = ""; + } + this.headers["Cookie"] += name + "=" + value + "; "; + } + + public setHeaderParam(key: string, value: string): void { + this.headers[key] = value; + } +} + +export interface ResponseBody { + text(): Promise; + binary(): Promise<{{{fileContentDataType}}}>; +} + + +/** + * Helper class to generate a `ResponseBody` from binary data + */ +export class SelfDecodingBody implements ResponseBody { + constructor(private dataSource: Promise<{{{fileContentDataType}}}>) {} + + binary(): Promise<{{{fileContentDataType}}}> { + return this.dataSource; + } + + async text(): Promise { + const data: {{{fileContentDataType}}} = await this.dataSource; + {{#platforms}} + {{#node}} + return data.toString(); + {{/node}} + {{#browser}} + // @ts-ignore + if (data.text) { + // @ts-ignore + return data.text(); + } + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener("load", () => resolve(reader.result as string)); + reader.addEventListener("error", () => reject(reader.error)); + reader.readAsText(data); + }); + {{/browser}} + {{/platforms}} + } +} + +export class ResponseContext { + public constructor( + public httpStatusCode: number, + public headers: { [key: string]: string }, + public body: ResponseBody + ) {} + + /** + * Parse header value in the form `value; param1="value1"` + * + * E.g. for Content-Type or Content-Disposition + * Parameter names are converted to lower case + * The first parameter is returned with the key `""` + */ + public getParsedHeader(headerName: string): { [parameter: string]: string } { + const result: { [parameter: string]: string } = {}; + if (!this.headers[headerName]) { + return result; + } + + const parameters = this.headers[headerName].split(";"); + for (const parameter of parameters) { + let [key, value] = parameter.split("=", 2); + key = key.toLowerCase().trim(); + if (value === undefined) { + result[""] = key; + } else { + value = value.trim(); + if (value.startsWith('"') && value.endsWith('"')) { + value = value.substring(1, value.length - 1); + } + result[key] = value; + } + } + return result; + } + + public async getBodyAsFile(): Promise { + const data = await this.body.binary(); + const fileName = this.getParsedHeader("content-disposition")["filename"] || ""; + {{#platforms}} + {{#node}} + return { data, name: fileName }; + {{/node}} + {{#browser}} + const contentType = this.headers["content-type"] || ""; + try { + return new File([data], fileName, { type: contentType }); + } catch (error) { + /** Fallback for when the File constructor is not available */ + return Object.assign(data, { + name: fileName, + type: contentType + }); + } + {{/browser}} + {{/platforms}} + } +} + +export interface HttpLibrary { + send(request: RequestContext): Observable; +} + +export interface PromiseHttpLibrary { + send(request: RequestContext): Promise; +} + +export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary { + return { + send(request: RequestContext): Observable { + return from(promiseHttpLibrary.send(request)); + } + } +} \ No newline at end of file diff --git a/http/isomorphic-fetch.mustache b/http/isomorphic-fetch.mustache new file mode 100644 index 000000000000..691b8187ccde --- /dev/null +++ b/http/isomorphic-fetch.mustache @@ -0,0 +1,53 @@ +import {HttpLibrary, RequestContext, ResponseContext} from './http'; +import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +{{#platforms}} +{{#node}} +import fetch from "node-fetch"; +{{/node}} +{{#browser}} +import "whatwg-fetch"; +{{/browser}} +{{/platforms}} + +export class IsomorphicFetchHttpLibrary implements HttpLibrary { + + public send(request: RequestContext): Observable { + let method = request.getHttpMethod().toString(); + let body = request.getBody(); + + const resultPromise = fetch(request.getUrl(), { + method: method, + body: body as any, + headers: request.getHeaders(), + {{#platforms}} + {{#browser}} + credentials: "same-origin" + {{/browser}} + {{/platforms}} + }).then((resp: any) => { + const headers: { [name: string]: string } = {}; + resp.headers.forEach((value: string, name: string) => { + headers[name] = value; + }); + + {{#platforms}} + {{#node}} + const body = { + text: () => resp.text(), + binary: () => resp.buffer() + }; + {{/node}} + {{^node}} + const body = { + text: () => resp.text(), + binary: () => resp.blob() + }; + {{/node}} + {{/platforms}} + return new ResponseContext(resp.status, headers, body); + }); + + return from>(resultPromise); + + } +} diff --git a/http/jquery.mustache b/http/jquery.mustache new file mode 100644 index 000000000000..3c67c5bd6f18 --- /dev/null +++ b/http/jquery.mustache @@ -0,0 +1,87 @@ +import { HttpLibrary, RequestContext, ResponseContext, HttpException, SelfDecodingBody } from './http'; +import * as e6p from 'es6-promise' +import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +e6p.polyfill(); +import * as $ from 'jquery'; + + +export class JQueryHttpLibrary implements HttpLibrary { + + public send(request: RequestContext): Observable { + let method = request.getHttpMethod().toString(); + let body = request.getBody(); + let headerParams = request.getHeaders() + + let requestOptions: any = { + url: request.getUrl(), + type: method, + headers: request.getHeaders(), + processData: false, + xhrFields: { withCredentials: true }, + data: body + }; + + // If we want a blob, we have to set the xhrFields' responseType AND add a + // custom converter to overwrite the default deserialization of JQuery... + requestOptions["xhrFields"] = { responseType: 'blob' }; + requestOptions["converters"] = {} + requestOptions["converters"]["* blob"] = (result:any) => result; + requestOptions["dataType"] = "blob"; + + + if (request.getHeaders()['Content-Type']) { + requestOptions.contentType = headerParams['Content-Type']; + } + requestOptions.dataFilter = ((headerParams: { [key:string]: string}) => { + return (data: string, type: string) => { + if (headerParams["Accept"] == "application/json" && data == "") { + return "{}" + } else { + return data + } + } + })(headerParams); + + if (request.getHeaders()["Cookie"]) { + throw new HttpException("Setting the \"Cookie\"-Header field is blocked by every major browser when using jquery.ajax requests. Please switch to another library like fetch to enable this option"); + } + + if (body && body.constructor.name == "FormData") { + requestOptions.contentType = false; + } + + const sentRequest = $.ajax(requestOptions); + + const resultPromise = new Promise((resolve, reject) => { + sentRequest.done((data, _, jqXHR) => { + const result = new ResponseContext( + jqXHR.status, + this.getResponseHeaders(jqXHR), + new SelfDecodingBody(Promise.resolve(data)) + ); + resolve(result); + }) + sentRequest.fail((jqXHR: any) => { + const headers = this.getResponseHeaders(jqXHR) + const result = new ResponseContext(jqXHR.status, headers, jqXHR.responseText); + resolve(result); + }) + }) + return from(resultPromise); + } + + private getResponseHeaders(jqXHR: any): { [key: string]: string } { + const responseHeaders: { [key: string]: string } = {}; + var headers = jqXHR.getAllResponseHeaders(); + headers = headers.split("\n"); + headers.forEach(function (header: any) { + header = header.split(": "); + var key = header.shift(); + if (key.length == 0) return + // chrome60+ force lowercase, other browsers can be different + key = key.toLowerCase(); + responseHeaders[key] = header.join(": "); + }); + return responseHeaders + } +} diff --git a/http/servers.mustache b/http/servers.mustache new file mode 100644 index 000000000000..aafda6432172 --- /dev/null +++ b/http/servers.mustache @@ -0,0 +1,55 @@ +import { RequestContext, HttpMethod } from "./http/http"; + +export interface BaseServerConfiguration { + makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext; +} + +/** + * + * Represents the configuration of a server including its + * url template and variable configuration based on the url. + * + */ +export class ServerConfiguration implements BaseServerConfiguration { + public constructor(private url: string, private variableConfiguration: T) {} + + /** + * Sets the value of the variables of this server. + * + * @param variableConfiguration a partial variable configuration for the variables contained in the url + */ + public setVariables(variableConfiguration: Partial) { + Object.assign(this.variableConfiguration, variableConfiguration); + } + + public getConfiguration(): T { + return this.variableConfiguration + } + + private getUrl() { + let replacedUrl = this.url; + for (const key in this.variableConfiguration) { + var re = new RegExp("{" + key + "}","g"); + replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]); + } + return replacedUrl + } + + /** + * Creates a new request context for this server using the url with variables + * replaced with their respective values and the endpoint of the request appended. + * + * @param endpoint the endpoint to be queried on the server + * @param httpMethod httpMethod to be used + * + */ + public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext { + return new RequestContext(this.getUrl() + endpoint, httpMethod); + } +} + +{{#servers}} +export const server{{-index}} = new ServerConfiguration<{ {{#variables}} "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} {{/variables}} }>("{{url}}", { {{#variables}} "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} }) +{{/servers}} + +export const servers = [{{#servers}}server{{-index}}{{^-last}}, {{/-last}}{{/servers}}]; diff --git a/index.mustache b/index.mustache new file mode 100644 index 000000000000..2302e5a1b457 --- /dev/null +++ b/index.mustache @@ -0,0 +1,41 @@ +import "es6-promise/auto"; + +export * from "./http/http"; +export * from "./auth/auth"; +export * from "./models/all"; +export { createConfiguration, Configuration } from "./configuration" +export * from "./apis/exception"; +export * from "./servers"; + +{{#useRxJS}} +export { Middleware } from './middleware'; +{{/useRxJS}} +{{^useRxJS}} +export { PromiseMiddleware as Middleware } from './middleware'; +{{/useRxJS}} +{{#useObjectParameters}} +export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI'; +{{/useObjectParameters}} +{{^useObjectParameters}} +{{#useRxJS}} +export { {{#apiInfo}}{{#apis}}{{#operations}}Observable{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObservableAPI'; +{{/useRxJS}} +{{^useRxJS}} +export { {{#apiInfo}}{{#apis}}{{#operations}}Promise{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/PromiseAPI'; +{{/useRxJS}} +{{/useObjectParameters}} + +{{#useInversify}} +export * from "./services/index"; +{{#useObjectParameters}} +export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObject{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObjectParamAPI'; +{{/useObjectParameters}} +{{^useObjectParameters}} +{{#useRxJS}} +export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObservable{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObservableAPI'; +{{/useRxJS}} +{{^useRxJS}} +export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractPromise{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/PromiseAPI'; +{{/useRxJS}} +{{/useObjectParameters}} +{{/useInversify}} diff --git a/licenseInfo.mustache b/licenseInfo.mustache new file mode 100644 index 000000000000..469fb03940fe --- /dev/null +++ b/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} + * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ diff --git a/model/ObjectSerializer.mustache b/model/ObjectSerializer.mustache new file mode 100644 index 000000000000..5a14d7ac3440 --- /dev/null +++ b/model/ObjectSerializer.mustache @@ -0,0 +1,240 @@ +{{#models}} +{{#model}} +export * from './{{{ classFilename }}}'; +{{/model}} +{{/models}} + +{{#models}} +{{#model}} +import { {{classname}}{{#hasEnums}}{{#vars}}{{#isEnum}}, {{classname}}{{enumName}} {{/isEnum}} {{/vars}}{{/hasEnums}} } from './{{{ classFilename }}}'; +{{/model}} +{{/models}} + +/* tslint:disable:no-unused-variable */ +let primitives = [ + "string", + "boolean", + "double", + "integer", + "long", + "float", + "number", + "any" + ]; + +const supportedMediaTypes: { [mediaType: string]: number } = { + "application/json": Infinity, + "application/octet-stream": 0 +} + + +let enumsMap: Set = new Set([ + {{#models}} + {{#model}} + {{#hasEnums}} + {{#vars}} + {{#isEnum}} + "{{classname}}{{enumName}}", + {{/isEnum}} + {{/vars}} + {{/hasEnums}} + {{/model}} + {{/models}} +]); + +let typeMap: {[index: string]: any} = { + {{#models}} + {{#model}} + "{{classname}}": {{classname}}, + {{/model}} + {{/models}} +} + +export class ObjectSerializer { + public static findCorrectType(data: any, expectedType: string) { + if (data == undefined) { + return expectedType; + } else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) { + return expectedType; + } else if (expectedType === "Date") { + return expectedType; + } else { + if (enumsMap.has(expectedType)) { + return expectedType; + } + + if (!typeMap[expectedType]) { + return expectedType; // w/e we don't know the type + } + + // Check the discriminator + let discriminatorProperty = typeMap[expectedType].discriminator; + if (discriminatorProperty == null) { + return expectedType; // the type does not have a discriminator. use it. + } else { + if (data[discriminatorProperty]) { + var discriminatorType = data[discriminatorProperty]; + if(typeMap[discriminatorType]){ + return discriminatorType; // use the type given in the discriminator + } else { + return expectedType; // discriminator did not map to a type + } + } else { + return expectedType; // discriminator was not present (or an empty string) + } + } + } + } + + public static serialize(data: any, type: string, format: string) { + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index in data) { + let date = data[index]; + transformedData.push(ObjectSerializer.serialize(date, subType, format)); + } + return transformedData; + } else if (type === "Date") { + if (format == "date") { + let month = data.getMonth()+1 + month = month < 10 ? "0" + month.toString() : month.toString() + let day = data.getDate(); + day = day < 10 ? "0" + day.toString() : day.toString(); + + return data.getFullYear() + "-" + month + "-" + day; + } else { + return data.toISOString(); + } + } else { + if (enumsMap.has(type)) { + return data; + } + if (!typeMap[type]) { // in case we dont know the type + return data; + } + + // Get the actual type of this object + type = this.findCorrectType(data, type); + + // get the map for the correct type. + let attributeTypes = typeMap[type].getAttributeTypeMap(); + let instance: {[index: string]: any} = {}; + for (let index in attributeTypes) { + let attributeType = attributeTypes[index]; + instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.name], attributeType.type, attributeType.format); + } + return instance; + } + } + + public static deserialize(data: any, type: string, format: string) { + // polymorphism may change the actual type. + type = ObjectSerializer.findCorrectType(data, type); + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index in data) { + let date = data[index]; + transformedData.push(ObjectSerializer.deserialize(date, subType, format)); + } + return transformedData; + } else if (type === "Date") { + return new Date(data); + } else { + if (enumsMap.has(type)) {// is Enum + return data; + } + + if (!typeMap[type]) { // dont know the type + return data; + } + let instance = new typeMap[type](); + let attributeTypes = typeMap[type].getAttributeTypeMap(); + for (let index in attributeTypes) { + let attributeType = attributeTypes[index]; + instance[attributeType.name] = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type, attributeType.format); + } + return instance; + } + } + + + /** + * Normalize media type + * + * We currently do not handle any media types attributes, i.e. anything + * after a semicolon. All content is assumed to be UTF-8 compatible. + */ + public static normalizeMediaType(mediaType: string | undefined): string | undefined { + if (mediaType === undefined) { + return undefined; + } + return mediaType.split(";")[0].trim().toLowerCase(); + } + + /** + * From a list of possible media types, choose the one we can handle best. + * + * The order of the given media types does not have any impact on the choice + * made. + */ + public static getPreferredMediaType(mediaTypes: Array): string { + /** According to OAS 3 we should default to json */ + if (!mediaTypes) { + return "application/json"; + } + + const normalMediaTypes = mediaTypes.map(this.normalizeMediaType); + let selectedMediaType: string | undefined = undefined; + let selectedRank: number = -Infinity; + for (const mediaType of normalMediaTypes) { + if (supportedMediaTypes[mediaType!] > selectedRank) { + selectedMediaType = mediaType; + selectedRank = supportedMediaTypes[mediaType!]; + } + } + + if (selectedMediaType === undefined) { + throw new Error("None of the given media types are supported: " + mediaTypes.join(", ")); + } + + return selectedMediaType!; + } + + /** + * Convert data to a string according the given media type + */ + public static stringify(data: any, mediaType: string): string { + if (mediaType === "application/json") { + return JSON.stringify(data); + } + + throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.stringify."); + } + + /** + * Parse data from a string according to the given media type + */ + public static parse(rawData: string, mediaType: string | undefined) { + if (mediaType === undefined) { + throw new Error("Cannot parse content. No Content-Type defined."); + } + + if (mediaType === "application/json") { + return JSON.parse(rawData); + } + + throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.parse."); + } +} diff --git a/model/model.mustache b/model/model.mustache new file mode 100644 index 000000000000..eafef58daa03 --- /dev/null +++ b/model/model.mustache @@ -0,0 +1,79 @@ +{{>licenseInfo}} +{{#models}} +{{#model}} +{{#tsImports}} +import { {{classname}} } from './{{filename}}'; +{{/tsImports}} +import { HttpFile } from '../http/http'; + +{{#description}} +/** +* {{{description}}} +*/ +{{/description}} +export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ +{{#vars}} +{{#description}} + /** + * {{{description}}} + */ +{{/description}} + '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}; +{{/vars}} + + {{#discriminator}} + static readonly discriminator: string | undefined = "{{discriminatorName}}"; + {{/discriminator}} + {{^discriminator}} + static readonly discriminator: string | undefined = undefined; + {{/discriminator}} + + {{^isArrayModel}} + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + {{#vars}} + { + "name": "{{name}}", + "baseName": "{{baseName}}", + "type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}", + "format": "{{dataFormat}}" + }{{#hasMore}}, + {{/hasMore}} + {{/vars}} + ]; + + static getAttributeTypeMap() { + {{#parent}} + return super.getAttributeTypeMap().concat({{classname}}.attributeTypeMap); + {{/parent}} + {{^parent}} + return {{classname}}.attributeTypeMap; + {{/parent}} + } + {{/isArrayModel}} + + public constructor() { + {{#parent}} + super(); + {{/parent}} + {{#allVars}} + {{#discriminatorValue}} + this.{{name}} = "{{discriminatorValue}}"; + {{/discriminatorValue}} + {{/allVars}} + {{#discriminatorName}} + this.{{discriminatorName}} = "{{classname}}"; + {{/discriminatorName}} + } +} + +{{#hasEnums}} + +{{#vars}} +{{#isEnum}} +export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} +{{/vars}} + +{{/hasEnums}} +{{/model}} +{{/models}} \ No newline at end of file diff --git a/model/models_all.mustache b/model/models_all.mustache new file mode 100644 index 000000000000..59c37a824222 --- /dev/null +++ b/model/models_all.mustache @@ -0,0 +1,5 @@ +{{#models}} +{{#model}} +export * from './{{{ classFilename }}}' +{{/model}} +{{/models}} \ No newline at end of file diff --git a/package.mustache b/package.mustache new file mode 100644 index 000000000000..0adbf68d9a14 --- /dev/null +++ b/package.mustache @@ -0,0 +1,61 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "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": { + {{#frameworks}} + {{#fetch-api}} + {{#platforms}} + {{#node}} + "node-fetch": "^2.6.0", + "@types/node-fetch": "^2.5.7", + {{/node}} + {{#browser}} + "whatwg-fetch": "^3.0.0", + {{/browser}} + {{/platforms}} + {{/fetch-api}} + {{#jquery}} + "@types/jquery": "^3.3.29", + "jquery": "^3.4.1", + {{/jquery}} + {{/frameworks}} + {{#platforms}} + {{#node}} + "@types/node": "*", + "form-data": "^2.5.0", + "btoa": "^1.2.1", + {{/node}} + {{/platforms}} + {{#useRxJS}} + "rxjs": "^6.4.0", + {{/useRxJS}} + {{#useInversify}} + "inversify": "^5.0.1", + {{/useInversify}} + "es6-promise": "^4.2.4", + "url-parse": "^1.4.3" + }, + "devDependencies": { + "typescript": "^3.9.3" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig":{ + "registry":"{{npmRepository}}" + } +{{/npmRepository}} +} diff --git a/rxjsStub.mustache b/rxjsStub.mustache new file mode 100644 index 000000000000..4c73715a2486 --- /dev/null +++ b/rxjsStub.mustache @@ -0,0 +1,27 @@ +export class Observable { + constructor(private promise: Promise) {} + + toPromise() { + return this.promise; + } + + pipe(callback: (value: T) => S | Promise): Observable { + return new Observable(this.promise.then(callback)); + } +} + +export function from(promise: Promise) { + return new Observable(promise); +} + +export function of(value: T) { + return new Observable(Promise.resolve(value)); +} + +export function mergeMap(callback: (value: T) => Observable) { + return (value: T) => callback(value).toPromise(); +} + +export function map(callback: any) { + return callback; +} diff --git a/services/ObjectParamAPI.mustache b/services/ObjectParamAPI.mustache new file mode 100644 index 000000000000..1c34257dcfa7 --- /dev/null +++ b/services/ObjectParamAPI.mustache @@ -0,0 +1,35 @@ +import type { HttpFile } from '../http/http'; +import type { Configuration } from '../configuration' +import type * as req from "../types/ObjectParamAPI"; +{{#useRxJS}} +import type { Observable } from 'rxjs'; +{{/useRxJS}} + +{{#models}} +{{#model}} +import type { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} +{{#operations}} + + +export abstract class AbstractObject{{classname}} { + {{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + * @param param the request object + */ + public abstract {{nickname}}(param: req.{{classname}}{{operationIdCamelCase}}Request, options?: Configuration): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>; + + {{/operation}} +} +{{/operations}} +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/services/ObservableAPI.mustache b/services/ObservableAPI.mustache new file mode 100644 index 000000000000..209436b07b41 --- /dev/null +++ b/services/ObservableAPI.mustache @@ -0,0 +1,23 @@ +import type { HttpFile } from "../http/http"; +import type { Observable } from {{#useRxJS}}"rxjs"{{/useRxJS}}{{^useRxJS}}"../rxjsStub"{{/useRxJS}}; +import type { Configuration } from "../configuration"; + +{{#models}} +{{#model}} +import { {{{ classname }}} } from "../models/{{{ classFilename }}}"; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} +{{#operations}} + + +export abstract class AbstractObservable{{classname}} { + {{#operation}} + public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>; + + {{/operation}} +} +{{/operations}} +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/services/PromiseAPI.mustache b/services/PromiseAPI.mustache new file mode 100644 index 000000000000..d3403ff707a4 --- /dev/null +++ b/services/PromiseAPI.mustache @@ -0,0 +1,22 @@ +import type { HttpFile } from "../http/http"; +import type { Configuration } from "../configuration"; + +{{#models}} +{{#model}} +import { {{{ classname }}} } from "../models/{{{ classFilename }}}"; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} +{{#operations}} + + +export abstract class AbstractPromise{{classname}} { + {{#operation}} + public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>; + + {{/operation}} +} +{{/operations}} +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/services/api.mustache b/services/api.mustache new file mode 100644 index 000000000000..8992017a7065 --- /dev/null +++ b/services/api.mustache @@ -0,0 +1,23 @@ +import type { Configuration } from "../configuration"; +import type { HttpFile, RequestContext, ResponseContext } from "../http/http"; + +{{#imports}} +import { {{classname}} } from "..{{filename}}"; +{{/imports}} +{{#operations}} + +export abstract class Abstract{{classname}}RequestFactory { + {{#operation}} + public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise; + + {{/operation}} +} + + +export abstract class Abstract{{classname}}ResponseProcessor { + {{#operation}} + public abstract {{nickname}}(response: ResponseContext): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}>; + + {{/operation}} +} +{{/operations}} diff --git a/services/configuration.mustache b/services/configuration.mustache new file mode 100644 index 000000000000..06e8dc992fcc --- /dev/null +++ b/services/configuration.mustache @@ -0,0 +1,21 @@ +import type { AbstractServerConfiguration } from "./http"; +import type { HttpLibrary, RequestContext } from "../http/http"; +import type { Middleware } from "../middleware"; +import type { AuthMethods, TokenProvider } from "../auth/auth"; +import type { Configuration } from "../configuration"; + +export abstract class AbstractConfiguration implements Configuration { + abstract get baseServer(): AbstractServerConfiguration; + abstract get httpApi(): HttpLibrary; + abstract get middleware(): Middleware[]; + abstract get authMethods(): AuthMethods; +} + +export abstract class AbstractAuthMethod { + public abstract getName(): string; + public abstract applySecurityAuthentication(context: RequestContext): void | Promise; +}; + +export abstract class AbstractTokenProvider implements TokenProvider { + public abstract getToken(): string | Promise; +} diff --git a/services/http.mustache b/services/http.mustache new file mode 100644 index 000000000000..89bda7bf3e3d --- /dev/null +++ b/services/http.mustache @@ -0,0 +1,20 @@ +{{#useRxJS}} +import type { Observable } from "rxjs"; +{{/useRxJS}} +import type { {{^useRxJS}}Promise{{/useRxJS}}HttpLibrary, HttpMethod, RequestContext, ResponseContext } from "../http/http"; +import type { {{^useRxJS}}Promise{{/useRxJS}}Middleware } from "../middleware"; +import type { BaseServerConfiguration } from "../servers"; + +export abstract class AbstractHttpLibrary implements {{^useRxJS}}Promise{{/useRxJS}}HttpLibrary { + public abstract send(request: RequestContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}; +}; + +export abstract class AbstractMiddleware implements {{^useRxJS}}Promise{{/useRxJS}}Middleware { + public abstract pre(context: RequestContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}; + public abstract post(context: ResponseContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}; +} + +export abstract class AbstractServerConfiguration implements BaseServerConfiguration { + public abstract makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext; +}; + diff --git a/services/index.mustache b/services/index.mustache new file mode 100644 index 000000000000..0b662f5d4d33 --- /dev/null +++ b/services/index.mustache @@ -0,0 +1,165 @@ +import { inject, injectable, multiInject, optional, interfaces } from "inversify"; + +import { Configuration } from "../configuration"; +import { ServerConfiguration, servers } from "../servers"; +import { HttpLibrary{{^useRxJS}}, wrapHttpLibrary{{/useRxJS}} } from "../http/http"; +import { Middleware{{^useRxJS}}, PromiseMiddlewareWrapper{{/useRxJS}} } from "../middleware"; +import { authMethodServices, AuthMethods } from "../auth/auth"; + +{{#frameworks}} +{{#fetch-api}} +import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "../http/isomorphic-fetch"; +{{/fetch-api}} +{{#jquery}} +import { JQueryHttpLibrary as DefaultHttpLibrary } from "../http/jquery"; +{{/jquery}} +{{/frameworks}} + +import { AbstractHttpLibrary, AbstractMiddleware, AbstractServerConfiguration } from "./http"; +import { AbstractConfiguration, AbstractAuthMethod, AbstractTokenProvider } from "./configuration"; + +export { AbstractHttpLibrary, AbstractMiddleware, AbstractServerConfiguration, AbstractConfiguration, AbstractAuthMethod, AbstractTokenProvider }; + +{{#useObjectParameters}} +import * as apis from "../types/ObjectParamAPI"; +import * as apiServices from "./ObjectParamAPI"; +{{/useObjectParameters}} +{{^useObjectParameters}} +{{#useRxJS}} +import * as apis from "../types/ObservableAPI"; +import * as apiServices from "./ObservableAPI"; +{{/useRxJS}} +{{^useRxJS}} +import * as apis from "../types/PromiseAPI"; +import * as apiServices from "./PromiseAPI"; +{{/useRxJS}} +{{/useObjectParameters}} + +@injectable() +class InjectableConfiguration implements AbstractConfiguration { + public httpApi: HttpLibrary = new DefaultHttpLibrary(); + public middleware: Middleware[] = []; + public authMethods: AuthMethods = {}; + + constructor( + @inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration = servers[0], + @inject(AbstractHttpLibrary) @optional() httpApi: AbstractHttpLibrary, + @multiInject(AbstractMiddleware) @optional() middleware: AbstractMiddleware[] = [], + @multiInject(AbstractAuthMethod) @optional() securityConfiguration: AbstractAuthMethod[] = [] + ) { + {{#useRxJS}} + this.httpApi = httpApi || new DefaultHttpLibrary(); + this.middleware = middleware; + {{/useRxJS}} + {{^useRxJS}} + this.httpApi = httpApi === undefined ? new DefaultHttpLibrary() : wrapHttpLibrary(httpApi); + for (const _middleware of middleware) { + this.middleware.push(new PromiseMiddlewareWrapper(_middleware)); + } + {{/useRxJS}} + for (const authMethod of securityConfiguration) { + const authName = authMethod.getName(); + // @ts-ignore + if (authMethodServices[authName] !== undefined) { + // @ts-ignore + this.authMethods[authName] = authMethod; + } + } + } +} + +/** + * Helper class to simplify binding the services + */ +export class ApiServiceBinder { + constructor(private container: interfaces.Container) { + this.container.bind(AbstractConfiguration).to(InjectableConfiguration); + } + + /** + * Allows you to bind a server configuration without having to import the service identifier. + */ + public get bindServerConfiguration() { + return this.container.bind(AbstractServerConfiguration); + } + + /** + * Use one of the predefined server configurations. + * + * To customize the server variables you can call `setVariables` on the + * return value; + */ + public bindServerConfigurationToPredefined(idx: number) { + this.bindServerConfiguration.toConstantValue(servers[idx]); + return servers[idx]; + } + + /** + * Explicitly define the service base url + */ + public bindServerConfigurationToURL(url: string) { + return this.bindServerConfiguration.toConstantValue( + new ServerConfiguration<{}>(url, {}) + ); + } + + /** + * Allows you to bind a http library without having to import the service identifier. + */ + public get bindHttpLibrary() { + return this.container.bind(AbstractHttpLibrary); + } + + /** + * Allows you to bind a middleware without having to import the service identifier. + * + * You can bind multiple middlewares by calling this multiple method times. + */ + public get bindMiddleware() { + return this.container.bind(AbstractMiddleware); + } + + /** + * Allows you to bind an auth method without having to import the service identifier. + * + * Note: The name of the bound auth method needs to be known in the specs, + * because the name is used to decide for which endpoints to apply the authentication. + */ + public get bindAuthMethod() { + return this.container.bind(AbstractAuthMethod); + } + + /** + * Use one of the predefined auth methods. + * + * Make sure that you have injected all dependencies for it. + */ + public bindAuthMethodToPredefined(name: keyof AuthMethods) { + return this.bindAuthMethod.to(authMethodServices[name]); + } + + /** + * Bind all the apis to their respective service identifiers + * + * If you want to only bind some of the apis, you need to do that manually. + */ + public bindAllApiServices() { + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#useObjectParameters}} + this.container.bind(apiServices.AbstractObject{{classname}}).to(apis.Object{{classname}}).inSingletonScope(); + {{/useObjectParameters}} + {{^useObjectParameters}} + {{#useRxJS}} + this.container.bind(apiServices.AbstractObservable{{classname}}).to(apis.Observable{{classname}}).inSingletonScope(); + {{/useRxJS}} + {{^useRxJS}} + this.container.bind(apiServices.AbstractPromise{{classname}}).to(apis.Promise{{classname}}).inSingletonScope(); + {{/useRxJS}} + {{/useObjectParameters}} + {{/operations}} + {{/apis}} + {{/apiInfo}} + } +} diff --git a/tsconfig.mustache b/tsconfig.mustache new file mode 100644 index 000000000000..674e7a64d3ae --- /dev/null +++ b/tsconfig.mustache @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "strict": true, + /* Basic Options */ + "target": "{{#supportsES6}}es6{{/supportsES6}}{{^supportsES6}}es5{{/supportsES6}}", + "module": "{{#supportsES6}}es6{{/supportsES6}}{{^supportsES6}}commonjs{{/supportsES6}}", + "moduleResolution": "node", + "declaration": true, + + /* Additional Checks */ + "noUnusedLocals": false, /* Report errors on unused locals. */ // TODO: reenable (unused imports!) + "noUnusedParameters": false, /* Report errors on unused parameters. */ // TODO: set to true again + "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, + {{#platforms}} + {{#node}} + "lib": [ "es6" ], + {{/node}} + {{#browser}} + "lib": [ "es6", "dom" ], + {{/browser}} + {{/platforms}} + {{#useInversify}} + "experimentalDecorators": true, + {{/useInversify}} + }, + "exclude": [ + "dist", + "node_modules" + ], + "filesGlob": [ + "./**/*.ts", + ] + +} \ No newline at end of file diff --git a/types/ObjectParamAPI.mustache b/types/ObjectParamAPI.mustache new file mode 100644 index 000000000000..b9469283be38 --- /dev/null +++ b/types/ObjectParamAPI.mustache @@ -0,0 +1,63 @@ +import { ResponseContext, RequestContext, HttpFile } from '../http/http'; +import * as models from '../models/all'; +import { Configuration} from '../configuration' +{{#useRxJS}} +import { Observable } from 'rxjs'; +{{/useRxJS}} + +{{#models}} +{{#model}} +import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} + +{{#operations}} +import { Observable{{classname}} } from "./ObservableAPI"; +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; + +{{#operation}} +export interface {{classname}}{{operationIdCamelCase}}Request { + {{#allParams}} + /** + * {{description}} + * @type {{dataType}} + * @memberof {{classname}}{{nickname}} + */ + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}} + {{/allParams}} +} + +{{/operation}} + +export class Object{{classname}} { + private api: Observable{{classname}} + + public constructor(configuration: Configuration, requestFactory?: {{classname}}RequestFactory, responseProcessor?: {{classname}}ResponseProcessor) { + this.api = new Observable{{classname}}(configuration, requestFactory, responseProcessor); + } + +{{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + * @param param the request object + */ + public {{nickname}}(param: {{classname}}{{operationIdCamelCase}}Request, options?: Configuration): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { + return this.api.{{nickname}}({{#allParams}}param.{{paramName}}, {{/allParams}} options){{^useRxJS}}.toPromise(){{/useRxJS}}; + } + +{{/operation}} + +} + +{{/operations}} + + +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/types/ObservableAPI.mustache b/types/ObservableAPI.mustache new file mode 100644 index 000000000000..79e1630a2654 --- /dev/null +++ b/types/ObservableAPI.mustache @@ -0,0 +1,93 @@ +import { ResponseContext, RequestContext, HttpFile } from '../http/http'; +import * as models from '../models/all'; +import { Configuration} from '../configuration' +import { Observable, of, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +{{#useInversify}} +import { injectable, inject, optional } from "inversify"; +import { AbstractConfiguration } from "../services/configuration"; +{{/useInversify}} + +{{#models}} +{{#model}} +import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} + +{{#operations}} +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; +{{#useInversify}} +import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service"; + +@injectable() +{{/useInversify}} +export class Observable{{classname}} { + {{#useInversify}} + private requestFactory: Abstract{{classname}}RequestFactory; + private responseProcessor: Abstract{{classname}}ResponseProcessor; + {{/useInversify}} + {{^useInversify}} + private requestFactory: {{classname}}RequestFactory; + private responseProcessor: {{classname}}ResponseProcessor; + {{/useInversify}} + private configuration: Configuration; + + public constructor( + {{#useInversify}} + @inject(AbstractConfiguration) configuration: Configuration, + @inject(Abstract{{classname}}RequestFactory) @optional() requestFactory?: Abstract{{classname}}RequestFactory, + @inject(Abstract{{classname}}ResponseProcessor) @optional() responseProcessor?: Abstract{{classname}}ResponseProcessor + {{/useInversify}} + {{^useInversify}} + configuration: Configuration, + requestFactory?: {{classname}}RequestFactory, + responseProcessor?: {{classname}}ResponseProcessor + {{/useInversify}} + ) { + this.configuration = configuration; + this.requestFactory = requestFactory || new {{classname}}RequestFactory(configuration); + this.responseProcessor = responseProcessor || new {{classname}}ResponseProcessor(); + } + +{{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + */ + public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { + const requestContextPromise = this.requestFactory.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); + + // build promise chain + let middlewarePreObservable = from(requestContextPromise); + for (let middleware of this.configuration.middleware) { + middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx))); + } + + return middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => this.configuration.httpApi.send(ctx))). + pipe(mergeMap((response: ResponseContext) => { + let middlewarePostObservable = of(response); + for (let middleware of this.configuration.middleware) { + middlewarePostObservable = middlewarePostObservable.pipe(mergeMap((rsp: ResponseContext) => middleware.post(rsp))); + } + return middlewarePostObservable.pipe(map((rsp: ResponseContext) => this.responseProcessor.{{nickname}}(rsp))); + })); + } + +{{/operation}} + +} + +{{/operations}} + + +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/types/PromiseAPI.mustache b/types/PromiseAPI.mustache new file mode 100644 index 000000000000..1bb0bdefe84c --- /dev/null +++ b/types/PromiseAPI.mustache @@ -0,0 +1,69 @@ +import { ResponseContext, RequestContext, HttpFile } from '../http/http'; +import * as models from '../models/all'; +import { Configuration} from '../configuration' +{{#useInversify}} +import { injectable, inject, optional } from "inversify"; +import { AbstractConfiguration } from "../services/configuration"; +{{/useInversify}} + +{{#models}} +{{#model}} +import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +{{/model}} +{{/models}} +{{#apiInfo}} +{{#apis}} +import { Observable{{classname}} } from './ObservableAPI'; + + +{{#operations}} +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; +{{#useInversify}} +import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service"; + +@injectable() +{{/useInversify}} +export class Promise{{classname}} { + private api: Observable{{classname}} + + public constructor( + {{#useInversify}} + @inject(AbstractConfiguration) configuration: Configuration, + @inject(Abstract{{classname}}RequestFactory) @optional() requestFactory?: Abstract{{classname}}RequestFactory, + @inject(Abstract{{classname}}ResponseProcessor) @optional() responseProcessor?: Abstract{{classname}}ResponseProcessor + {{/useInversify}} + {{^useInversify}} + configuration: Configuration, + requestFactory?: {{classname}}RequestFactory, + responseProcessor?: {{classname}}ResponseProcessor + {{/useInversify}} + ) { + this.api = new Observable{{classname}}(configuration, requestFactory, responseProcessor); + } + +{{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + */ + public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { + const result = this.api.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); + return result.toPromise(); + } + +{{/operation}} + +} + +{{/operations}} + + +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/util.mustache b/util.mustache new file mode 100644 index 000000000000..d1888434535c --- /dev/null +++ b/util.mustache @@ -0,0 +1,28 @@ +/** + * Returns if a specific http code is in a given code range + * where the code range is defined as a combination of digits + * and "X" (the letter X) with a length of 3 + * + * @param codeRange string with length 3 consisting of digits and "X" (the letter X) + * @param code the http status code to be checked against the code range + */ +export function isCodeInRange(codeRange: string, code: number): boolean { + // This is how the default value is encoded in OAG + if (codeRange === "0") { + return true; + } + if (codeRange == code.toString()) { + return true; + } else { + const codeString = code.toString(); + if (codeString.length != codeRange.length) { + return false; + } + for (let i = 0; i < codeString.length; i++) { + if (codeRange.charAt(i) != "X" && codeRange.charAt(i) != codeString.charAt(i)) { + return false; + } + } + return true; + } +} \ No newline at end of file From 1037f906970215a89263c6d06d73b25f4a79cedc Mon Sep 17 00:00:00 2001 From: Tomofumi Chiba Date: Sat, 27 Jun 2020 15:32:43 +0900 Subject: [PATCH 02/15] Add Deno support to typescript(experimental) generator (#6732) * add 'deno' to typescript platforms * add Deno support to typescript generator * add Deno support to typescript generator * add Deno support to typescript generator * add Deno support to typescript generator * add Deno support to typescript generator * add extensionForDeno property for typescript generator * add URLParse Deno wrapper for typescript generator * update deno version in .travis.yml --- api/api.mustache | 18 +++++----- api/baseapi.mustache | 2 +- api/middleware.mustache | 4 +-- auth/auth.mustache | 2 +- configuration.mustache | 10 +++--- http/http.mustache | 58 +++++++++++++++++++++++++++++---- http/isomorphic-fetch.mustache | 4 +-- http/servers.mustache | 2 +- index.mustache | 30 ++++++++--------- model/ObjectSerializer.mustache | 4 +-- model/model.mustache | 4 +-- model/models_all.mustache | 2 +- types/ObjectParamAPI.mustache | 12 +++---- types/ObservableAPI.mustache | 18 +++++----- types/PromiseAPI.mustache | 12 +++---- 15 files changed, 112 insertions(+), 70 deletions(-) diff --git a/api/api.mustache b/api/api.mustache index e4bb60616ff2..3bebe096502b 100644 --- a/api/api.mustache +++ b/api/api.mustache @@ -1,21 +1,21 @@ // TODO: better import syntax? -import { BaseAPIRequestFactory, RequiredError } from './baseapi'; -import {Configuration} from '../configuration'; -import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http'; +import { BaseAPIRequestFactory, RequiredError } from './baseapi{{extensionForDeno}}'; +import {Configuration} from '../configuration{{extensionForDeno}}'; +import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http{{extensionForDeno}}'; {{#platforms}} {{#node}} import * as FormData from "form-data"; {{/node}} {{/platforms}} -import {ObjectSerializer} from '../models/ObjectSerializer'; -import {ApiException} from './exception'; -import {isCodeInRange} from '../util'; +import {ObjectSerializer} from '../models/ObjectSerializer{{extensionForDeno}}'; +import {ApiException} from './exception{{extensionForDeno}}'; +import {isCodeInRange} from '../util{{extensionForDeno}}'; {{#useInversify}} import { injectable } from "inversify"; {{/useInversify}} {{#imports}} -import { {{classname}} } from '..{{filename}}'; +import { {{classname}} } from '..{{filename}}{{extensionForDeno}}'; {{/imports}} {{#operations}} @@ -102,9 +102,9 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{#node}} localVarFormParams.append('{{baseName}}', {{paramName}}.data, {{paramName}}.name); {{/node}} - {{#browser}} + {{^node}} localVarFormParams.append('{{baseName}}', {{paramName}}, {{paramName}}.name); - {{/browser}} + {{/node}} {{/platforms}} {{/isFile}} } diff --git a/api/baseapi.mustache b/api/baseapi.mustache index 545fe4aacffc..e33b699f1e76 100644 --- a/api/baseapi.mustache +++ b/api/baseapi.mustache @@ -1,4 +1,4 @@ -import { Configuration } from '../configuration' +import { Configuration } from '../configuration{{extensionForDeno}}' {{#useInversify}} import { injectable, inject } from "inversify"; import { AbstractConfiguration } from "../services/configuration"; diff --git a/api/middleware.mustache b/api/middleware.mustache index 1c5929a2d005..ee3a45bddcd9 100644 --- a/api/middleware.mustache +++ b/api/middleware.mustache @@ -1,5 +1,5 @@ -import {RequestContext, ResponseContext} from './http/http'; -import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rxjsStub'{{/useRxJS}}; +import {RequestContext, ResponseContext} from './http/http{{extensionForDeno}}'; +import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rxjsStub{{extensionForDeno}}'{{/useRxJS}}; /** * Defines the contract for a middleware intercepting requests before diff --git a/auth/auth.mustache b/auth/auth.mustache index 8e8d33d86d35..bab1d5b1bfa2 100644 --- a/auth/auth.mustache +++ b/auth/auth.mustache @@ -5,7 +5,7 @@ import * as btoa from "btoa"; {{/node}} {{/platforms}} -import { RequestContext } from "../http/http"; +import { RequestContext } from "../http/http{{extensionForDeno}}"; {{#useInversify}} import { injectable, inject, named } from "inversify"; import { AbstractTokenProvider } from "../services/configuration"; diff --git a/configuration.mustache b/configuration.mustache index 539e9b06138d..38f46ded384c 100644 --- a/configuration.mustache +++ b/configuration.mustache @@ -1,15 +1,15 @@ -import { HttpLibrary } from "./http/http"; -import { Middleware, PromiseMiddleware, PromiseMiddlewareWrapper } from "./middleware"; +import { HttpLibrary } from "./http/http{{extensionForDeno}}"; +import { Middleware, PromiseMiddleware, PromiseMiddlewareWrapper } from "./middleware{{extensionForDeno}}"; {{#frameworks}} {{#fetch-api}} -import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorphic-fetch"; +import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorphic-fetch{{extensionForDeno}}"; {{/fetch-api}} {{#jquery}} import { JQueryHttpLibrary as DefaultHttpLibrary } from "./http/jquery"; {{/jquery}} {{/frameworks}} -import { BaseServerConfiguration, server1 } from "./servers"; -import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth"; +import { BaseServerConfiguration, server1 } from "./servers{{extensionForDeno}}"; +import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth{{extensionForDeno}}"; export interface Configuration { readonly baseServer: BaseServerConfiguration; diff --git a/http/http.mustache b/http/http.mustache index dffa0dc544e6..efb155c7df7d 100644 --- a/http/http.mustache +++ b/http/http.mustache @@ -4,11 +4,17 @@ import * as FormData from "form-data"; {{/node}} {{/platforms}} +{{#platforms}} +{{^deno}} // typings of url-parse are incorrect... // @ts-ignore import * as URLParse from "url-parse"; -import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +{{/deno}} +{{/platforms}} +import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; +{{#platforms}} +{{^deno}} {{#frameworks}} {{#fetch-api}} export * from './isomorphic-fetch'; @@ -17,6 +23,8 @@ export * from './isomorphic-fetch'; export * from './jquery'; {{/jquery}} {{/frameworks}} +{{/deno}} +{{/platforms}} /** * Represents an HTTP method. @@ -43,11 +51,44 @@ export type HttpFile = { name: string }; {{/node}} -{{#browser}} +{{^node}} export type HttpFile = {{{fileContentDataType}}} & { readonly name: string }; -{{/browser}} +{{/node}} {{/platforms}} +{{#platforms}} +{{#deno}} +/** + * URLParse Wrapper for Deno + */ +class URLParse { + private url: URL; + constructor(address: string, _parser: boolean) { + this.url = new URL(address); + } + public set(_part: 'query', obj: {[key: string]: string | undefined}) { + for (const key in obj) { + const value = obj[key]; + if (value) { + this.url.searchParams.set(key, value); + } else { + this.url.searchParams.set(key, ""); + } + } + } + public get query() { + const obj: {[key: string]: string} = {}; + for (const [key, value] of this.url.searchParams.entries()) { + obj[key] = value; + } + return obj; + } + public toString() { + return this.url.toString(); + } +} +{{/deno}} +{{/platforms}} export class HttpException extends Error { public constructor(msg: string) { @@ -75,7 +116,7 @@ export class RequestContext { * @param httpMethod http method */ public constructor(url: string, private httpMethod: HttpMethod) { - this.url = URLParse(url, true); + this.url = new URLParse(url, true); } /* @@ -91,7 +132,7 @@ export class RequestContext { * */ public setUrl(url: string) { - this.url = URLParse(url, true); + this.url = new URLParse(url, true); } /** @@ -177,6 +218,9 @@ export class SelfDecodingBody implements ResponseBody { reader.readAsText(data); }); {{/browser}} + {{#deno}} + return data.text(); + {{/deno}} {{/platforms}} } } @@ -225,7 +269,7 @@ export class ResponseContext { {{#node}} return { data, name: fileName }; {{/node}} - {{#browser}} + {{^node}} const contentType = this.headers["content-type"] || ""; try { return new File([data], fileName, { type: contentType }); @@ -236,7 +280,7 @@ export class ResponseContext { type: contentType }); } - {{/browser}} + {{/node}} {{/platforms}} } } diff --git a/http/isomorphic-fetch.mustache b/http/isomorphic-fetch.mustache index 691b8187ccde..7646836694b3 100644 --- a/http/isomorphic-fetch.mustache +++ b/http/isomorphic-fetch.mustache @@ -1,5 +1,5 @@ -import {HttpLibrary, RequestContext, ResponseContext} from './http'; -import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +import {HttpLibrary, RequestContext, ResponseContext} from './http{{extensionForDeno}}'; +import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; {{#platforms}} {{#node}} import fetch from "node-fetch"; diff --git a/http/servers.mustache b/http/servers.mustache index aafda6432172..7d7d66936ff4 100644 --- a/http/servers.mustache +++ b/http/servers.mustache @@ -1,4 +1,4 @@ -import { RequestContext, HttpMethod } from "./http/http"; +import { RequestContext, HttpMethod } from "./http/http{{extensionForDeno}}"; export interface BaseServerConfiguration { makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext; diff --git a/index.mustache b/index.mustache index 2302e5a1b457..3e4d0907d0e4 100644 --- a/index.mustache +++ b/index.mustache @@ -1,41 +1,39 @@ -import "es6-promise/auto"; - -export * from "./http/http"; -export * from "./auth/auth"; -export * from "./models/all"; -export { createConfiguration, Configuration } from "./configuration" -export * from "./apis/exception"; -export * from "./servers"; +export * from "./http/http{{extensionForDeno}}"; +export * from "./auth/auth{{extensionForDeno}}"; +export * from "./models/all{{extensionForDeno}}"; +export { createConfiguration, Configuration } from "./configuration{{extensionForDeno}}" +export * from "./apis/exception{{extensionForDeno}}"; +export * from "./servers{{extensionForDeno}}"; {{#useRxJS}} -export { Middleware } from './middleware'; +export { Middleware } from './middleware{{extensionForDeno}}'; {{/useRxJS}} {{^useRxJS}} -export { PromiseMiddleware as Middleware } from './middleware'; +export { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}'; {{/useRxJS}} {{#useObjectParameters}} -export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI'; +export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI{{extensionForDeno}}'; {{/useObjectParameters}} {{^useObjectParameters}} {{#useRxJS}} -export { {{#apiInfo}}{{#apis}}{{#operations}}Observable{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObservableAPI'; +export { {{#apiInfo}}{{#apis}}{{#operations}}Observable{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObservableAPI{{extensionForDeno}}'; {{/useRxJS}} {{^useRxJS}} -export { {{#apiInfo}}{{#apis}}{{#operations}}Promise{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/PromiseAPI'; +export { {{#apiInfo}}{{#apis}}{{#operations}}Promise{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/PromiseAPI{{extensionForDeno}}'; {{/useRxJS}} {{/useObjectParameters}} {{#useInversify}} -export * from "./services/index"; +export * from "./services/index{{extensionForDeno}}"; {{#useObjectParameters}} export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObject{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObjectParamAPI'; {{/useObjectParameters}} {{^useObjectParameters}} {{#useRxJS}} -export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObservable{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObservableAPI'; +export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObservable{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObservableAPI{{extensionForDeno}}'; {{/useRxJS}} {{^useRxJS}} -export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractPromise{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/PromiseAPI'; +export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractPromise{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/PromiseAPI{{extensionForDeno}}'; {{/useRxJS}} {{/useObjectParameters}} {{/useInversify}} diff --git a/model/ObjectSerializer.mustache b/model/ObjectSerializer.mustache index 5a14d7ac3440..8fa4417f4a6a 100644 --- a/model/ObjectSerializer.mustache +++ b/model/ObjectSerializer.mustache @@ -1,12 +1,12 @@ {{#models}} {{#model}} -export * from './{{{ classFilename }}}'; +export * from './{{{ classFilename }}}{{extensionForDeno}}'; {{/model}} {{/models}} {{#models}} {{#model}} -import { {{classname}}{{#hasEnums}}{{#vars}}{{#isEnum}}, {{classname}}{{enumName}} {{/isEnum}} {{/vars}}{{/hasEnums}} } from './{{{ classFilename }}}'; +import { {{classname}}{{#hasEnums}}{{#vars}}{{#isEnum}}, {{classname}}{{enumName}} {{/isEnum}} {{/vars}}{{/hasEnums}} } from './{{{ classFilename }}}{{extensionForDeno}}'; {{/model}} {{/models}} diff --git a/model/model.mustache b/model/model.mustache index eafef58daa03..b8df9a3a3724 100644 --- a/model/model.mustache +++ b/model/model.mustache @@ -2,9 +2,9 @@ {{#models}} {{#model}} {{#tsImports}} -import { {{classname}} } from './{{filename}}'; +import { {{classname}} } from './{{filename}}{{extensionForDeno}}'; {{/tsImports}} -import { HttpFile } from '../http/http'; +import { HttpFile } from '../http/http{{extensionForDeno}}'; {{#description}} /** diff --git a/model/models_all.mustache b/model/models_all.mustache index 59c37a824222..43782db7ef55 100644 --- a/model/models_all.mustache +++ b/model/models_all.mustache @@ -1,5 +1,5 @@ {{#models}} {{#model}} -export * from './{{{ classFilename }}}' +export * from './{{{ classFilename }}}{{extensionForDeno}}' {{/model}} {{/models}} \ No newline at end of file diff --git a/types/ObjectParamAPI.mustache b/types/ObjectParamAPI.mustache index b9469283be38..9d8e23f8b2d2 100644 --- a/types/ObjectParamAPI.mustache +++ b/types/ObjectParamAPI.mustache @@ -1,21 +1,21 @@ -import { ResponseContext, RequestContext, HttpFile } from '../http/http'; -import * as models from '../models/all'; -import { Configuration} from '../configuration' +import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}'; +import * as models from '../models/all{{extensionForDeno}}'; +import { Configuration} from '../configuration{{extensionForDeno}}' {{#useRxJS}} import { Observable } from 'rxjs'; {{/useRxJS}} {{#models}} {{#model}} -import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +import { {{{ classname }}} } from '../models/{{{ classFilename }}}{{extensionForDeno}}'; {{/model}} {{/models}} {{#apiInfo}} {{#apis}} {{#operations}} -import { Observable{{classname}} } from "./ObservableAPI"; -import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; +import { Observable{{classname}} } from "./ObservableAPI{{extensionForDeno}}"; +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}"; {{#operation}} export interface {{classname}}{{operationIdCamelCase}}Request { diff --git a/types/ObservableAPI.mustache b/types/ObservableAPI.mustache index 79e1630a2654..d0cadf56fbd6 100644 --- a/types/ObservableAPI.mustache +++ b/types/ObservableAPI.mustache @@ -1,25 +1,25 @@ -import { ResponseContext, RequestContext, HttpFile } from '../http/http'; -import * as models from '../models/all'; -import { Configuration} from '../configuration' -import { Observable, of, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; -import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}}; +import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}'; +import * as models from '../models/all{{extensionForDeno}}'; +import { Configuration} from '../configuration{{extensionForDeno}}' +import { Observable, of, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; +import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; {{#useInversify}} import { injectable, inject, optional } from "inversify"; -import { AbstractConfiguration } from "../services/configuration"; +import { AbstractConfiguration } from "../services/configuration{{extensionForDeno}}"; {{/useInversify}} {{#models}} {{#model}} -import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +import { {{{ classname }}} } from '../models/{{{ classFilename }}}{{extensionForDeno}}'; {{/model}} {{/models}} {{#apiInfo}} {{#apis}} {{#operations}} -import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}"; {{#useInversify}} -import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service"; +import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service{{extensionForDeno}}"; @injectable() {{/useInversify}} diff --git a/types/PromiseAPI.mustache b/types/PromiseAPI.mustache index 1bb0bdefe84c..7e57347176b5 100644 --- a/types/PromiseAPI.mustache +++ b/types/PromiseAPI.mustache @@ -1,6 +1,6 @@ -import { ResponseContext, RequestContext, HttpFile } from '../http/http'; -import * as models from '../models/all'; -import { Configuration} from '../configuration' +import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}'; +import * as models from '../models/all{{extensionForDeno}}'; +import { Configuration} from '../configuration{{extensionForDeno}}' {{#useInversify}} import { injectable, inject, optional } from "inversify"; import { AbstractConfiguration } from "../services/configuration"; @@ -8,16 +8,16 @@ import { AbstractConfiguration } from "../services/configuration"; {{#models}} {{#model}} -import { {{{ classname }}} } from '../models/{{{ classFilename }}}'; +import { {{{ classname }}} } from '../models/{{{ classFilename }}}{{extensionForDeno}}'; {{/model}} {{/models}} {{#apiInfo}} {{#apis}} -import { Observable{{classname}} } from './ObservableAPI'; +import { Observable{{classname}} } from './ObservableAPI{{extensionForDeno}}'; {{#operations}} -import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}"; +import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}"; {{#useInversify}} import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service"; From 5a70a227649c65bdf4ef34aaa90c96bc9763ae9f Mon Sep 17 00:00:00 2001 From: Bodo Graumann Date: Mon, 28 Sep 2020 16:28:08 +0200 Subject: [PATCH 03/15] [typescript] Fix generation of enum models (#7529) This fixes #665 for the consolidated typescript generator. Original fix for typescript-node was in PR #2266, merged as 8417c5bed0e23764dc841816c2322cf7dc4e9b0d in version 4.1.0. --- model/ObjectSerializer.mustache | 7 ++++++- model/model.mustache | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/model/ObjectSerializer.mustache b/model/ObjectSerializer.mustache index 8fa4417f4a6a..382e7f8d7b4b 100644 --- a/model/ObjectSerializer.mustache +++ b/model/ObjectSerializer.mustache @@ -31,10 +31,13 @@ const supportedMediaTypes: { [mediaType: string]: number } = { let enumsMap: Set = new Set([ {{#models}} {{#model}} + {{#isEnum}} + "{{classname}}{{enumName}}", + {{/isEnum}} {{#hasEnums}} {{#vars}} {{#isEnum}} - "{{classname}}{{enumName}}", + "{{classname}}{{enumName}}", {{/isEnum}} {{/vars}} {{/hasEnums}} @@ -45,7 +48,9 @@ let enumsMap: Set = new Set([ let typeMap: {[index: string]: any} = { {{#models}} {{#model}} + {{^isEnum}} "{{classname}}": {{classname}}, + {{/isEnum}} {{/model}} {{/models}} } diff --git a/model/model.mustache b/model/model.mustache index b8df9a3a3724..84201657b0f4 100644 --- a/model/model.mustache +++ b/model/model.mustache @@ -11,6 +11,7 @@ import { HttpFile } from '../http/http{{extensionForDeno}}'; * {{{description}}} */ {{/description}} +{{^isEnum}} export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ {{#vars}} {{#description}} @@ -75,5 +76,9 @@ export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" { {{/vars}} {{/hasEnums}} +{{/isEnum}} +{{#isEnum}} +export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} {{/model}} {{/models}} \ No newline at end of file From 4c30355e3899e47b295c7457ea02549179d035eb Mon Sep 17 00:00:00 2001 From: Justin Black Date: Sun, 18 Oct 2020 21:58:59 -0700 Subject: [PATCH 04/15] Unifies naming for isArray in Schema class properties (#7691) * Updates key java files * Adds all lingering isArray fixes * Adds two files * Reverts two cs files * Fixes lingering isListContainer + isArrayModel references * Some ensure up to date updates --- api/api.mustache | 8 ++++---- model/model.mustache | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/api.mustache b/api/api.mustache index 3bebe096502b..7a6d2f2e76a2 100644 --- a/api/api.mustache +++ b/api/api.mustache @@ -78,7 +78,7 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{/hasFormParams}} {{#formParams}} - {{#isListContainer}} + {{#isArray}} if ({{paramName}}) { {{#isCollectionFormatMulti}} {{paramName}}.forEach((element) => { @@ -90,8 +90,8 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"])); {{/isCollectionFormatMulti}} } - {{/isListContainer}} - {{^isListContainer}} + {{/isArray}} + {{^isArray}} if ({{paramName}} !== undefined) { // TODO: replace .append with .set {{^isFile}} @@ -108,7 +108,7 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{/platforms}} {{/isFile}} } - {{/isListContainer}} + {{/isArray}} {{/formParams}} {{#hasFormParams}} requestContext.setBody(localVarFormParams); diff --git a/model/model.mustache b/model/model.mustache index 84201657b0f4..83dc74c34497 100644 --- a/model/model.mustache +++ b/model/model.mustache @@ -29,7 +29,7 @@ export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ static readonly discriminator: string | undefined = undefined; {{/discriminator}} - {{^isArrayModel}} + {{^isArray}} static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ {{#vars}} { @@ -50,7 +50,7 @@ export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ return {{classname}}.attributeTypeMap; {{/parent}} } - {{/isArrayModel}} + {{/isArray}} public constructor() { {{#parent}} From a3c4c3aad81617d4d3f8f264859dbc710f4f161b Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 6 Nov 2020 19:04:12 -0800 Subject: [PATCH 05/15] Removes secondaryParam and hasMore (#7882) * Removes secondaryParam and hasMore * Fixes tests * Only uses bodyParam in groovy template --- api/api.mustache | 2 +- auth/auth.mustache | 6 +++--- model/model.mustache | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/api.mustache b/api/api.mustache index 7a6d2f2e76a2..25a080f8ae93 100644 --- a/api/api.mustache +++ b/api/api.mustache @@ -117,7 +117,7 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { // Body Params {{#bodyParam}} const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}} - "{{{mediaType}}}"{{#hasMore}},{{/hasMore}} + "{{{mediaType}}}"{{^-last}},{{/-last}} {{/consumes}}]); requestContext.setHeaderParam("Content-Type", contentType); const serializedBody = ObjectSerializer.stringify( diff --git a/auth/auth.mustache b/auth/auth.mustache index bab1d5b1bfa2..0e0ae23b1f28 100644 --- a/auth/auth.mustache +++ b/auth/auth.mustache @@ -104,14 +104,14 @@ export class {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication export type AuthMethods = { {{#authMethods}} - "{{name}}"?: SecurityAuthentication{{#hasMore}},{{/hasMore}} + "{{name}}"?: SecurityAuthentication{{^-last}},{{/-last}} {{/authMethods}} } {{#useInversify}} export const authMethodServices = { {{#authMethods}} - "{{name}}": {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication{{#hasMore}},{{/hasMore}} + "{{name}}": {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication{{^-last}},{{/-last}} {{/authMethods}} } {{/useInversify}} @@ -123,7 +123,7 @@ export type OAuth2Configuration = string; export type AuthMethodsConfiguration = { {{#authMethods}} - "{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{#hasMore}},{{/hasMore}} + "{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{^-last}},{{/-last}} {{/authMethods}} } diff --git a/model/model.mustache b/model/model.mustache index 83dc74c34497..3cf7498ceaa7 100644 --- a/model/model.mustache +++ b/model/model.mustache @@ -37,8 +37,8 @@ export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ "baseName": "{{baseName}}", "type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}", "format": "{{dataFormat}}" - }{{#hasMore}}, - {{/hasMore}} + }{{^-last}}, + {{/-last}} {{/vars}} ]; From b875aa47efef45e34277ff9c84927833a26e0a9f Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Tue, 24 Nov 2020 21:05:07 +0100 Subject: [PATCH 06/15] Allows install typescript client via npm from Git (#7878) * Allows install typescript client via npm from Git 'prepublishOnly' is run before the package is published. 'prepack' is run before the package is published and after installation local installation eg. via Git. * Update examples for Typescript --- package.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.mustache b/package.mustache index 0adbf68d9a14..807989026b14 100644 --- a/package.mustache +++ b/package.mustache @@ -14,7 +14,7 @@ "typings": "./dist/index.d.ts", "scripts": { "build": "tsc", - "prepublishOnly": "npm run build" + "prepare": "npm run build" }, "dependencies": { {{#frameworks}} From 4e7a0ef1eb058a962f6ed983ae351268422df860 Mon Sep 17 00:00:00 2001 From: Tomofumi Chiba Date: Mon, 28 Dec 2020 21:57:00 +0900 Subject: [PATCH 07/15] [typescript(experimental)] fix for Deno v1.6 (#8265) * fix for Deno 1.6 * update dependency version of Deno test code --- index.mustache | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.mustache b/index.mustache index 3e4d0907d0e4..b19f7f9348a9 100644 --- a/index.mustache +++ b/index.mustache @@ -1,7 +1,8 @@ export * from "./http/http{{extensionForDeno}}"; export * from "./auth/auth{{extensionForDeno}}"; export * from "./models/all{{extensionForDeno}}"; -export { createConfiguration, Configuration } from "./configuration{{extensionForDeno}}" +export { createConfiguration } from "./configuration{{extensionForDeno}}" +export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { Configuration } from "./configuration{{extensionForDeno}}" export * from "./apis/exception{{extensionForDeno}}"; export * from "./servers{{extensionForDeno}}"; @@ -9,7 +10,7 @@ export * from "./servers{{extensionForDeno}}"; export { Middleware } from './middleware{{extensionForDeno}}'; {{/useRxJS}} {{^useRxJS}} -export { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}'; +export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}'; {{/useRxJS}} {{#useObjectParameters}} export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI{{extensionForDeno}}'; From 5c6be5c1a1cbbfb722494af68cc1e767d04be2a0 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Sun, 21 Feb 2021 12:14:52 +0800 Subject: [PATCH 08/15] replace tab with spaces, minor code format change (#8775) --- api/api.mustache | 84 +++++++++++++++++------------------ api/exception.mustache | 6 +-- api/middleware.mustache | 30 ++++++------- http/http.mustache | 41 +++++++++-------- http/jquery.mustache | 15 +++---- http/servers.mustache | 48 ++++++++++---------- tsconfig.mustache | 5 +-- types/ObjectParamAPI.mustache | 11 ++--- types/ObservableAPI.mustache | 37 +++++++-------- types/PromiseAPI.mustache | 7 ++- util.mustache | 38 ++++++++-------- 11 files changed, 155 insertions(+), 167 deletions(-) diff --git a/api/api.mustache b/api/api.mustache index 25a080f8ae93..95cb62e631fb 100644 --- a/api/api.mustache +++ b/api/api.mustache @@ -26,8 +26,8 @@ import { {{classname}} } from '..{{filename}}{{extensionForDeno}}'; @injectable() {{/useInversify}} export class {{classname}}RequestFactory extends BaseAPIRequestFactory { - - {{#operation}} + + {{#operation}} /** {{#notes}} * {{¬es}} @@ -40,44 +40,44 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{/allParams}} */ public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise { - let config = options || this.configuration; - {{#allParams}} - - {{#required}} + let config = options || this.configuration; + {{#allParams}} + + {{#required}} // verify required parameter '{{paramName}}' is not null or undefined if ({{paramName}} === null || {{paramName}} === undefined) { throw new RequiredError('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.'); } - {{/required}} - {{/allParams}} - - // Path Params - const localVarPath = '{{{path}}}'{{#pathParams}} + {{/required}} + {{/allParams}} + + // Path Params + const localVarPath = '{{{path}}}'{{#pathParams}} .replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}}; - // Make Request Context - const requestContext = config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}}); + // Make Request Context + const requestContext = config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}}); requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8") // Query Params - {{#queryParams}} + {{#queryParams}} if ({{paramName}} !== undefined) { - requestContext.setQueryParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); + requestContext.setQueryParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); } - {{/queryParams}} - - // Header Params - {{#headerParams}} - requestContext.setHeaderParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); - {{/headerParams}} - - // Form Params - {{#hasFormParams}} - let localVarFormParams = new FormData(); - {{/hasFormParams}} - - {{#formParams}} + {{/queryParams}} + + // Header Params + {{#headerParams}} + requestContext.setHeaderParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}")); + {{/headerParams}} + + // Form Params + {{#hasFormParams}} + let localVarFormParams = new FormData(); + {{/hasFormParams}} + + {{#formParams}} {{#isArray}} if ({{paramName}}) { {{#isCollectionFormatMulti}} @@ -86,14 +86,14 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { }) {{/isCollectionFormatMulti}} {{^isCollectionFormatMulti}} - // TODO: replace .append with .set - localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"])); + // TODO: replace .append with .set + localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"])); {{/isCollectionFormatMulti}} } {{/isArray}} {{^isArray}} if ({{paramName}} !== undefined) { - // TODO: replace .append with .set + // TODO: replace .append with .set {{^isFile}} localVarFormParams.append('{{baseName}}', {{paramName}} as any); {{/isFile}} @@ -109,13 +109,13 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{/isFile}} } {{/isArray}} - {{/formParams}} - {{#hasFormParams}} - requestContext.setBody(localVarFormParams); - {{/hasFormParams}} + {{/formParams}} + {{#hasFormParams}} + requestContext.setBody(localVarFormParams); + {{/hasFormParams}} - // Body Params - {{#bodyParam}} + // Body Params + {{#bodyParam}} const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}} "{{{mediaType}}}"{{^-last}},{{/-last}} {{/consumes}}]); @@ -125,7 +125,7 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { contentType ); requestContext.setBody(serializedBody); - {{/bodyParam}} + {{/bodyParam}} {{#hasAuthMethods}} let authMethod = null; @@ -144,8 +144,6 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { {{/operation}} } {{/operations}} - - {{#operations}} {{#useInversify}} @@ -213,9 +211,9 @@ export class {{classname}}ResponseProcessor { } let body = response.body || ""; - throw new ApiException(response.httpStatusCode, "Unknown API Status Code!\nBody: \"" + body + "\""); + throw new ApiException(response.httpStatusCode, "Unknown API Status Code!\nBody: \"" + body + "\""); } - - {{/operation}} + + {{/operation}} } {{/operations}} diff --git a/api/exception.mustache b/api/exception.mustache index b76dca5aa4bc..fb8d314eb41d 100644 --- a/api/exception.mustache +++ b/api/exception.mustache @@ -8,7 +8,7 @@ * */ export class ApiException extends Error { - public constructor(public code: number, public body: T) { + public constructor(public code: number, public body: T) { super("HTTP-Code: " + code + "\nMessage: " + JSON.stringify(body)) - } -} \ No newline at end of file + } +} diff --git a/api/middleware.mustache b/api/middleware.mustache index ee3a45bddcd9..578756d534dd 100644 --- a/api/middleware.mustache +++ b/api/middleware.mustache @@ -8,13 +8,13 @@ import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rx * */ export interface Middleware { - /** - * Modifies the request before the request is sent. - * - * @param context RequestContext of a request which is about to be sent to the server - * @returns an observable of the updated request context - * - */ + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ pre(context: RequestContext): Observable; /** * Modifies the returned response before it is deserialized. @@ -48,13 +48,13 @@ export class PromiseMiddlewareWrapper implements Middleware { * */ export interface PromiseMiddleware { - /** - * Modifies the request before the request is sent. - * - * @param context RequestContext of a request which is about to be sent to the server - * @returns an observable of the updated request context - * - */ + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ pre(context: RequestContext): Promise; /** * Modifies the returned response before it is deserialized. @@ -63,4 +63,4 @@ export interface PromiseMiddleware { * @returns an observable of the modified response context */ post(context: ResponseContext): Promise; -} \ No newline at end of file +} diff --git a/http/http.mustache b/http/http.mustache index efb155c7df7d..725b8e9a64a9 100644 --- a/http/http.mustache +++ b/http/http.mustache @@ -63,9 +63,11 @@ export type HttpFile = {{{fileContentDataType}}} & { readonly name: string }; */ class URLParse { private url: URL; + constructor(address: string, _parser: boolean) { this.url = new URL(address); } + public set(_part: 'query', obj: {[key: string]: string | undefined}) { for (const key in obj) { const value = obj[key]; @@ -76,6 +78,7 @@ class URLParse { } } } + public get query() { const obj: {[key: string]: string} = {}; for (const [key, value] of this.url.searchParams.entries()) { @@ -83,6 +86,7 @@ class URLParse { } return obj; } + public toString() { return this.url.toString(); } @@ -109,24 +113,24 @@ export class RequestContext { private body: RequestBody = undefined; private url: URLParse; - /** - * Creates the request context using a http method and request resource url - * - * @param url url of the requested resource - * @param httpMethod http method - */ + /** + * Creates the request context using a http method and request resource url + * + * @param url url of the requested resource + * @param httpMethod http method + */ public constructor(url: string, private httpMethod: HttpMethod) { this.url = new URLParse(url, true); } - + /* * Returns the url set in the constructor including the query string * */ public getUrl(): string { - return this.url.toString(); + return this.url.toString(); } - + /** * Replaces the url set in the constructor with this url. * @@ -149,27 +153,27 @@ export class RequestContext { } public getHttpMethod(): HttpMethod { - return this.httpMethod; + return this.httpMethod; } - + public getHeaders(): { [key: string]: string } { - return this.headers; + return this.headers; } public getBody(): RequestBody { return this.body; } - public setQueryParam(name: string, value: string) { + public setQueryParam(name: string, value: string) { let queryObj = this.url.query; queryObj[name] = value; this.url.set("query", queryObj); } - /** - * Sets a cookie with the name and value. NO check for duplicate cookies is performed - * - */ + /** + * Sets a cookie with the name and value. NO check for duplicate cookies is performed + * + */ public addCookie(name: string, value: string): void { if (!this.headers["Cookie"]) { this.headers["Cookie"] = ""; @@ -187,7 +191,6 @@ export interface ResponseBody { binary(): Promise<{{{fileContentDataType}}}>; } - /** * Helper class to generate a `ResponseBody` from binary data */ @@ -299,4 +302,4 @@ export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLib return from(promiseHttpLibrary.send(request)); } } -} \ No newline at end of file +} diff --git a/http/jquery.mustache b/http/jquery.mustache index 3c67c5bd6f18..9d1427b977fb 100644 --- a/http/jquery.mustache +++ b/http/jquery.mustache @@ -21,14 +21,13 @@ export class JQueryHttpLibrary implements HttpLibrary { data: body }; - // If we want a blob, we have to set the xhrFields' responseType AND add a - // custom converter to overwrite the default deserialization of JQuery... + // If we want a blob, we have to set the xhrFields' responseType AND add a + // custom converter to overwrite the default deserialization of JQuery... requestOptions["xhrFields"] = { responseType: 'blob' }; requestOptions["converters"] = {} requestOptions["converters"]["* blob"] = (result:any) => result; requestOptions["dataType"] = "blob"; - if (request.getHeaders()['Content-Type']) { requestOptions.contentType = headerParams['Content-Type']; } @@ -41,7 +40,7 @@ export class JQueryHttpLibrary implements HttpLibrary { } } })(headerParams); - + if (request.getHeaders()["Cookie"]) { throw new HttpException("Setting the \"Cookie\"-Header field is blocked by every major browser when using jquery.ajax requests. Please switch to another library like fetch to enable this option"); } @@ -49,9 +48,9 @@ export class JQueryHttpLibrary implements HttpLibrary { if (body && body.constructor.name == "FormData") { requestOptions.contentType = false; } - + const sentRequest = $.ajax(requestOptions); - + const resultPromise = new Promise((resolve, reject) => { sentRequest.done((data, _, jqXHR) => { const result = new ResponseContext( @@ -65,7 +64,7 @@ export class JQueryHttpLibrary implements HttpLibrary { const headers = this.getResponseHeaders(jqXHR) const result = new ResponseContext(jqXHR.status, headers, jqXHR.responseText); resolve(result); - }) + }) }) return from(resultPromise); } @@ -79,7 +78,7 @@ export class JQueryHttpLibrary implements HttpLibrary { var key = header.shift(); if (key.length == 0) return // chrome60+ force lowercase, other browsers can be different - key = key.toLowerCase(); + key = key.toLowerCase(); responseHeaders[key] = header.join(": "); }); return responseHeaders diff --git a/http/servers.mustache b/http/servers.mustache index 7d7d66936ff4..d2a0a254255c 100644 --- a/http/servers.mustache +++ b/http/servers.mustache @@ -22,30 +22,30 @@ export class ServerConfiguration implements Object.assign(this.variableConfiguration, variableConfiguration); } - public getConfiguration(): T { - return this.variableConfiguration - } - - private getUrl() { - let replacedUrl = this.url; - for (const key in this.variableConfiguration) { - var re = new RegExp("{" + key + "}","g"); - replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]); - } - return replacedUrl - } - - /** - * Creates a new request context for this server using the url with variables - * replaced with their respective values and the endpoint of the request appended. - * - * @param endpoint the endpoint to be queried on the server - * @param httpMethod httpMethod to be used - * - */ - public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext { - return new RequestContext(this.getUrl() + endpoint, httpMethod); - } + public getConfiguration(): T { + return this.variableConfiguration + } + + private getUrl() { + let replacedUrl = this.url; + for (const key in this.variableConfiguration) { + var re = new RegExp("{" + key + "}","g"); + replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]); + } + return replacedUrl + } + + /** + * Creates a new request context for this server using the url with variables + * replaced with their respective values and the endpoint of the request appended. + * + * @param endpoint the endpoint to be queried on the server + * @param httpMethod httpMethod to be used + * + */ + public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext { + return new RequestContext(this.getUrl() + endpoint, httpMethod); + } } {{#servers}} diff --git a/tsconfig.mustache b/tsconfig.mustache index 674e7a64d3ae..886f26f68ac3 100644 --- a/tsconfig.mustache +++ b/tsconfig.mustache @@ -30,11 +30,10 @@ {{/useInversify}} }, "exclude": [ - "dist", + "dist", "node_modules" ], "filesGlob": [ "./**/*.ts", ] - -} \ No newline at end of file +} diff --git a/types/ObjectParamAPI.mustache b/types/ObjectParamAPI.mustache index 9d8e23f8b2d2..43886364d6c3 100644 --- a/types/ObjectParamAPI.mustache +++ b/types/ObjectParamAPI.mustache @@ -30,13 +30,12 @@ export interface {{classname}}{{operationIdCamelCase}}Request { } {{/operation}} - export class Object{{classname}} { private api: Observable{{classname}} public constructor(configuration: Configuration, requestFactory?: {{classname}}RequestFactory, responseProcessor?: {{classname}}ResponseProcessor) { this.api = new Observable{{classname}}(configuration, requestFactory, responseProcessor); - } + } {{#operation}} /** @@ -51,13 +50,9 @@ export class Object{{classname}} { public {{nickname}}(param: {{classname}}{{operationIdCamelCase}}Request, options?: Configuration): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { return this.api.{{nickname}}({{#allParams}}param.{{paramName}}, {{/allParams}} options){{^useRxJS}}.toPromise(){{/useRxJS}}; } - -{{/operation}} +{{/operation}} } - {{/operations}} - - {{/apis}} -{{/apiInfo}} \ No newline at end of file +{{/apiInfo}} diff --git a/types/ObservableAPI.mustache b/types/ObservableAPI.mustache index d0cadf56fbd6..b001eba36b45 100644 --- a/types/ObservableAPI.mustache +++ b/types/ObservableAPI.mustache @@ -7,7 +7,6 @@ import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS} import { injectable, inject, optional } from "inversify"; import { AbstractConfiguration } from "../services/configuration{{extensionForDeno}}"; {{/useInversify}} - {{#models}} {{#model}} import { {{{ classname }}} } from '../models/{{{ classFilename }}}{{extensionForDeno}}'; @@ -64,30 +63,26 @@ export class Observable{{classname}} { {{/allParams}} */ public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { - const requestContextPromise = this.requestFactory.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); + const requestContextPromise = this.requestFactory.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); - // build promise chain - let middlewarePreObservable = from(requestContextPromise); - for (let middleware of this.configuration.middleware) { - middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx))); - } + // build promise chain + let middlewarePreObservable = from(requestContextPromise); + for (let middleware of this.configuration.middleware) { + middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx))); + } - return middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => this.configuration.httpApi.send(ctx))). - pipe(mergeMap((response: ResponseContext) => { - let middlewarePostObservable = of(response); - for (let middleware of this.configuration.middleware) { - middlewarePostObservable = middlewarePostObservable.pipe(mergeMap((rsp: ResponseContext) => middleware.post(rsp))); - } - return middlewarePostObservable.pipe(map((rsp: ResponseContext) => this.responseProcessor.{{nickname}}(rsp))); - })); + return middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => this.configuration.httpApi.send(ctx))). + pipe(mergeMap((response: ResponseContext) => { + let middlewarePostObservable = of(response); + for (let middleware of this.configuration.middleware) { + middlewarePostObservable = middlewarePostObservable.pipe(mergeMap((rsp: ResponseContext) => middleware.post(rsp))); + } + return middlewarePostObservable.pipe(map((rsp: ResponseContext) => this.responseProcessor.{{nickname}}(rsp))); + })); } - + {{/operation}} - } - {{/operations}} - - {{/apis}} -{{/apiInfo}} \ No newline at end of file +{{/apiInfo}} diff --git a/types/PromiseAPI.mustache b/types/PromiseAPI.mustache index 7e57347176b5..4014dec1e068 100644 --- a/types/PromiseAPI.mustache +++ b/types/PromiseAPI.mustache @@ -15,7 +15,6 @@ import { {{{ classname }}} } from '../models/{{{ classFilename }}}{{extensionFor {{#apis}} import { Observable{{classname}} } from './ObservableAPI{{extensionForDeno}}'; - {{#operations}} import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}"; {{#useInversify}} @@ -54,10 +53,10 @@ export class Promise{{classname}} { {{/allParams}} */ public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { - const result = this.api.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); + const result = this.api.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); return result.toPromise(); } - + {{/operation}} } @@ -66,4 +65,4 @@ export class Promise{{classname}} { {{/apis}} -{{/apiInfo}} \ No newline at end of file +{{/apiInfo}} diff --git a/util.mustache b/util.mustache index d1888434535c..07317f5828bc 100644 --- a/util.mustache +++ b/util.mustache @@ -7,22 +7,22 @@ * @param code the http status code to be checked against the code range */ export function isCodeInRange(codeRange: string, code: number): boolean { - // This is how the default value is encoded in OAG - if (codeRange === "0") { - return true; - } - if (codeRange == code.toString()) { - return true; - } else { - const codeString = code.toString(); - if (codeString.length != codeRange.length) { - return false; - } - for (let i = 0; i < codeString.length; i++) { - if (codeRange.charAt(i) != "X" && codeRange.charAt(i) != codeString.charAt(i)) { - return false; - } - } - return true; - } -} \ No newline at end of file + // This is how the default value is encoded in OAG + if (codeRange === "0") { + return true; + } + if (codeRange == code.toString()) { + return true; + } else { + const codeString = code.toString(); + if (codeString.length != codeRange.length) { + return false; + } + for (let i = 0; i < codeString.length; i++) { + if (codeRange.charAt(i) != "X" && codeRange.charAt(i) != codeString.charAt(i)) { + return false; + } + } + return true; + } +} From ab65f8487d45f9ee1b1f9cde46959d6c0ff0dce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 09/15] Apply template-patches/typescript-custom-license-header.patch --- .generator/templates/licenseInfo.mustache | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.generator/templates/licenseInfo.mustache b/.generator/templates/licenseInfo.mustache index 469fb03940fe..c59e5b520cbc 100644 --- a/.generator/templates/licenseInfo.mustache +++ b/.generator/templates/licenseInfo.mustache @@ -1,9 +1,7 @@ /** - * {{{appName}}} - * {{{appDescription}}} - * - * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} - * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech From b77548f4862b6d809a1b919e00eaac006cf4a1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 10/15] Apply template-patches/typescript-star-imports.patch --- .generator/templates/api/api.mustache | 2 +- .generator/templates/http/http.mustache | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.generator/templates/api/api.mustache b/.generator/templates/api/api.mustache index 95cb62e631fb..fadd1e2b4c52 100644 --- a/.generator/templates/api/api.mustache +++ b/.generator/templates/api/api.mustache @@ -4,7 +4,7 @@ import {Configuration} from '../configuration{{extensionForDeno}}'; import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http{{extensionForDeno}}'; {{#platforms}} {{#node}} -import * as FormData from "form-data"; +import FormData from "form-data"; {{/node}} {{/platforms}} import {ObjectSerializer} from '../models/ObjectSerializer{{extensionForDeno}}'; diff --git a/.generator/templates/http/http.mustache b/.generator/templates/http/http.mustache index 725b8e9a64a9..91331cfe7c27 100644 --- a/.generator/templates/http/http.mustache +++ b/.generator/templates/http/http.mustache @@ -1,14 +1,14 @@ {{#platforms}} {{#node}} // TODO: evaluate if we can easily get rid of this library -import * as FormData from "form-data"; +import FormData from "form-data"; {{/node}} {{/platforms}} {{#platforms}} {{^deno}} // typings of url-parse are incorrect... // @ts-ignore -import * as URLParse from "url-parse"; +import URLParse from "url-parse"; {{/deno}} {{/platforms}} import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; From af9f01a81ca62646e5ff2c4221fe708cea5188de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 11/15] Apply template-patches/typescript-date-serialization.patch --- .generator/templates/model/ObjectSerializer.mustache | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.generator/templates/model/ObjectSerializer.mustache b/.generator/templates/model/ObjectSerializer.mustache index 382e7f8d7b4b..cb58a6c2f8d0 100644 --- a/.generator/templates/model/ObjectSerializer.mustache +++ b/.generator/templates/model/ObjectSerializer.mustache @@ -106,6 +106,9 @@ export class ObjectSerializer { } return transformedData; } else if (type === "Date") { + if ("string" == typeof data) { + return data; + } if (format == "date") { let month = data.getMonth()+1 month = month < 10 ? "0" + month.toString() : month.toString() From f0f1224f47c2fb31bf67e4d96efa210da3dfe179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 12/15] Apply template-patches/typescript-user-agent.patch --- .generator/templates/http/http.mustache | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.generator/templates/http/http.mustache b/.generator/templates/http/http.mustache index 91331cfe7c27..88851d4e4ee2 100644 --- a/.generator/templates/http/http.mustache +++ b/.generator/templates/http/http.mustache @@ -1,3 +1,4 @@ +import { userAgent } from '../../../version'; {{#platforms}} {{#node}} // TODO: evaluate if we can easily get rid of this library @@ -109,7 +110,7 @@ export type RequestBody = undefined | string | FormData; * Represents an HTTP request context */ export class RequestContext { - private headers: { [key: string]: string } = {}; + private headers: { [key: string]: string } = { 'user-agent': userAgent }; private body: RequestBody = undefined; private url: URLParse; From e85d2ed75da60cdb18a9467d99869cae76689a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 13/15] Apply template-patches/typescript-alias-from-function.patch --- .generator/templates/types/ObservableAPI.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.generator/templates/types/ObservableAPI.mustache b/.generator/templates/types/ObservableAPI.mustache index b001eba36b45..645d28b7c25c 100644 --- a/.generator/templates/types/ObservableAPI.mustache +++ b/.generator/templates/types/ObservableAPI.mustache @@ -1,7 +1,7 @@ import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}'; import * as models from '../models/all{{extensionForDeno}}'; import { Configuration} from '../configuration{{extensionForDeno}}' -import { Observable, of, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; +import { Observable, of, from as from_ } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}}; {{#useInversify}} import { injectable, inject, optional } from "inversify"; @@ -66,7 +66,7 @@ export class Observable{{classname}} { const requestContextPromise = this.requestFactory.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); // build promise chain - let middlewarePreObservable = from(requestContextPromise); + let middlewarePreObservable = from_(requestContextPromise); for (let middleware of this.configuration.middleware) { middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx))); } From 82d47a1cba375a5569a1a1c4034beb55b016cf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Tue, 23 Mar 2021 10:48:26 +0100 Subject: [PATCH 14/15] Apply template-patches/typescript-per-endpoint-servers.patch --- .generator/templates/api/api.mustache | 4 +-- .generator/templates/configuration.mustache | 34 +++++++++++++++++---- .generator/templates/http/servers.mustache | 30 +++++++++++++++++- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/.generator/templates/api/api.mustache b/.generator/templates/api/api.mustache index fadd1e2b4c52..9d578db3b6ec 100644 --- a/.generator/templates/api/api.mustache +++ b/.generator/templates/api/api.mustache @@ -1,6 +1,6 @@ // TODO: better import syntax? import { BaseAPIRequestFactory, RequiredError } from './baseapi{{extensionForDeno}}'; -import {Configuration} from '../configuration{{extensionForDeno}}'; +import {Configuration, getServer } from '../configuration{{extensionForDeno}}'; import { RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http{{extensionForDeno}}'; {{#platforms}} {{#node}} @@ -57,7 +57,7 @@ export class {{classname}}RequestFactory extends BaseAPIRequestFactory { .replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}}; // Make Request Context - const requestContext = config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}}); + const requestContext = getServer(config, '{{classname}}.{{nickname}}').makeRequestContext(localVarPath, HttpMethod.{{httpMethod}}); requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8") // Query Params diff --git a/.generator/templates/configuration.mustache b/.generator/templates/configuration.mustache index 38f46ded384c..51237ddbc98a 100644 --- a/.generator/templates/configuration.mustache +++ b/.generator/templates/configuration.mustache @@ -8,11 +8,13 @@ import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorp import { JQueryHttpLibrary as DefaultHttpLibrary } from "./http/jquery"; {{/jquery}} {{/frameworks}} -import { BaseServerConfiguration, server1 } from "./servers{{extensionForDeno}}"; +import { BaseServerConfiguration, servers, operationServers } from "./servers{{extensionForDeno}}"; import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth{{extensionForDeno}}"; export interface Configuration { - readonly baseServer: BaseServerConfiguration; + readonly baseServer?: BaseServerConfiguration; + readonly serverIndex: number; + readonly operationServerIndices: { [ name: string ]: number }; readonly httpApi: HttpLibrary; readonly middleware: Middleware[]; readonly authMethods: AuthMethods; @@ -24,9 +26,17 @@ export interface Configuration { */ export interface ConfigurationParameters { /** - * Default server to use + * Default server to use (takes preference over serverIndex and operationServerIndices) */ baseServer?: BaseServerConfiguration; + /** + * Default index of a server to use from the predefined server list + */ + serverIndex?: number; + /** + * Default index of a server to use for an operation from the predefined server operation map + */ + operationServerIndices?: { [ name: string ]: number }; /** * HTTP library to use e.g. IsomorphicFetch */ @@ -49,7 +59,9 @@ export interface ConfigurationParameters { * Configuration factory function * * If a property is not included in conf, a default is used: - * - baseServer: server1 + * - baseServer: null + * - serverIndex: 0 + * - operationServerIndices: {} * - httpApi: IsomorphicFetchHttpLibrary * - middleware: [] * - promiseMiddleware: [] @@ -59,7 +71,9 @@ export interface ConfigurationParameters { */ export function createConfiguration(conf: ConfigurationParameters = {}): Configuration { const configuration: Configuration = { - baseServer: conf.baseServer !== undefined ? conf.baseServer : server1, + baseServer: conf.baseServer, + serverIndex: conf.serverIndex || 0, + operationServerIndices: conf.operationServerIndices || {}, httpApi: conf.httpApi || new DefaultHttpLibrary(), middleware: conf.middleware || [], authMethods: configureAuthMethods(conf.authMethods) @@ -70,4 +84,12 @@ export function createConfiguration(conf: ConfigurationParameters = {}): Configu ); } return configuration; -} \ No newline at end of file +} + +export function getServer(conf: Configuration, endpoint: string): BaseServerConfiguration { + if (conf.baseServer !== undefined) { + return conf.baseServer; + } + let index = (endpoint in conf.operationServerIndices) ? conf.operationServerIndices[endpoint] : conf.serverIndex; + return (endpoint in operationServers) ? operationServers[endpoint][index] : servers[index]; +} diff --git a/.generator/templates/http/servers.mustache b/.generator/templates/http/servers.mustache index d2a0a254255c..5f465d34df9d 100644 --- a/.generator/templates/http/servers.mustache +++ b/.generator/templates/http/servers.mustache @@ -49,7 +49,35 @@ export class ServerConfiguration implements } {{#servers}} -export const server{{-index}} = new ServerConfiguration<{ {{#variables}} "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} {{/variables}} }>("{{url}}", { {{#variables}} "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} }) +export const server{{-index}} = new ServerConfiguration<{ {{#variables}} + "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} +{{/variables}} }>("{{url}}", { {{#variables}} + "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} +}) {{/servers}} export const servers = [{{#servers}}server{{-index}}{{^-last}}, {{/-last}}{{/servers}}]; + +export const operationServers: { [endpoint: string]: BaseServerConfiguration[] } = { +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} +{{#servers}} +{{#-first}} + "{{{classname}}}.{{{nickname}}}": [ +{{/-first}} + new ServerConfiguration<{ {{#variables}} + "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} + {{/variables}} }>("{{url}}", { {{#variables}} + "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} + }), +{{#-last}} + ], +{{/-last}} +{{/servers}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} From 227b27835c9175724813e89a7fc28eeea83bb363 Mon Sep 17 00:00:00 2001 From: "ci.datadog-api-spec" Date: Tue, 23 Mar 2021 14:01:28 +0000 Subject: [PATCH 15/15] Regenerate client from commit 7e667f1 of spec repo --- .apigentools-info | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.apigentools-info b/.apigentools-info index 726d87158800..90fe40c45930 100644 --- a/.apigentools-info +++ b/.apigentools-info @@ -4,13 +4,13 @@ "spec_versions": { "v1": { "apigentools_version": "1.4.1.dev6", - "regenerated": "2021-03-23 10:51:13.975619", - "spec_repo_commit": "34cf37f" + "regenerated": "2021-03-23 14:01:21.590919", + "spec_repo_commit": "7e667f1" }, "v2": { "apigentools_version": "1.4.1.dev6", - "regenerated": "2021-03-23 10:51:18.277869", - "spec_repo_commit": "34cf37f" + "regenerated": "2021-03-23 14:01:26.701173", + "spec_repo_commit": "7e667f1" } } } \ No newline at end of file