diff --git a/openapi-template/ApiEntitiesRecord.mustache b/openapi-template/ApiEntitiesRecord.mustache new file mode 100644 index 0000000..d0b6c4c --- /dev/null +++ b/openapi-template/ApiEntitiesRecord.mustache @@ -0,0 +1,26 @@ +import {Map, Record, RecordOf} from 'immutable'; + +import { +{{#models}} +{{#model}} +{{#isEntity}} + {{classname}}RecordEntity, +{{/isEntity}} +{{/model}} +{{/models}} +} from "./models/index{{importFileExtension}}" + +export const ApiEntitiesRecordProps = { + recType: "ApiEntitiesRecord" as "ApiEntitiesRecord", +{{#models}} +{{#model}} +{{#isEntity}} + {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}: ({{classname}}RecordEntity(), Map()), +{{/isEntity}} +{{/model}} +{{/models}} +}; + +export type ApiEntitiesRecordPropsType = typeof ApiEntitiesRecordProps; +export const ApiEntitiesRecord = Record(ApiEntitiesRecordProps, ApiEntitiesRecordProps.recType); +export type ApiEntitiesRecord = RecordOf; diff --git a/openapi-template/ApiEntitiesReducer.mustache b/openapi-template/ApiEntitiesReducer.mustache new file mode 100644 index 0000000..781fc51 --- /dev/null +++ b/openapi-template/ApiEntitiesReducer.mustache @@ -0,0 +1,21 @@ +import {ApiEntitiesRecord} from "./ApiEntitiesRecord{{importFileExtension}}"; +import {ReducerBuilder} from "redux-ts-simple"; +import {normalizedEntities} from "./runtimeSagasAndRecords{{importFileExtension}}"; + +export const ApiEntitiesReducer = new ReducerBuilder(ApiEntitiesRecord()) + .on(normalizedEntities, (state, action): ApiEntitiesRecord => { + const {entities} = action.payload; + return state.withMutations(mutableState => { + for (const entityKey in entities) { + const entityMap = entities[entityKey]; + const currentEntityMap = mutableState.get(entityKey as any); + if (currentEntityMap) { + let mergedEntityMap = currentEntityMap.mergeDeep(entityMap); + if (!mergedEntityMap.equals(currentEntityMap)) { + mutableState.set(entityKey as any, mergedEntityMap); + } + } + } + }); + }) + .build(); diff --git a/openapi-template/ApiEntitiesSelectors.mustache b/openapi-template/ApiEntitiesSelectors.mustache new file mode 100644 index 0000000..9eaffec --- /dev/null +++ b/openapi-template/ApiEntitiesSelectors.mustache @@ -0,0 +1,5 @@ +export let getApiEntitiesState: (state: any) => any = (state: any) => state.app.apiEntities; + +export function setApiEntitiesStateGetter(getter: (state: any) => any) { // Use this to customize the location where you have placed your ApiEntitiesRecord in your project. + getApiEntitiesState = getter; +} \ No newline at end of file diff --git a/openapi-template/README.mustache b/openapi-template/README.mustache new file mode 100644 index 0000000..b73c645 --- /dev/null +++ b/openapi-template/README.mustache @@ -0,0 +1,127 @@ +# {{npmName}}@{{npmVersion}} + +A TypeScript SDK client for the {{host}} API. + +## Usage + +First, install the SDK from npm. + +```bash +npm install {{npmName}} --save +``` + +Next, try it out. + +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +```ts +{{>api_example}} +``` +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} + +## Documentation + +### API Endpoints + +All URIs are relative to *{{basePath}}* + +| Class | Method | HTTP request | Description +| ----- | ------ | ------------ | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}/{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +### Models + +{{#models}}{{#model}}- [{{{classname}}}]({{modelDocPath}}/{{{classname}}}.md){{/model}} +{{/models}} + +### Authorization + +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +#### {{name}}{{#isOAuth}} {{flow}}{{/isOAuth}} + +{{#isApiKey}} + +- **Type**: API key +- **API key parameter name**: `{{keyParamName}}` +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasicBasic}} + +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} +{{#isOAuth}} + +- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - `{{scope}}`: {{description}} +{{/scopes}} +{{/isOAuth}} +{{/authMethods}} + +## About + +This TypeScript SDK client supports the [Fetch API](https://fetch.spec.whatwg.org/) +and is automatically generated by the +[OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: `{{appVersion}}` +- Package version: `{{npmVersion}}` +{{^hideGenerationTimestamp}} +- Build date: `{{generatedDate}}` +{{/hideGenerationTimestamp}} +- Generator version: `{{generatorVersion}}` +- Build package: `{{generatorClass}}` + +The generated npm module supports the following: + +- Environments + * Node.js + * Webpack + * Browserify +- Language levels + * ES5 - you must have a Promises/A+ library installed + * ES6 +- Module systems + * CommonJS + * ES6 module system + +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Development + +### Building + +To build the TypeScript source code, you need to have Node.js and npm installed. +After cloning the repository, navigate to the project directory and run: + +```bash +npm install +npm run build +``` + +### Publishing + +Once you've built the package, you can publish it to npm: + +```bash +npm publish +``` + +## License + +[{{licenseInfo}}]({{{licenseUrl}}}) diff --git a/openapi-template/allSagas.mustache b/openapi-template/allSagas.mustache new file mode 100644 index 0000000..b756ab4 --- /dev/null +++ b/openapi-template/allSagas.mustache @@ -0,0 +1,19 @@ +import {all, fork} from "redux-saga/effects"; + +import { +{{#apiInfo}} +{{#apis}} + {{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}}AllSagas, +{{/apis}} +{{/apiInfo}} +} from "./index{{importFileExtension}}"; + +export function *allApiSagas() { + yield all([ +{{#apiInfo}} +{{#apis}} + fork({{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}}AllSagas), +{{/apis}} +{{/apiInfo}} + ]); +} diff --git a/openapi-template/api_doc.mustache b/openapi-template/api_doc.mustache new file mode 100644 index 0000000..f5c4927 --- /dev/null +++ b/openapi-template/api_doc.mustache @@ -0,0 +1,63 @@ +# {{classname}}{{#description}} + +{{.}}{{/description}} + +All URIs are relative to *{{basePath}}* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +{{#operations}}{{#operation}}| [**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{summary}} | +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +## {{operationId}} + +> {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) + +{{summary}}{{#notes}} + +{{.}}{{/notes}} + +### Example + +```ts +{{>api_example}} +``` + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}| **{{paramName}}** | {{#isEnum}}{{#allowableValues}}{{#values}}`{{{.}}}`{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}{{/isEnum}}{{^isEnum}}{{#isModel}}[{{baseType}}]({{baseType}}.md){{/isModel}}{{^isModel}}`{{{dataType}}}`{{/isModel}}{{/isEnum}} | {{description}} |{{^required}} [Optional]{{/required}}{{^isContainer}}{{#defaultValue}} [Defaults to `{{.}}`]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [Enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}`void` (Empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}{{#isOAuth}} {{flow}}{{/isOAuth}}](../README.md#{{{name}}}{{#isOAuth}}-{{flow}}{{/isOAuth}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}`{{{mediaType}}}`{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}`{{{mediaType}}}`{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} + +[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/openapi-template/api_example.mustache b/openapi-template/api_example.mustache new file mode 100644 index 0000000..b43ddee --- /dev/null +++ b/openapi-template/api_example.mustache @@ -0,0 +1,44 @@ +import { + Configuration, + {{classname}}, +} from '{{npmName}}'; +import type { {{operationIdCamelCase}}Request } from '{{npmName}}'; + +async function example() { + console.log("🚀 Testing {{npmName}} SDK..."); + {{#hasAuthMethods}} + const config = new Configuration({ {{#authMethods}}{{#isBasicBasic}} + // To configure HTTP basic authorization: {{{name}}} + username: "YOUR USERNAME", + password: "YOUR PASSWORD",{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + accessToken: "YOUR BEARER TOKEN",{{/isBasicBearer}}{{#isOAuth}} + // To configure OAuth2 access token for authorization: {{{name}}} {{{flow}}} + accessToken: "YOUR ACCESS TOKEN",{{/isOAuth}}{{#isApiKey}} + // To configure API key authorization: {{{name}}} + apiKey: "YOUR API KEY",{{/isApiKey}}{{#isHttpSignature}} + // To configure HTTP signature authorization: {{{name}}} + headers: { "YOUR HEADER NAME": "YOUR SIGNATURE" },{{/isHttpSignature}}{{/authMethods}} + }); + {{/hasAuthMethods}} + const api = new {{classname}}({{#hasAuthMethods}}config{{/hasAuthMethods}}); + + {{#hasParams}} + const body = { + {{#allParams}} + // {{{dataType}}}{{#description}} | {{{description}}}{{/description}}{{^required}} (optional){{/required}} + {{paramName}}: {{{example}}}{{^example}}...{{/example}}, + {{/allParams}} + } satisfies {{operationIdCamelCase}}Request; + + {{/hasParams}} + try { + const data = await api.{{{operationId}}}({{#hasParams}}body{{/hasParams}}); + console.log(data); + } catch (error) { + console.error(error); + } +} + +// Run the test +example().catch(console.error); diff --git a/openapi-template/apis.index.mustache b/openapi-template/apis.index.mustache new file mode 100644 index 0000000..98afdba --- /dev/null +++ b/openapi-template/apis.index.mustache @@ -0,0 +1,16 @@ +/* tslint:disable */ +/* eslint-disable */ +{{#useSagaAndRecords}} +export * from './SagaApiManager{{importFileExtension}}' +export * from './allSagas{{importFileExtension}}' +{{/useSagaAndRecords}} +{{#apiInfo}} +{{#apis}} +{{#operations}} +export * from './{{ classFilename }}{{importFileExtension}}'; +{{#useSagaAndRecords}} +export * from './{{{ classFilename }}}Sagas{{importFileExtension}}'; +{{/useSagaAndRecords}} +{{/operations}} +{{/apis}} +{{/apiInfo}} diff --git a/openapi-template/apis.mustache b/openapi-template/apis.mustache new file mode 100644 index 0000000..25a0e7f --- /dev/null +++ b/openapi-template/apis.mustache @@ -0,0 +1,557 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +import * as runtime from '../runtime{{importFileExtension}}'; +{{#imports.0}} +import type { + {{#imports}} + {{className}}, + {{/imports}} +} from '../models/index{{importFileExtension}}'; +{{^withoutRuntimeChecks}} +import { + {{#imports}} + {{className}}FromJSON, + {{className}}ToJSON, + {{/imports}} +} from '../models/index{{importFileExtension}}'; +{{/withoutRuntimeChecks}} +{{/imports.0}} + +{{#operations}} +{{#operation}} +{{#allParams.0}} +export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request { +{{#allParams}} + {{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{#hasReadOnly}}Omit<{{{dataType}}}, {{#readOnlyVars}}'{{baseName}}'{{^-last}}|{{/-last}}{{/readOnlyVars}}>{{/hasReadOnly}}{{^hasReadOnly}}{{{dataType}}}{{/hasReadOnly}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}; +{{/allParams}} +} + +{{/allParams.0}} +{{/operation}} +{{/operations}} +{{#withInterfaces}} +{{#operations}} +/** + * {{classname}} - interface + * {{#lambda.indented_1}}{{{unescapedDescription}}}{{/lambda.indented_1}} + * @export + * @interface {{classname}}Interface + */ +export interface {{classname}}Interface { +{{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{=<% %>=}}{<%&dataType%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + * @param {*} [options] Override http request option. + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + * @throws {RequiredError} + * @memberof {{classname}}Interface + */ + {{nickname}}Raw({{#allParams.0}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request, {{/allParams.0}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + */ + {{^useSingleRequestParameter}} + {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{{#responses}}{{#dataType}}{{{dataType}}}{{/dataType}}{{^dataType}}void{{/dataType}}{{^-last}} | {{/-last}}{{/responses}}{{^responses}}{{{returnType}}}{{#returnType}}{{#isResponseOptional}} | null | undefined {{/isResponseOptional}}{{/returnType}}{{^returnType}}void{{/returnType}}{{/responses}}>; + {{/useSingleRequestParameter}} + {{#useSingleRequestParameter}} + {{nickname}}({{#allParams.0}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request, {{/allParams.0}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{{#responses}}{{#dataType}}{{{dataType}}}{{/dataType}}{{^dataType}}void{{/dataType}}{{^-last}} | {{/-last}}{{/responses}}{{^responses}}{{{returnType}}}{{#returnType}}{{#isResponseOptional}} | null | undefined {{/isResponseOptional}}{{/returnType}}{{^returnType}}void{{/returnType}}{{/responses}}>; + {{/useSingleRequestParameter}} + +{{/operation}} +} + +{{/operations}} +{{/withInterfaces}} +{{#operations}} +/** + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} + */ +{{#withInterfaces}} +export class {{classname}} extends runtime.BaseAPI implements {{classname}}Interface { +{{/withInterfaces}} +{{^withInterfaces}} +export class {{classname}} extends runtime.BaseAPI { +{{/withInterfaces}} + + {{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + */ + async {{nickname}}Raw({{#allParams.0}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request, {{/allParams.0}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + {{#allParams}} + {{#required}} + if (requestParameters['{{paramName}}'] == null) { + throw new runtime.RequiredError( + '{{paramName}}', + 'Required parameter "{{paramName}}" was null or undefined when calling {{nickname}}().' + ); + } + + {{/required}} + {{/allParams}} + const queryParameters: any = {}; + + {{#queryParams}} + {{#isArray}} + if (requestParameters['{{paramName}}'] != null) { + {{#isCollectionFormatMulti}} + queryParameters['{{baseName}}'] = requestParameters['{{paramName}}']; + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + queryParameters['{{baseName}}'] = {{#uniqueItems}}Array.from({{/uniqueItems}}requestParameters['{{paramName}}']{{#uniqueItems}}){{/uniqueItems}}!.join(runtime.COLLECTION_FORMATS["{{collectionFormat}}"]); + {{/isCollectionFormatMulti}} + } + + {{/isArray}} + {{^isArray}} + if (requestParameters['{{paramName}}'] != null) { + {{#isExplode}} + {{#isContainer}} + for (let key of Object.keys(requestParameters['{{paramName}}'])) { + queryParameters[key] = requestParameters['{{paramName}}'][key]; + } + {{/isContainer}} + {{^isContainer}} + queryParameters['{{baseName}}'] = requestParameters['{{paramName}}']; + {{/isContainer}} + {{/isExplode}} + {{^isExplode}} + {{#isDateTimeType}} + queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString(); + {{/isDateTimeType}} + {{^isDateTimeType}} + {{#isDateType}} + queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString().substring(0,10); + {{/isDateType}} + {{^isDateType}} + queryParameters['{{baseName}}'] = requestParameters['{{paramName}}']; + {{/isDateType}} + {{/isDateTimeType}} + {{/isExplode}} + } + + {{/isArray}} + {{/queryParams}} + const headerParameters: runtime.HTTPHeaders = {}; + + {{#bodyParam}} + {{^consumes}} + headerParameters['Content-Type'] = 'application/json'; + + {{/consumes}} + {{#consumes.0}} + headerParameters['Content-Type'] = '{{{mediaType}}}'; + + {{/consumes.0}} + {{/bodyParam}} + {{#headerParams}} + {{#isArray}} + if (requestParameters['{{paramName}}'] != null) { + headerParameters['{{baseName}}'] = {{#uniqueItems}}Array.from({{/uniqueItems}}requestParameters['{{paramName}}']{{#uniqueItems}}){{/uniqueItems}}!.join(runtime.COLLECTION_FORMATS["{{collectionFormat}}"]); + } + + {{/isArray}} + {{^isArray}} + if (requestParameters['{{paramName}}'] != null) { + headerParameters['{{baseName}}'] = String(requestParameters['{{paramName}}']); + } + + {{/isArray}} + {{/headerParams}} + {{#authMethods}} + {{#isBasic}} + {{#isBasicBasic}} + if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) { + headerParameters["Authorization"] = "Basic " + btoa(this.configuration.username + ":" + this.configuration.password); + } + {{/isBasicBasic}} + {{#isBasicBearer}} + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}]); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + {{/isBasicBearer}} + {{/isBasic}} + {{#isApiKey}} + {{#isKeyInHeader}} + if (this.configuration && this.configuration.apiKey) { + headerParameters["{{keyParamName}}"] = await this.configuration.apiKey("{{keyParamName}}"); // {{name}} authentication + } + + {{/isKeyInHeader}} + {{#isKeyInQuery}} + if (this.configuration && this.configuration.apiKey) { + queryParameters["{{keyParamName}}"] = await this.configuration.apiKey("{{keyParamName}}"); // {{name}} authentication + } + + {{/isKeyInQuery}} + {{/isApiKey}} + {{#isOAuth}} + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken("{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}]); + } + + {{/isOAuth}} + {{/authMethods}} + {{#hasFormParams}} + const consumes: runtime.Consume[] = [ + {{#consumes}} + { contentType: '{{{mediaType}}}' }, + {{/consumes}} + ]; + // @ts-ignore: canConsumeForm may be unused + const canConsumeForm = runtime.canConsumeForm(consumes); + + let formParams: { append(param: string, value: any): any }; + let useForm = false; + {{#formParams}} + {{#isFile}} + // use FormData to transmit files using content-type "multipart/form-data" + useForm = canConsumeForm; + {{/isFile}} + {{/formParams}} + if (useForm) { + formParams = new FormData(); + } else { + formParams = new URLSearchParams(); + } + + {{#formParams}} + {{#isArray}} + if (requestParameters['{{paramName}}'] != null) { + {{#isCollectionFormatMulti}} + requestParameters['{{paramName}}'].forEach((element) => { + formParams.append('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', element as any); + }) + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + formParams.append('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', {{#uniqueItems}}Array.from({{/uniqueItems}}requestParameters['{{paramName}}']{{#uniqueItems}}){{/uniqueItems}}!.join(runtime.COLLECTION_FORMATS["{{collectionFormat}}"])); + {{/isCollectionFormatMulti}} + } + + {{/isArray}} + {{^isArray}} + if (requestParameters['{{paramName}}'] != null) { + {{#isDateTimeType}} + formParams.append('{{baseName}}', (requestParameters['{{paramName}}'] as any).toISOString()); + {{/isDateTimeType}} + {{^isDateTimeType}} + {{#isPrimitiveType}} + formParams.append('{{baseName}}', requestParameters['{{paramName}}'] as any); + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isEnumRef}} + formParams.append('{{baseName}}', requestParameters['{{paramName}}'] as any); + {{/isEnumRef}} + {{^isEnumRef}} + {{^withoutRuntimeChecks}} + formParams.append('{{baseName}}', new Blob([JSON.stringify({{{dataType}}}ToJSON(requestParameters['{{paramName}}']))], { type: "application/json", })); + {{/withoutRuntimeChecks}}{{#withoutRuntimeChecks}} + formParams.append('{{baseName}}', new Blob([JSON.stringify(requestParameters['{{paramName}}'])], { type: "application/json", })); + {{/withoutRuntimeChecks}} + {{/isEnumRef}} + {{/isPrimitiveType}} + {{/isDateTimeType}} + } + + {{/isArray}} + {{/formParams}} + {{/hasFormParams}} + + let urlPath = `{{{path}}}`; + {{#pathParams}} + {{#isDateTimeType}} + if (requestParameters['{{paramName}}'] instanceof Date) { + urlPath = urlPath.replace(`{${"{{baseName}}"}}`, encodeURIComponent(requestParameters['{{paramName}}'].toISOString())); + } else { + urlPath = urlPath.replace(`{${"{{baseName}}"}}`, encodeURIComponent(String(requestParameters['{{paramName}}']))); + } + {{/isDateTimeType}} + {{^isDateTimeType}} + {{#isDateType}} + if (requestParameters['{{paramName}}'] instanceof Date) { + urlPath = urlPath.replace(`{${"{{baseName}}"}}`, encodeURIComponent(requestParameters['{{paramName}}'].toISOString().substring(0,10))); + } else { + urlPath = urlPath.replace(`{${"{{baseName}}"}}`, encodeURIComponent(String(requestParameters['{{paramName}}']))); + } + {{/isDateType}} + {{^isDateType}} + urlPath = urlPath.replace(`{${"{{baseName}}"}}`, encodeURIComponent(String(requestParameters['{{paramName}}']))); + {{/isDateType}} + {{/isDateTimeType}} + {{/pathParams}} + + const response = await this.request({ + path: urlPath, + method: '{{httpMethod}}', + headers: headerParameters, + query: queryParameters, + {{#hasBodyParam}} + {{#bodyParam}} + {{#isContainer}} + {{^withoutRuntimeChecks}} + body: requestParameters['{{paramName}}']{{#isArray}}{{#items}}{{^isPrimitiveType}}!.map({{datatype}}ToJSON){{/isPrimitiveType}}{{/items}}{{/isArray}}, + {{/withoutRuntimeChecks}} + {{#withoutRuntimeChecks}} + body: requestParameters['{{paramName}}'], + {{/withoutRuntimeChecks}} + {{/isContainer}} + {{^isContainer}} + {{^isPrimitiveType}} + {{^withoutRuntimeChecks}} + body: {{dataType}}ToJSON(requestParameters['{{paramName}}']), + {{/withoutRuntimeChecks}} + {{#withoutRuntimeChecks}} + body: requestParameters['{{paramName}}'], + {{/withoutRuntimeChecks}} + {{/isPrimitiveType}} + {{#isPrimitiveType}} + body: requestParameters['{{paramName}}'] as any, + {{/isPrimitiveType}} + {{/isContainer}} + {{/bodyParam}} + {{/hasBodyParam}} + {{#hasFormParams}} + body: formParams, + {{/hasFormParams}} + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + {{#responses}} + if (response.status === {{code}}) { + {{#dataType}} + {{#isBinary}} + // Binary response for status {{code}} + return new runtime.BlobApiResponse(response); + {{/isBinary}} + {{^isBinary}} + {{#isFile}} + // File response for status {{code}} + return new runtime.BlobApiResponse(response); + {{/isFile}} + {{^isFile}} + {{#primitiveType}} + {{#isMap}} + // Map of primitives response for status {{code}} + return new runtime.JSONApiResponse(response); + {{/isMap}} + {{^isMap}} + {{#isArray}} + // Array of primitives response for status {{code}} + return new runtime.JSONApiResponse(response); + {{/isArray}} + {{^isArray}} + {{#simpleType}} + // Simple primitive type response for status {{code}} - check content-type + if (this.isJsonMime(response.headers.get('content-type'))) { + return new runtime.JSONApiResponse<{{dataType}}>(response); + } else { + return new runtime.TextApiResponse(response) as any; + } + {{/simpleType}} + {{^simpleType}} + // Primitive but not simple (e.g., object with additionalProperties) for status {{code}} + return new runtime.JSONApiResponse(response); + {{/simpleType}} + {{/isArray}} + {{/isMap}} + {{/primitiveType}} + {{^primitiveType}} + {{#isArray}} + // Array of objects response for status {{code}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => {{#uniqueItems}}new Set({{/uniqueItems}}jsonValue.map({{baseType}}FromJSON){{/withoutRuntimeChecks}}){{#uniqueItems}}){{/uniqueItems}}; + {{/isArray}} + {{^isArray}} + {{#isMap}} + // Map of objects response for status {{code}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => runtime.mapValues(jsonValue, {{baseType}}FromJSON){{/withoutRuntimeChecks}}); + {{/isMap}} + {{^isMap}} + // Object response for status {{code}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => {{baseType}}FromJSON(jsonValue){{/withoutRuntimeChecks}}); + {{/isMap}} + {{/isArray}} + {{/primitiveType}} + {{/isFile}} + {{/isBinary}} + {{/dataType}} + {{^dataType}} + // No body response for status {{code}} + return new runtime.VoidApiResponse(response); + {{/dataType}} + } + {{/responses}} + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: {{#responses}}{{code}}{{^-last}}, {{/-last}}{{/responses}}`); + {{^responses}} + // FALLBACK: No responses defined in OpenAPI spec - use default behavior for backward compatibility + // This maintains backward compatibility with specs that don't explicitly define all responses + {{#returnType}} + {{#isResponseFile}} + return new runtime.BlobApiResponse(response); + {{/isResponseFile}} + {{^isResponseFile}} + {{#returnTypeIsPrimitive}} + {{#isMap}} + return new runtime.JSONApiResponse(response); + {{/isMap}} + {{#isArray}} + return new runtime.JSONApiResponse(response); + {{/isArray}} + {{#returnSimpleType}} + if (this.isJsonMime(response.headers.get('content-type'))) { + return new runtime.JSONApiResponse<{{returnType}}>(response); + } else { + return new runtime.TextApiResponse(response) as any; + } + {{/returnSimpleType}} + {{/returnTypeIsPrimitive}} + {{^returnTypeIsPrimitive}} + {{#isArray}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => {{#uniqueItems}}new Set({{/uniqueItems}}jsonValue.map({{returnBaseType}}FromJSON){{/withoutRuntimeChecks}}){{#uniqueItems}}){{/uniqueItems}}; + {{/isArray}} + {{^isArray}} + {{#isMap}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => runtime.mapValues(jsonValue, {{returnBaseType}}FromJSON){{/withoutRuntimeChecks}}); + {{/isMap}} + {{^isMap}} + return new runtime.JSONApiResponse(response{{^withoutRuntimeChecks}}, (jsonValue) => {{returnBaseType}}FromJSON(jsonValue){{/withoutRuntimeChecks}}); + {{/isMap}} + {{/isArray}} + {{/returnTypeIsPrimitive}} + {{/isResponseFile}} + {{/returnType}} + {{^returnType}} + return new runtime.VoidApiResponse(response); + {{/returnType}} + {{/responses}} + } + + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + */ + {{^useSingleRequestParameter}} + async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{{#responses}}{{#dataType}}{{{dataType}}}{{/dataType}}{{^dataType}}void{{/dataType}}{{^-last}} | {{/-last}}{{/responses}}{{^responses}}{{{returnType}}}{{#returnType}}{{#isResponseOptional}} | null | undefined {{/isResponseOptional}}{{/returnType}}{{^returnType}}void{{/returnType}}{{/responses}}> { + {{#returnType}} + const response = await this.{{nickname}}Raw({{#allParams.0}}{ {{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}} }, {{/allParams.0}}initOverrides); + return await response.value(); + {{/returnType}} + {{^returnType}} + await this.{{nickname}}Raw({{#allParams.0}}{ {{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}} }, {{/allParams.0}}initOverrides); + {{/returnType}} + } + {{/useSingleRequestParameter}} + {{#useSingleRequestParameter}} + async {{nickname}}({{#allParams.0}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<{{#responses}}{{#dataType}}{{{dataType}}}{{/dataType}}{{^dataType}}void{{/dataType}}{{^-last}} | {{/-last}}{{/responses}}{{^responses}}{{{returnType}}}{{#returnType}}{{#isResponseOptional}} | null | undefined {{/isResponseOptional}}{{/returnType}}{{^returnType}}void{{/returnType}}{{/responses}}> { + {{#returnType}} + const response = await this.{{nickname}}Raw({{#allParams.0}}requestParameters, {{/allParams.0}}initOverrides); + {{#isResponseOptional}} + // CHANGED: Handle all status codes for optional responses + switch (response.raw.status) { + {{#responses}} + {{#is2xx}} + case {{code}}: + return {{#dataType}}await response.value(){{/dataType}}{{^dataType}}null{{/dataType}}; + {{/is2xx}} + {{/responses}} + default: + // For non-2xx responses, throw error to maintain backward compatibility + throw new runtime.ResponseError(response.raw, `Unexpected status code: ${response.raw.status}`); + } + {{/isResponseOptional}} + {{^isResponseOptional}} + return await response.value(); + {{/isResponseOptional}} + {{/returnType}} + {{^returnType}} + await this.{{nickname}}Raw({{#allParams.0}}requestParameters, {{/allParams.0}}initOverrides); + {{/returnType}} + } + {{/useSingleRequestParameter}} + + {{/operation}} +} +{{/operations}} +{{#hasEnums}} + +{{#operations}} +{{#operation}} +{{#allParams}} +{{#isEnum}} +{{#stringEnums}} +/** + * @export + * @enum {string} + */ +export enum {{operationIdCamelCase}}{{enumName}} { +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}} +{{^stringEnums}} +/** + * @export + */ +export const {{operationIdCamelCase}}{{enumName}} = { +{{#allowableValues}} + {{#enumVars}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; +export type {{operationIdCamelCase}}{{enumName}} = typeof {{operationIdCamelCase}}{{enumName}}[keyof typeof {{operationIdCamelCase}}{{enumName}}]; +{{/stringEnums}} +{{/isEnum}} +{{/allParams}} +{{/operation}} +{{/operations}} +{{/hasEnums}} diff --git a/openapi-template/gitignore b/openapi-template/gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/openapi-template/gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/openapi-template/index.mustache b/openapi-template/index.mustache new file mode 100644 index 0000000..cd7c8db --- /dev/null +++ b/openapi-template/index.mustache @@ -0,0 +1,17 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './runtime{{importFileExtension}}'; +{{#useSagaAndRecords}} +export * from './runtimeSagasAndRecords{{importFileExtension}}'; +export * from './ApiEntitiesRecord{{importFileExtension}}'; +export * from './ApiEntitiesReducer{{importFileExtension}}'; +export * from './ApiEntitiesSelectors{{importFileExtension}}'; +{{/useSagaAndRecords}} +{{#apiInfo}} +{{#apis.0}} +export * from './apis/index{{importFileExtension}}'; +{{/apis.0}} +{{/apiInfo}} +{{#models.0}} +export * from './models/index{{importFileExtension}}'; +{{/models.0}} diff --git a/openapi-template/licenseInfo.mustache b/openapi-template/licenseInfo.mustache new file mode 100644 index 0000000..be193d0 --- /dev/null +++ b/openapi-template/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/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/openapi-template/modelEnum.mustache b/openapi-template/modelEnum.mustache new file mode 100644 index 0000000..35e0303 --- /dev/null +++ b/openapi-template/modelEnum.mustache @@ -0,0 +1,28 @@ +{{>modelEnumInterfaces}} + +export function instanceOf{{classname}}(value: any): boolean { + for (const key in {{classname}}) { + if (Object.prototype.hasOwnProperty.call({{classname}}, key)) { + if ({{classname}}[key as keyof typeof {{classname}}] === value) { + return true; + } + } + } + return false; +} + +export function {{classname}}FromJSON(json: any): {{classname}} { + return {{classname}}FromJSONTyped(json, false); +} + +export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boolean): {{classname}} { + return json as {{classname}}; +} + +export function {{classname}}ToJSON(value?: {{classname}} | null): any { + return value as any; +} + +export function {{classname}}ToJSONTyped(value: any, ignoreDiscriminator: boolean): {{classname}} { + return value as {{classname}}; +} diff --git a/openapi-template/modelEnumInterfaces.mustache b/openapi-template/modelEnumInterfaces.mustache new file mode 100644 index 0000000..8d44b92 --- /dev/null +++ b/openapi-template/modelEnumInterfaces.mustache @@ -0,0 +1,37 @@ +{{#stringEnums}} +/** + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} + * @export + * @enum {string} + */ +export enum {{classname}} { +{{#allowableValues}} +{{#enumVars}} + {{#enumDescription}} + /** + * {{enumDescription}} + */ + {{/enumDescription}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} +{{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}}{{^stringEnums}} +/** + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} + * @export + */ +export const {{classname}} = { +{{#allowableValues}} +{{#enumVars}} + {{#enumDescription}} + /** + * {{enumDescription}} + */ + {{/enumDescription}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} +{{/enumVars}} +{{/allowableValues}} +} as const; +export type {{classname}} = typeof {{classname}}[keyof typeof {{classname}}]; +{{/stringEnums}} \ No newline at end of file diff --git a/openapi-template/modelGeneric.mustache b/openapi-template/modelGeneric.mustache new file mode 100644 index 0000000..16f2f43 --- /dev/null +++ b/openapi-template/modelGeneric.mustache @@ -0,0 +1,261 @@ +import { mapValues } from '../runtime{{importFileExtension}}'; +{{#hasImports}} +{{#tsImports}} +import type { {{{classname}}} } from './{{filename}}{{importFileExtension}}'; +import { + {{classname}}FromJSON, + {{classname}}FromJSONTyped, + {{classname}}ToJSON, + {{classname}}ToJSONTyped, +} from './{{filename}}{{importFileExtension}}'; +{{/tsImports}} + +{{/hasImports}} +{{#discriminator}} +{{#discriminator.mappedModels}} +import { type {{modelName}}, {{modelName}}FromJSONTyped, {{modelName}}ToJSON, {{modelName}}ToJSONTyped } from './{{modelName}}{{importFileExtension}}'; +{{/discriminator.mappedModels}} +{{/discriminator}} +{{>modelGenericInterfaces}} + + +/** + * Check if a given object implements the {{classname}} interface. + */ +export function instanceOf{{classname}}(value: object): value is {{classname}} { + {{#vars}} + {{#required}} + if (!('{{name}}' in value) || value['{{name}}'] === undefined) return false; + {{/required}} + {{/vars}} + return true; +} + +export function {{classname}}FromJSON(json: any): {{classname}} { + return {{classname}}FromJSONTyped(json, false); +} + +export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boolean): {{classname}} { + {{#hasVars}} + if (json == null) { + return json; + } +{{#discriminator}} + if (!ignoreDiscriminator) { +{{#discriminator.mappedModels}} + if (json['{{discriminator.propertyBaseName}}'] === '{{mappingName}}') { + return {{modelName}}FromJSONTyped(json, ignoreDiscriminator); + } +{{/discriminator.mappedModels}} + {{#hasSelfReferencingDiscriminatorMapping}} + if (json['{{discriminator.propertyBaseName}}'] === '{{selfReferencingDiscriminatorMapping.mappingName}}') { + return {{selfReferencingDiscriminatorMapping.modelName}}FromJSONTyped(json, true); + } + {{/hasSelfReferencingDiscriminatorMapping}} + + } +{{/discriminator}} + return { + {{#parent}}...{{{.}}}FromJSONTyped(json, true),{{/parent}} + {{#additionalPropertiesType}} + ...json, + {{/additionalPropertiesType}} + {{#vars}} + {{#isPrimitiveType}} + {{#isArray}} + {{#uniqueItems}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}{{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Set(json['{{baseName}}']), + {{/uniqueItems}} + {{^uniqueItems}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}{{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}json['{{baseName}}'], + {{/uniqueItems}} + {{/isArray}} + {{^isArray}} + {{#isDateType}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])), + {{/isDateType}} + {{#isDateTimeType}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])), + {{/isDateTimeType}} + {{^isDateType}} + {{^isDateTimeType}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}json['{{baseName}}'], + {{/isDateTimeType}} + {{/isDateType}} + {{/isArray}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + {{#uniqueItems}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Set((json['{{baseName}}'] as Array).map({{#items}}{{datatype}}{{/items}}FromJSON))), + {{/uniqueItems}} + {{^uniqueItems}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}(json['{{baseName}}'] as Array).map({{#items}}{{datatype}}{{/items}}FromJSON)), + {{/uniqueItems}} + {{/isArray}} + {{#isMap}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}mapValues(json['{{baseName}}'], {{#items}}{{datatype}}{{/items}}FromJSON)), + {{/isMap}} + {{^isArray}} + {{^isMap}} + {{^isFreeFormObject}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}{{datatype}}FromJSON(json['{{baseName}}']), + {{/isFreeFormObject}} + {{#isFreeFormObject}} + '{{name}}': {{^required}}json['{{baseName}}'] == null ? undefined : {{/required}}json['{{baseName}}'], + {{/isFreeFormObject}} + {{/isMap}} + {{/isArray}} + {{/isPrimitiveType}} + {{/vars}} + }; + {{/hasVars}} + {{^hasVars}} + return json; + {{/hasVars}} +} + +export function {{classname}}ToJSON(json: any): {{classname}} { + return {{classname}}ToJSONTyped(json, false); +} + +export function {{classname}}ToJSONTyped(value?: {{#hasReadOnly}}Omit<{{classname}}, {{#readOnlyVars}}'{{baseName}}'{{^-last}}|{{/-last}}{{/readOnlyVars}}>{{/hasReadOnly}}{{^hasReadOnly}}{{classname}}{{/hasReadOnly}} | null, ignoreDiscriminator: boolean = false): any { + {{#hasVars}} + if (value == null) { + return value; + } + {{#discriminator}} + + if (!ignoreDiscriminator) { + switch (value['{{discriminator.propertyName}}']) { + {{#discriminator.mappedModels}} + case '{{mappingName}}': + return {{modelName}}ToJSONTyped(value as {{modelName}}, ignoreDiscriminator); + {{/discriminator.mappedModels}} + default: + return value; + } + } + {{/discriminator}} + + return { + {{#parent}}...{{{.}}}ToJSONTyped(value, true),{{/parent}} + {{#additionalPropertiesType}} + ...value, + {{/additionalPropertiesType}} + {{#vars}} + {{^isReadOnly}} + {{#isPrimitiveType}} + {{#isDateType}} + '{{baseName}}': {{^required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{#isNullable}}{{#required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{/isNullable}}value['{{name}}'].toISOString().substring(0,10), + {{/isDateType}} + {{#isDateTimeType}} + '{{baseName}}': {{^required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{#isNullable}}{{#required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{/isNullable}}value['{{name}}'].toISOString(), + {{/isDateTimeType}} + {{#isArray}} + '{{baseName}}': {{#uniqueItems}}{{^required}}value['{{name}}'] == null ? undefined : {{/required}}{{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}Array.from(value['{{name}}'] as Set){{/uniqueItems}}{{^uniqueItems}}value['{{name}}']{{/uniqueItems}}, + {{/isArray}} + {{^isDateType}} + {{^isDateTimeType}} + {{^isArray}} + '{{baseName}}': value['{{name}}'], + {{/isArray}} + {{/isDateTimeType}} + {{/isDateType}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + {{#uniqueItems}} + '{{baseName}}': {{^required}}value['{{name}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}Array.from(value['{{name}}'] as Set).map({{#items}}{{datatype}}{{/items}}ToJSON)), + {{/uniqueItems}} + {{^uniqueItems}} + '{{baseName}}': {{^required}}value['{{name}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}(value['{{name}}'] as Array).map({{#items}}{{datatype}}{{/items}}ToJSON)), + {{/uniqueItems}} + {{/isArray}} + {{#isMap}} + '{{baseName}}': {{^required}}value['{{name}}'] == null ? undefined : {{/required}}({{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}mapValues(value['{{name}}'], {{#items}}{{datatype}}{{/items}}ToJSON)), + {{/isMap}} + {{^isArray}} + {{^isMap}} + {{^isFreeFormObject}} + '{{baseName}}': {{datatype}}ToJSON(value['{{name}}']), + {{/isFreeFormObject}} + {{#isFreeFormObject}} + '{{baseName}}': value['{{name}}'], + {{/isFreeFormObject}} + {{/isMap}} + {{/isArray}} + {{/isPrimitiveType}} + {{/isReadOnly}} + {{/vars}} + }; + {{/hasVars}} + {{^hasVars}} + return value; + {{/hasVars}} +} +{{#validationAttributes}} + +export const {{classname}}PropertyValidationAttributesMap: { + [property: string]: { + maxLength?: number, + minLength?: number, + pattern?: string, + maximum?: number, + exclusiveMaximum?: boolean, + minimum?: number, + exclusiveMinimum?: boolean, + multipleOf?: number, + maxItems?: number, + minItems?: number, + uniqueItems?: boolean + } +} = { +{{#vars}} +{{#hasValidation}} + {{name}}: { + {{#maxLength}} + maxLength: {{maxLength}}, + {{/maxLength}} + {{#minLength}} + minLength: {{minLength}}, + {{/minLength}} + {{#pattern}} + pattern: '{{pattern}}', + {{/pattern}} + {{#maximum}} + maximum: {{maximum}}, + exclusiveMaximum: {{exclusiveMaximum}}, + {{/maximum}} + {{#minimum}} + minimum: {{minimum}}, + exclusiveMinimum: {{exclusiveMinimum}}, + {{/minimum}} + {{#multipleOf}} + multipleOf: {{multipleOf}}, + {{/multipleOf}} + {{#maxItems}} + maxItems: {{maxItems}}, + {{/maxItems}} + {{#minItems}} + minItems: {{minItems}}, + {{/minItems}} + {{#isArray}} + uniqueItems: {{uniqueItems}}, + {{/isArray}} + }, +{{/hasValidation}} +{{/vars}} +} +{{#isAdditionalPropertiesTrue}} + +export const {{classname}}AdditionalPropertiesValidationAttributes: { maxProperties?: number, minProperties?: number } = { + {{#maxProperties}} + maxProperties: {{maxProperties}}, + {{/maxProperties}} + {{#minProperties}} + minProperties: {{minProperties}}, + {{/minProperties}} +} +{{/isAdditionalPropertiesTrue}} +{{/validationAttributes}} diff --git a/openapi-template/modelGenericInterfaces.mustache b/openapi-template/modelGenericInterfaces.mustache new file mode 100644 index 0000000..68e5747 --- /dev/null +++ b/openapi-template/modelGenericInterfaces.mustache @@ -0,0 +1,50 @@ +/** + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} + * @export + * @interface {{classname}} + */ +export interface {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ +{{#additionalPropertiesType}} + [key: string]: {{{additionalPropertiesType}}}{{#hasVars}} | any{{/hasVars}}; +{{/additionalPropertiesType}} +{{#vars}} + /** + * {{#lambda.indented_star_4}}{{{unescapedDescription}}}{{/lambda.indented_star_4}} + * @type {{=<% %>=}}{<%&datatype%>}<%={{ }}=%> + * @memberof {{classname}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ + {{#isReadOnly}}readonly {{/isReadOnly}}{{name}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}; +{{/vars}} +}{{#hasEnums}} + +{{#vars}} +{{#isEnum}} +{{#stringEnums}} +/** +* @export +* @enum {string} +*/ +export enum {{classname}}{{enumName}} { +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}}{{^stringEnums}} +/** + * @export + */ +export const {{classname}}{{enumName}} = { +{{#allowableValues}} + {{#enumVars}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; +export type {{classname}}{{enumName}} = typeof {{classname}}{{enumName}}[keyof typeof {{classname}}{{enumName}}]; +{{/stringEnums}} +{{/isEnum}}{{/vars}}{{/hasEnums}} \ No newline at end of file diff --git a/openapi-template/modelOneOf.mustache b/openapi-template/modelOneOf.mustache new file mode 100644 index 0000000..d9a828e --- /dev/null +++ b/openapi-template/modelOneOf.mustache @@ -0,0 +1,255 @@ +{{#hasImports}} +{{#oneOfArrays}} +import type { {{{.}}} } from './{{.}}{{importFileExtension}}'; +import { + instanceOf{{{.}}}, + {{{.}}}FromJSON, + {{{.}}}FromJSONTyped, + {{{.}}}ToJSON, +} from './{{.}}{{importFileExtension}}'; +{{/oneOfArrays}} +{{#oneOfModels}} +import type { {{{.}}} } from './{{.}}{{importFileExtension}}'; +import { + instanceOf{{{.}}}, + {{{.}}}FromJSON, + {{{.}}}FromJSONTyped, + {{{.}}}ToJSON, +} from './{{.}}{{importFileExtension}}'; +{{/oneOfModels}} + +{{/hasImports}} +{{>modelOneOfInterfaces}} + + +export function {{classname}}FromJSON(json: any): {{classname}} { + return {{classname}}FromJSONTyped(json, false); +} + +export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boolean): {{classname}} { + if (json == null) { + return json; + } +{{#discriminator}} + switch (json['{{discriminator.propertyBaseName}}']) { +{{#discriminator.mappedModels}} + case '{{mappingName}}': + return Object.assign({}, {{modelName}}FromJSONTyped(json, true), { {{discriminator.propertyName}}: '{{mappingName}}' } as const); +{{/discriminator.mappedModels}} + default: + return json; + } +{{/discriminator}} +{{^discriminator}} + {{#oneOfModels}} + {{#-first}} + if (typeof json !== 'object') { + return json; + } + {{/-first}} + if (instanceOf{{{.}}}(json)) { + return {{{.}}}FromJSONTyped(json, true); + } + {{/oneOfModels}} + {{#oneOfArrays}} + {{#-first}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'object')) { + {{/-first}} + if (json.every(item => instanceOf{{{.}}}(item))) { + return json.map(value => {{{.}}}FromJSONTyped(value, true)); + } + {{#-last}} + } + return json; + } + {{/-last}} + {{/oneOfArrays}} + {{#oneOfPrimitives}} + {{#isArray}} + {{#items}} + {{#isDateType}} + if (Array.isArray(json)) { + if (json.every(item => !(isNaN(new Date(json).getTime()))) { + return json.map(value => new Date(json); + } + } + {{/isDateType}} + {{#isDateTimeType}} + if (Array.isArray(json)) { + if (json.every(item => !(isNaN(new Date(json).getTime()))) { + return json.map(value => new Date(json); + } + } + {{/isDateTimeType}} + {{#isNumeric}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return json; + } + } + {{/isNumeric}} + {{#isBoolean}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'boolean'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return json; + } + } + {{/isBoolean}} + {{#isString}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return json; + } + } + {{/isString}} + {{/items}} + {{/isArray}} + {{^isArray}} + {{#isDateType}} + if (!(isNaN(new Date(json).getTime()))) { + return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json)); + } + {{/isDateType}} + {{^isDateType}} + {{#isDateTimeType}} + if (!(isNaN(new Date(json).getTime()))) { + return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json)); + } + {{/isDateTimeType}} + {{/isDateType}} + {{#isNumeric}} + if (typeof json === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return json; + } + {{/isNumeric}} + {{#isBoolean}} + if (typeof json === 'boolean'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return json; + } + {{/isBoolean}} + {{#isString}} + if (typeof json === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return json; + } + {{/isString}} + {{/isArray}} + {{/oneOfPrimitives}} + return {} as any; +{{/discriminator}} +} + +export function {{classname}}ToJSON(json: any): any { + return {{classname}}ToJSONTyped(json, false); +} + +export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } +{{#discriminator}} + switch (value['{{discriminator.propertyName}}']) { +{{#discriminator.mappedModels}} + case '{{mappingName}}': + return Object.assign({}, {{modelName}}ToJSON(value), { {{discriminator.propertyName}}: '{{mappingName}}' } as const); +{{/discriminator.mappedModels}} + default: + return value; + } +{{/discriminator}} +{{^discriminator}} + {{#oneOfModels}} + {{#-first}} + if (typeof value !== 'object') { + return value; + } + {{/-first}} + if (instanceOf{{{.}}}(value)) { + return {{{.}}}ToJSON(value as {{{.}}}); + } + {{/oneOfModels}} + {{#oneOfArrays}} + {{#-first}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'object')) { + {{/-first}} + if (value.every(item => instanceOf{{{.}}}(item))) { + return value.map(value => {{{.}}}ToJSON(value as {{{.}}})); + } + {{#-last}} + } + return value; + } + {{/-last}} + {{/oneOfArrays}} + {{#oneOfPrimitives}} + {{#isArray}} + {{#items}} + {{#isDateType}} + if (Array.isArray(value)) { + if (value.every(item => item instanceof Date) { + return value.map(value => value.toISOString().substring(0,10))); + } + } + {{/isDateType}} + {{#isDateTimeType}} + if (Array.isArray(value)) { + if (value.every(item => item instanceof Date) { + return value.map(value => value.toISOString(); + } + } + {{/isDateTimeType}} + {{#isNumeric}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return value; + } + } + {{/isNumeric}} + {{#isBoolean}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'boolean'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return value; + } + } + {{/isBoolean}} + {{#isString}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return value; + } + } + {{/isString}} + {{/items}} + {{/isArray}} + {{^isArray}} + {{#isDateType}} + if (value instanceof Date) { + return ((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString().substring(0,10)); + } + {{/isDateType}} + {{#isDateTimeType}} + if (value instanceof Date) { + return {{^required}}{{#isNullable}}value === null ? null : {{/isNullable}}{{^isNullable}}value == null ? undefined : {{/isNullable}}{{/required}}((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString()); + } + {{/isDateTimeType}} + {{#isNumeric}} + if (typeof value === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + {{/isNumeric}} + {{#isBoolean}} + if (typeof value === 'boolean'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + {{/isBoolean}} + {{#isString}} + if (typeof value === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + {{/isString}} + {{/isArray}} + {{/oneOfPrimitives}} + return {}; +{{/discriminator}} +} diff --git a/openapi-template/modelOneOfInterfaces.mustache b/openapi-template/modelOneOfInterfaces.mustache new file mode 100644 index 0000000..ee4d1fc --- /dev/null +++ b/openapi-template/modelOneOfInterfaces.mustache @@ -0,0 +1,6 @@ +/** + * @type {{classname}} + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} + * @export + */ +export type {{classname}} = {{#discriminator}}{{#mappedModels}}{ {{discriminator.propertyName}}: '{{mappingName}}' } & {{modelName}}{{^-last}} | {{/-last}}{{/mappedModels}}{{/discriminator}}{{^discriminator}}{{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}}{{/discriminator}}; \ No newline at end of file diff --git a/openapi-template/model_doc.mustache b/openapi-template/model_doc.mustache new file mode 100644 index 0000000..b2f77d5 --- /dev/null +++ b/openapi-template/model_doc.mustache @@ -0,0 +1,41 @@ +{{#models}}{{#model}} +# {{classname}} + +{{#description}}{{&description}} +{{/description}} + +## Properties + +Name | Type +------------ | ------------- +{{#vars}}`{{name}}` | {{#isPrimitiveType}}{{dataType}}{{/isPrimitiveType}}{{^isPrimitiveType}}[{{dataType}}]({{complexType}}.md){{/isPrimitiveType}} +{{/vars}} + +{{^withoutRuntimeChecks}} +## Example + +```typescript +import type { {{classname}} } from '{{npmName}}' + +// TODO: Update the object below with actual values +const example = { +{{#vars}} + "{{name}}": {{{example}}}, +{{/vars}} +} satisfies {{classname}} + +console.log(example) + +// Convert the instance to a JSON string +const exampleJSON: string = JSON.stringify(example) +console.log(exampleJSON) + +// Parse the JSON string back to an object +const exampleParsed = JSON.parse(exampleJSON) as {{classname}} +console.log(exampleParsed) +``` +{{/withoutRuntimeChecks}} + +[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/openapi-template/models.index.mustache b/openapi-template/models.index.mustache new file mode 100644 index 0000000..e1c7d93 --- /dev/null +++ b/openapi-template/models.index.mustache @@ -0,0 +1,32 @@ +/* tslint:disable */ +/* eslint-disable */ +{{#models}} +{{#model}} +{{^withoutRuntimeChecks}} +export * from './{{{ classFilename }}}{{importFileExtension}}'; +{{#useSagaAndRecords}} +{{^isEnum}} +export * from './{{{ classFilename }}}Record{{importFileExtension}}'; +{{/isEnum}} +{{/useSagaAndRecords}} +{{/withoutRuntimeChecks}} +{{#withoutRuntimeChecks}} +{{#isEnum}} +{{>modelEnumInterfaces}} + +{{/isEnum}} +{{^isEnum}} +{{#oneOf}} +{{#-first}} +{{>modelOneOfInterfaces}} + +{{/-first}} +{{/oneOf}} +{{^oneOf}} +{{>modelGenericInterfaces}} + +{{/oneOf}} +{{/isEnum}} +{{/withoutRuntimeChecks}} +{{/model}} +{{/models}} diff --git a/openapi-template/models.mustache b/openapi-template/models.mustache new file mode 100644 index 0000000..7926c46 --- /dev/null +++ b/openapi-template/models.mustache @@ -0,0 +1,24 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +{{#models}} +{{#model}} +{{#isEnum}} +{{>modelEnum}} + +{{/isEnum}} +{{^isEnum}} +{{#oneOf}} +{{#-first}} +{{>modelOneOf}} + +{{/-first}} +{{/oneOf}} +{{^oneOf}} +{{>modelGeneric}} + +{{/oneOf}} +{{/isEnum}} +{{/model}} +{{/models}} diff --git a/openapi-template/npmignore.mustache b/openapi-template/npmignore.mustache new file mode 100644 index 0000000..42061c0 --- /dev/null +++ b/openapi-template/npmignore.mustache @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/openapi-template/package.mustache b/openapi-template/package.mustache new file mode 100644 index 0000000..0de88d4 --- /dev/null +++ b/openapi-template/package.mustache @@ -0,0 +1,43 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "author": "OpenAPI-Generator", + "repository": { + "type": "git", + "url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git" + }, +{{#licenseName}} + "license": "{{licenseName}}", +{{/licenseName}} +{{#packageAsSourceOnlyLibrary}} + "main": "./index.ts", +{{/packageAsSourceOnlyLibrary}} +{{^packageAsSourceOnlyLibrary}} + "main": "./dist/index.js", + "types": "./dist/index.d.ts", +{{#supportsES6}} + "module": "./dist/esm/index.js", + "sideEffects": false, +{{/supportsES6}} + "scripts": { + "build": "tsc{{#supportsES6}} && tsc -p tsconfig.esm.json{{/supportsES6}}"{{^sagasAndRecords}}, + "prepare": "npm run build"{{/sagasAndRecords}} + }, +{{/packageAsSourceOnlyLibrary}} + "devDependencies": { +{{#sagasAndRecords}} + "immutable": "^4.0.0-rc.12", + "normalizr": "^3.6.1", + "redux-saga": "^1.1.3", + "redux-ts-simple": "^3.2.0", + "reselect": "^4.0.0", +{{/sagasAndRecords}} + "typescript": "^4.0 || ^5.0" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig": { + "registry": "{{npmRepository}}" + } +{{/npmRepository}} +} diff --git a/openapi-template/recordGeneric.mustache b/openapi-template/recordGeneric.mustache new file mode 100644 index 0000000..819a29e --- /dev/null +++ b/openapi-template/recordGeneric.mustache @@ -0,0 +1,295 @@ +import {ApiRecordUtils, knownRecordFactories{{#returnPassthrough}}, appFromJS, NormalizedRecordEntities{{/returnPassthrough}}} from "../runtimeSagasAndRecords{{importFileExtension}}"; +import {getApiEntitiesState} from "../ApiEntitiesSelectors{{importFileExtension}}" +import {List, Record, RecordOf, Map} from 'immutable'; +import {Schema, schema, NormalizedSchema} from "normalizr"; +import {select, call} from "redux-saga/effects"; + +import { + {{classname}}, +{{#vars}} +{{#isEnum}} + {{classname}}{{enumName}}, +{{/isEnum}} +{{/vars}} +} from './{{classname}}{{importFileExtension}}'; + +{{#imports}} +import { + {{{.}}}, +} from './{{{.}}}{{importFileExtension}}'; +{{/imports}} + +{{#modelImports}} +import { + {{{.}}}Record, + {{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}RecordUtils +} from './{{{.}}}Record{{importFileExtension}}'; +{{/modelImports}} + +export const {{classname}}RecordProps = { + recType: "{{classname}}ApiRecord" as "{{classname}}ApiRecord", +{{#vars}} + {{#isArray}} + {{#items.isModel}} + {{#keepAsJSObject}} + {{name}}: {{#required}}new {{{dataType}}}(){{/required}}{{^required}}null as {{{dataType}}} | null{{/required}}, + {{/keepAsJSObject}} + {{^keepAsJSObject}} + {{name}}: ({{{items.dataType}}}Record(), {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null as {{{dataTypeAlternate}}} | null{{/required}}), + {{/keepAsJSObject}} + {{/items.isModel}} + {{^items.isModel}} + {{name}}: {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null as {{{dataTypeAlternate}}} | null{{/required}}, + {{/items.isModel}} + {{/isArray}} + {{#isModel}} + {{#keepAsJSObject}} + {{name}}: {{#required}}{} as any as {{{dataType}}}{{/required}}{{^required}}null as {{{dataType}}} | null{{/required}}, + {{/keepAsJSObject}} + {{^keepAsJSObject}} + {{name}}: {{#required}}{{{defaultValue}}}{{/required}}{{^required}}({{{defaultValue}}}, null as {{{dataTypeAlternate}}} | null){{/required}}, + {{/keepAsJSObject}} + {{/isModel}} + {{^isArray}} + {{^isModel}} + {{name}}: {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null as {{{dataTypeAlternate}}} | null{{/required}}, + {{/isModel}} + {{/isArray}} +{{/vars}} +}; + +export type {{classname}}RecordPropsType = typeof {{classname}}RecordProps; +export const {{classname}}Record = Record({{classname}}RecordProps, {{classname}}RecordProps.recType); +export type {{classname}}Record = RecordOf<{{classname}}RecordPropsType>; + +knownRecordFactories.set({{classname}}RecordProps.recType, {{classname}}Record); + +{{#isEntity}} +export const {{classname}}RecordEntityProps = { + ...{{classname}}RecordProps, + recType: "{{classname}}ApiRecordEntity" as "{{classname}}ApiRecordEntity", +{{#vars}} + {{#isEntity}} + {{^keepAsJSObject}} + {{name}}: {{#required}}"-1"{{/required}}{{^required}}null as string | null{{/required}}, + {{/keepAsJSObject}} + {{/isEntity}} + {{#isArray}} + {{#items.isEntity}} + {{^keepAsJSObject}} + {{name}}: {{#required}}List(){{/required}}{{^required}}null as List | null{{/required}}, + {{/keepAsJSObject}} + {{/items.isEntity}} + {{/isArray}} +{{/vars}} +}; + +export type {{classname}}RecordEntityPropsType = typeof {{classname}}RecordEntityProps; +export const {{classname}}RecordEntity = Record({{classname}}RecordEntityProps, {{classname}}RecordEntityProps.recType); +export type {{classname}}RecordEntity = RecordOf<{{classname}}RecordEntityPropsType>; + +knownRecordFactories.set({{classname}}RecordEntityProps.recType, {{classname}}RecordEntity); +{{/isEntity}} + +class {{classname}}RecordUtils extends ApiRecordUtils<{{classname}}, {{classname}}Record> { + public normalize(apiObject: {{classname}}, asEntity?: boolean): {{classname}} { + (apiObject as any).recType = {{#isEntity}}asEntity ? {{classname}}RecordEntityProps.recType : {{/isEntity}}{{classname}}RecordProps.recType; +{{#vars}} + {{#isUniqueId}} + {{#isArray}} + {{#items.isArray}} + {{^required}}if (apiObject['{{name}}']) { {{/required}}(apiObject as any)['{{name}}'] = apiObject['{{name}}'].map(item => item.map(item2 => item2?.toString()));{{^required}} } {{/required}} + {{/items.isArray}} + {{^items.isArray}} + {{^required}}if (apiObject['{{name}}']) { {{/required}}(apiObject as any)['{{name}}'] = apiObject['{{name}}'].map(item => item?.toString());{{^required}} } {{/required}} + {{/items.isArray}} + {{/isArray}} + {{^isArray}} + {{^required}}if (apiObject['{{name}}']) { {{/required}}(apiObject as any)['{{name}}'] = apiObject['{{name}}'].toString();{{^required}} } {{/required}} + {{/isArray}} + {{/isUniqueId}} + {{^keepAsJSObject}} + {{#isModel}} + {{^required}}if (apiObject['{{name}}']) { {{/required}}{{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.normalize(apiObject['{{name}}']);{{^required}} } {{/required}} + {{/isModel}} + {{#isArray}} + {{#items.isModel}} + {{^required}}if (apiObject['{{name}}']) { {{/required}}{{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.normalizeArray(apiObject['{{name}}']);{{^required}} } {{/required}} + {{/items.isModel}} + {{/isArray}} + {{/keepAsJSObject}} +{{/vars}} + return apiObject; + } +{{#isEntity}} + + public getSchema(): Schema { + return new schema.Entity("{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}", { +{{#vars}} + {{#isEntity}} + {{^keepAsJSObject}} + {{name}}: {{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.getSchema(), + {{/keepAsJSObject}} + {{/isEntity}} + {{#isArray}} + {{#items.isEntity}} + {{^keepAsJSObject}} + {{name}}: [{{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.getSchema()], + {{/keepAsJSObject}} + {{/items.isEntity}} + {{/isArray}} +{{/vars}} + }); + } + + public *toInlined(entityId?: string | null) { + if (!entityId) {return undefined; } + // @ts-ignore + const entity = yield select(apiEntity{{classname}}Selector, {id: entityId}); + if (!entity) {return undefined; } + + const { + recType, +{{#vars}} +{{#isEntity}} +{{^keepAsJSObject}} + {{name}}: {{name}}_original, +{{/keepAsJSObject}} +{{/isEntity}} +{{#isArray}} +{{#items.isEntity}} +{{^keepAsJSObject}} + {{name}}: {{name}}_original, +{{/keepAsJSObject}} +{{/items.isEntity}} +{{/isArray}} +{{/vars}} + ...unchangedProperties + } = entity; + + const entityProperties = { +{{#vars}} +{{#isEntity}} +{{^keepAsJSObject}} + // @ts-ignore + {{name}}: {{^required}}entity['{{name}}'] ? {{/required}}yield call({{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.toInlined, entity['{{name}}']){{^required}} : null{{/required}}, +{{/keepAsJSObject}} +{{/isEntity}} +{{#isArray}} +{{#items.isEntity}} +{{^keepAsJSObject}} + // @ts-ignore + {{name}}: {{^required}}entity['{{name}}'] ? {{/required}}yield call({{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.toInlinedArray, entity['{{name}}']){{^required}} : null{{/required}}, +{{/keepAsJSObject}} +{{/items.isEntity}} +{{/isArray}} +{{/vars}} + } + + return {{classname}}Record({ + ...unchangedProperties, + ...entityProperties + }); + } + + public *toInlinedArray(entityIds: List | null) { + if (!entityIds) {return null; } + let entities = List<{{classname}}Record>(); + for (let entityIndex = 0; entityIndex < entityIds.count(); entityIndex++) { + // @ts-ignore + const entity = yield call(this.toInlined, entityIds.get(entityIndex)); + if (entity) { + entities.push(entity); + } + } + return entities; + } +{{/isEntity}} + + public toApi(record: {{classname}}Record): {{classname}} { + const apiObject = super.toApi(record); +{{#vars}} + {{#isUniqueId}} + {{#isArray}} + {{#items.isArray}} + {{^required}}if (record['{{name}}']) { {{/required}}apiObject['{{name}}'] = {{#isArray}}record['{{name}}'].map(item => item.toArray().map(item2 => (item2 ? parseFloat(item2) : null) as number)).toArray(){{/isArray}};{{^required}} } {{/required}} + {{/items.isArray}} + {{^items.isArray}} + {{^required}}if (record['{{name}}']) { {{/required}}apiObject['{{name}}'] = {{#isArray}}record['{{name}}'].map(item => (item ? parseFloat(item) : null) as number).toArray(){{/isArray}};{{^required}} } {{/required}} + {{/items.isArray}} + {{/isArray}} + {{^isArray}} + {{^required}}if (record['{{name}}']) { {{/required}}apiObject['{{name}}'] = {{^isArray}}parseFloat(record['{{name}}']){{/isArray}};{{^required}} } {{/required}} + {{/isArray}} + {{/isUniqueId}} + {{^keepAsJSObject}} + {{#isModel}} + {{^required}}if (record['{{name}}']) { {{/required}}apiObject['{{name}}'] = {{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.toApi(record['{{name}}']);{{^required}} } {{/required}} + {{/isModel}} + {{#isArray}} + {{#items.isModel}} + {{^required}}if (record['{{name}}']) { {{/required}}apiObject['{{name}}'] = {{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.toApiArray(record['{{name}}']);{{^required}} } {{/required}} + {{/items.isModel}} + {{/isArray}} + {{/keepAsJSObject}} +{{/vars}} + return apiObject; + } +{{#returnPassthrough}} +{{#vars.1}} + + public fromApiPassthrough(apiObject: {{classname}}): {{{dataTypeAlternate}}} { + {{#isModel}} + if (!apiObject.{{{returnPassthrough}}}) {return {{{defaultValue}}}; } + const normalizedApiObject = {{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.normalize(apiObject.{{{returnPassthrough}}}); + return appFromJS(normalizedApiObject); + {{/isModel}} + {{#isArray}} + {{#items.isModel}} + if (!apiObject.{{{returnPassthrough}}}) {return {{{defaultValue}}}; } + const normalizedApiObject = {{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.normalizeArray(apiObject.{{{returnPassthrough}}}); + return appFromJS(normalizedApiObject); + {{/items.isModel}} + {{^items.isModel}} + return appFromJS(apiObject.{{{returnPassthrough}}}); + {{/items.isModel}} + {{/isArray}} + {{^isModel}} + {{^isArray}} + return apiObject.{{{returnPassthrough}}}!; + {{/isArray}} + {{/isModel}} + } + + public fromApiPassthroughAsEntities(apiObject: {{classname}}): NormalizedRecordEntities { + {{#isEntity}} + if (!apiObject.{{{returnPassthrough}}}) {return {entities: {}, result: List()}; } + return ApiRecordUtils.toNormalizedRecordEntities({{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.normalizeArrayAsEntities([apiObject.{{{returnPassthrough}}}]), true); + {{/isEntity}} + {{#isArray}} + {{#items.isEntity}} + if (!apiObject.{{{returnPassthrough}}}) {return {entities: {}, result: List()}; } + return ApiRecordUtils.toNormalizedRecordEntities({{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.normalizeArrayAsEntities(apiObject.{{{returnPassthrough}}}), true); + {{/items.isEntity}} + {{^items.isEntity}} + console.log("entities revival not supported on this response"); + return {entities: {}, result: List()}; + {{/items.isEntity}} + {{/isArray}} + {{^isEntity}} + {{^isArray}} + console.log("entities revival not supported on this response"); + return {entities: {}, result: List()}; + {{/isArray}} + {{/isEntity}} + } +{{/vars.1}} +{{/returnPassthrough}} +} + +export const {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}RecordUtils = new {{classname}}RecordUtils(); + +{{#isEntity}} +export const apiEntities{{classname}}Selector = (state: any) => getApiEntitiesState(state).{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}} as Map; +export const apiEntity{{classname}}Selector = (state: any, {id}: {id?: string | null}) => id ? apiEntities{{classname}}Selector(state).get(id) : undefined; +{{/isEntity}} diff --git a/openapi-template/records.mustache b/openapi-template/records.mustache new file mode 100644 index 0000000..c737941 --- /dev/null +++ b/openapi-template/records.mustache @@ -0,0 +1,17 @@ +{{#models}} +{{#model}} +{{#isEnum}} +// This file is not needed and was generated only because of how codegen is built... Enums do not need to be converted to Records and can be used directly. +{{/isEnum}} +{{^isEnum}} +{{^oneOf}} +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +{{>recordGeneric}} + +{{/oneOf}} +{{/isEnum}} +{{/model}} +{{/models}} diff --git a/openapi-template/runtime.mustache b/openapi-template/runtime.mustache new file mode 100644 index 0000000..d4ee98d --- /dev/null +++ b/openapi-template/runtime.mustache @@ -0,0 +1,426 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +export const BASE_PATH = "{{{basePath}}}".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string; // parameter for basic security + password?: string; // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): string | undefined { + return this.configuration.username; + } + + get password(): string | undefined { + return this.configuration.password; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + // CHANGED: Removed status code check to allow handling of all HTTP status codes (not just 200-299) + // Previously, this method would throw ResponseError for any status code outside 200-299. + // Now, all responses are returned regardless of status code, allowing the API methods + // to handle and return typed responses for all status codes defined in the OpenAPI spec. + // This enables typed access to error responses (4xx, 5xx) and other status codes. + return response; + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overriddenInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: any; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +}; + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name: "ResponseError" = "ResponseError"; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name: "FetchError" = "FetchError"; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = any; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} + +{{^withoutRuntimeChecks}} +export function mapValues(data: any, fn: (item: any) => any) { + const result: { [key: string]: any } = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} +{{/withoutRuntimeChecks}} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: any): T; +} + +export class JSONApiResponse { + constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) {} + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/openapi-template/runtimeSagasAndRecords.mustache b/openapi-template/runtimeSagasAndRecords.mustache new file mode 100644 index 0000000..5c6c927 --- /dev/null +++ b/openapi-template/runtimeSagasAndRecords.mustache @@ -0,0 +1,120 @@ +/* tslint:disable */ +/* eslint-disable */ + +import {fromJS as originalFromJS, isIndexed, List, Map as ImmMap, RecordOf} from 'immutable'; +import {normalize, NormalizedSchema, schema, Schema} from "normalizr"; +import {ActionDefinition, createAction} from "redux-ts-simple"; + +export const knownRecordFactories = new Map(); +export const knownIndexedSetByKey: (string | number)[] = []; + +export function appFromJS(any: any): any { + return originalFromJS(any, (key, value) => { + if (isIndexed(value)) { + return knownIndexedSetByKey.indexOf(key) !== -1 ? value.toSet() : value.toList(); + } // we're reviving an array -> it's a List + const MatchingType = knownRecordFactories.get(value.get('recType') as string) as { new(input?: any): any }; // check if we know a Record with this type + if (MatchingType) { + return new MatchingType(value); + } + return value.toMap(); // no matching Record type found -> it's a plain old Map + }); +} + +export type NormalizedRecordEntity = NormalizedSchema<{ [key: string]: Map> }, string>; +export type NormalizedRecordEntities = NormalizedSchema<{ [key: string]: Map> }, List>; + +export abstract class ApiRecordUtils> { + public abstract normalize(apiObject: TAPI, asEntity?: boolean): any; + + public getSchema(): Schema { + console.log("Entity mode not supported on this record."); + return new schema.Entity("entityNotSupported"); + } + + public normalizeArray(apiObjectArray: TAPI[], asEntity?: boolean): TAPI[] { + apiObjectArray.forEach(apiObject => this.normalize(apiObject, asEntity)); + return apiObjectArray; + } + + public normalizeAsEntities(apiObject: TAPI): NormalizedSchema { + const normalized = this.normalize(apiObject, true); + return normalize(normalized, this.getSchema()); + } + + public normalizeArrayAsEntities(apiObject: TAPI[]): NormalizedSchema { + const normalized = this.normalizeArray(apiObject, true); + return normalize(normalized, new schema.Array(this.getSchema())); + } + + public fromApi(apiObject: TAPI): TREC { + return appFromJS(this.normalize(apiObject)); + } + + public fromApiArray(apiObjectArray: TAPI[]): List { + this.normalizeArray(apiObjectArray); + return appFromJS(apiObjectArray); + } + + public fromApiAsEntities(apiObject: TAPI): NormalizedRecordEntity { + return ApiRecordUtils.toNormalizedRecordEntities(this.normalizeAsEntities(apiObject), false); + } + + public fromApiArrayAsEntities(apiObject: TAPI[]): NormalizedRecordEntities { + return ApiRecordUtils.toNormalizedRecordEntities(this.normalizeArrayAsEntities(apiObject), true); + } + + public toApi(record: TREC): TAPI { + const apiObject = record.toJS(); + delete apiObject.recType; + return apiObject; + } + + public toApiArray(records: List): TAPI[] { + return records.map(record => this.toApi(record)).toArray(); + } + + public static toNormalizedRecordEntities(normalizedAsEntities: any, forArray: boolean) { + const entities = normalizedAsEntities.entities; + for (const entityKey of Object.keys(entities)) { + entities[entityKey] = appFromJS(entities[entityKey]); + } + normalizedAsEntities.result = appFromJS(normalizedAsEntities.result || (forArray ? "" : [])); + return normalizedAsEntities; + } +} + +export const allApiActionFailures: SagaActionDefinition[] = []; + +export interface BaseEntitySupportPayloadApiAction { + toInlined?: boolean; + toEntities?: boolean; + markErrorsAsHandled?: boolean; +} + +export interface BasePayloadApiAction { + markErrorsAsHandled?: boolean; +} + +export interface SagaActionDefinition extends ActionDefinition { + toString: () => string; +} + +export function createSagaAction(type: string, options?: { doNotAutoRegisterFailure?: boolean, namespace?: string }): SagaActionDefinition { + const {doNotAutoRegisterFailure, namespace} = options || {} as any; + let actionDefinition = createAction(namespace ? `${namespace}-${type}` : type); + (actionDefinition as any).toString = () => actionDefinition.type; + if (type.endsWith("Failure") && !doNotAutoRegisterFailure) { + allApiActionFailures.push(actionDefinition); + } + return actionDefinition; +} + +export let apiCall: any>(context: Ctx, fn: Fn, ...args: Parameters) => Generator; + +export function setApiCall(apiCallFc: any>(context: Ctx, fn: Fn, ...args: Parameters) => Generator) { + console.log("init apiCall"); + apiCall = apiCallFc; +} + +export const normalizedEntities = createSagaAction("normalizedEntities"); \ No newline at end of file diff --git a/openapi-template/sagaApiManager.mustache b/openapi-template/sagaApiManager.mustache new file mode 100644 index 0000000..d8979fd --- /dev/null +++ b/openapi-template/sagaApiManager.mustache @@ -0,0 +1,28 @@ +import { + Configuration, + ConfigurationParameters, +} from "../index{{importFileExtension}}"; + +import { +{{#apiInfo}} +{{#apis}} + {{classFilename}}, +{{/apis}} +{{/apiInfo}} +} from "./index{{importFileExtension}}"; + +export class Api { +{{#apiInfo}} +{{#apis}} + public static {{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}}: {{classFilename}}; +{{/apis}} +{{/apiInfo}} + + public static init(apiBaseConfig: ConfigurationParameters) { +{{#apiInfo}} +{{#apis}} + Api.{{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}} = new {{classFilename}}(new Configuration(apiBaseConfig)); +{{/apis}} +{{/apiInfo}} + } +} diff --git a/openapi-template/sagas.mustache b/openapi-template/sagas.mustache new file mode 100644 index 0000000..2ea9cf3 --- /dev/null +++ b/openapi-template/sagas.mustache @@ -0,0 +1,245 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +import {Api} from './index{{importFileExtension}}'; +import {List} from 'immutable'; +import {all, fork, put, takeLatest} from "redux-saga/effects"; +import {apiCall, createSagaAction as originalCreateSagaAction, BaseEntitySupportPayloadApiAction, BasePayloadApiAction, NormalizedRecordEntities, normalizedEntities} from "../runtimeSagasAndRecords{{importFileExtension}}"; +import {Action} from "redux-ts-simple"; + +{{#imports.0}} +import { + {{#imports}} + {{className}}, + {{className}}Record, + {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}RecordUtils, + {{/imports}} + {{#passthroughImports}} + {{.}}, + {{/passthroughImports}} +} from '../models/index{{importFileExtension}}'; +{{/imports.0}} +{{#hasEnums}} +{{#operations}} +{{#operation}} +{{#allParams}} +{{#isEnum}} + +import { + {{operationIdCamelCase}}{{enumName}}, +} from './{{classname}}{{importFileExtension}}'; +{{/isEnum}} +{{/allParams}} +{{/operation}} +{{/operations}} +{{/hasEnums}} + +const createSagaAction = (type: string) => originalCreateSagaAction(type, {namespace: "api_{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}"}); + +export const {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}SagaMap = new Map Generator>([ +{{#operations}} + {{#operation}} + ["{{nickname}}", {{nickname}}Saga], + {{/operation}} +{{/operations}} + ] +); + +export function *{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}AllSagas() { + yield all([...{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}SagaMap.values()].map(actionSaga => fork(actionSaga))); +} + +{{#operations}} +{{#operation}} +//region {{nickname}} + +{{#returnTypeSupportsEntities}} +export interface Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}} extends {{#allParams.0}}Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}Request, {{/allParams.0}}BaseEntitySupportPayloadApiAction { +} +{{/returnTypeSupportsEntities}} +{{^returnTypeSupportsEntities}} +export interface Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}} extends {{#allParams.0}}Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}Request, {{/allParams.0}}BasePayloadApiAction { +} +{{/returnTypeSupportsEntities}} + +{{#allParams.0}} +export interface Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}Request { +{{#allParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataTypeAlternate}}}; +{{/allParams}} +} +{{/allParams.0}} + +export const {{nickname}}Request = createSagaAction<{{#allParams.0}}Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}Request{{/allParams.0}}{{^allParams.0}}void{{/allParams.0}}>("{{nickname}}Request"); +{{#returnType}} +export const {{nickname}}Success = createSagaAction<{{#hasReturnPassthroughVoid}}void{{/hasReturnPassthroughVoid}}{{^hasReturnPassthroughVoid}}{{{returnTypeAlternate}}}{{/hasReturnPassthroughVoid}}>("{{nickname}}Success"); +{{#returnTypeSupportsEntities}} +export const {{nickname}}Success_Entities = createSagaAction("{{nickname}}Success_Entities"); +{{/returnTypeSupportsEntities}} +{{/returnType}} +{{^returnType}} +export const {{nickname}}Success = createSagaAction("{{nickname}}Success"); +{{/returnType}} +export const {{nickname}}Failure = createSagaAction<{error: any, requestPayload: Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}}>("{{nickname}}Failure"); + +export const {{nickname}} = createSagaAction("{{nickname}}"); + +export function *{{nickname}}Saga() { + yield takeLatest({{nickname}}, {{nickname}}SagaImp); +} + +export function *{{nickname}}SagaImp(_action_: Action){{^returnType}}: any{{/returnType}} { + const {markErrorsAsHandled, ..._payloadRest_} = _action_.payload; + try { +{{#returnTypeSupportsEntities}} + const {toEntities, toInlined = !toEntities, ...requestPayload} = _payloadRest_; +{{/returnTypeSupportsEntities}} +{{#allParams.0}} + const { +{{#allParams}} + {{paramName}}, +{{/allParams}} + } = _payloadRest_; +{{/allParams.0}} + + yield put({{nickname}}Request({{#allParams.0}}{{#returnTypeSupportsEntities}}requestPayload{{/returnTypeSupportsEntities}}{{^returnTypeSupportsEntities}}_action_.payload{{/returnTypeSupportsEntities}}{{/allParams.0}})); + + const response{{#returnType}}: Required<{{{returnType}}}>{{/returnType}} = yield apiCall(Api.{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}, Api.{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}['{{nickname}}'], +{{#allParams.0}} +{{#allParams}} +{{#isUniqueId}} +{{#isArray}} +{{#items.isArray}} + {{^required}}{{paramName}} ? {{/required}}{{paramName}}.map(p => p.toArray().map(p2 => (p2 ? parseFloat(p2) : null) as number)).toArray(){{^required}} : undefined{{/required}}, +{{/items.isArray}} +{{^items.isArray}} + {{^required}}{{paramName}} ? {{/required}}{{paramName}}.map(p => (p ? parseFloat(p) : null) as number ).toArray(){{^required}} : undefined{{/required}}, +{{/items.isArray}} +{{/isArray}} +{{^isArray}} + {{^required}}{{paramName}} ? {{/required}}parseFloat({{paramName}}){{^required}} : undefined{{/required}}, +{{/isArray}} +{{/isUniqueId}} +{{^isUniqueId}} +{{#isArray}} +{{#items.isModel}} + {{^required}}{{paramName}} ? {{/required}}{{#lambda.camelcase}}{{items.dataType}}{{/lambda.camelcase}}RecordUtils.toApiArray({{paramName}}){{^required}} : undefined{{/required}}, +{{/items.isModel}} +{{^items.isModel}} + {{^required}}{{paramName}} ? {{/required}}{{paramName}}.toJS(){{^required}} : undefined{{/required}}, +{{/items.isModel}} +{{/isArray}} +{{#isModel}} + {{^required}}{{paramName}} ? {{/required}}{{#lambda.camelcase}}{{{dataTypeAlternate}}}{{/lambda.camelcase}}Utils.toApi({{paramName}}){{^required}} : undefined{{/required}}, +{{/isModel}} +{{^isArray}} +{{^isModel}} + {{paramName}}, +{{/isModel}} +{{/isArray}} +{{/isUniqueId}} +{{/allParams}} +{{/allParams.0}} + ); + +{{#returnType}} +{{^hasReturnPassthroughVoid}} + let successReturnValue: any = undefined; +{{/hasReturnPassthroughVoid}} +{{/returnType}} +{{#returnTypeSupportsEntities}} + if (toEntities) { +{{#returnPassthrough}} + successReturnValue = {{#lambda.camelcase}}{{{returnType}}}{{/lambda.camelcase}}RecordUtils.fromApiPassthroughAsEntities(response); +{{/returnPassthrough}} +{{^hasReturnPassthroughVoid}} +{{^returnPassthrough}} +{{#returnTypeIsArray}} +{{#returnTypeAlternate}} + successReturnValue = {{#lambda.camelcase}}{{returnBaseTypeAlternate}}{{/lambda.camelcase}}Utils.fromApiArrayAsEntities(response); +{{/returnTypeAlternate}} +{{/returnTypeIsArray}} +{{#returnTypeIsModel}} + successReturnValue = {{#lambda.camelcase}}{{returnTypeAlternate}}{{/lambda.camelcase}}Utils.fromApiArrayAsEntities([response]); +{{/returnTypeIsModel}} +{{/returnPassthrough}} +{{/hasReturnPassthroughVoid}} + yield put(normalizedEntities(successReturnValue)); + yield put({{nickname}}Success_Entities(successReturnValue)); + } + if (toInlined) { +{{/returnTypeSupportsEntities}} +{{#returnType}} +{{#returnPassthrough}} + successReturnValue = {{#lambda.camelcase}}{{{returnType}}}{{/lambda.camelcase}}RecordUtils.fromApiPassthrough(response); + yield put({{nickname}}Success(successReturnValue)); +{{/returnPassthrough}} +{{#hasReturnPassthroughVoid}} + yield put({{nickname}}Success()); +{{/hasReturnPassthroughVoid}} +{{^hasReturnPassthroughVoid}} +{{^returnPassthrough}} +{{#returnTypeIsArray}} +{{#returnTypeAlternate}} + successReturnValue = {{#lambda.camelcase}}{{returnBaseTypeAlternate}}{{/lambda.camelcase}}Utils.fromApiArray(response); + yield put({{nickname}}Success(successReturnValue)); +{{/returnTypeAlternate}} +{{/returnTypeIsArray}} +{{#returnTypeIsModel}} + successReturnValue = {{#lambda.camelcase}}{{returnTypeAlternate}}{{/lambda.camelcase}}Utils.fromApi(response); + yield put({{nickname}}Success(successReturnValue)); +{{/returnTypeIsModel}} +{{^returnTypeIsArray}} +{{^returnTypeIsModel}} + yield put({{nickname}}Success(response)); +{{/returnTypeIsModel}} +{{/returnTypeIsArray}} +{{/returnPassthrough}} +{{/hasReturnPassthroughVoid}} +{{/returnType}} +{{^returnType}} + yield put({{nickname}}Success()); +{{/returnType}} +{{#returnTypeSupportsEntities}} + } +{{/returnTypeSupportsEntities}} + +{{#returnType}} +{{#returnPassthrough}} + return successReturnValue; +{{/returnPassthrough}} +{{#hasReturnPassthroughVoid}} + return undefined; +{{/hasReturnPassthroughVoid}} +{{^hasReturnPassthroughVoid}} +{{^returnPassthrough}} +{{#returnTypeIsArray}} +{{#returnTypeAlternate}} + return successReturnValue; +{{/returnTypeAlternate}} +{{/returnTypeIsArray}} +{{#returnTypeIsModel}} + return successReturnValue; +{{/returnTypeIsModel}} +{{^returnTypeIsArray}} +{{^returnTypeIsModel}} + return response; +{{/returnTypeIsModel}} +{{/returnTypeIsArray}} +{{/returnPassthrough}} +{{/hasReturnPassthroughVoid}} +{{/returnType}} +{{^returnType}} + return undefined; +{{/returnType}} + } catch (error: any) { + if (markErrorsAsHandled) {error.wasHandled = true; } + yield put({{nickname}}Failure({error, requestPayload: _action_.payload})); + return error; + } +} +//endregion +{{/operation}} +{{/operations}} diff --git a/openapi-template/sourceLibraryIndex.mustache b/openapi-template/sourceLibraryIndex.mustache new file mode 100644 index 0000000..3a0629e --- /dev/null +++ b/openapi-template/sourceLibraryIndex.mustache @@ -0,0 +1 @@ +export * from './src/index{{importFileExtension}}'; diff --git a/openapi-template/tsconfig.esm.mustache b/openapi-template/tsconfig.esm.mustache new file mode 100644 index 0000000..2c0331c --- /dev/null +++ b/openapi-template/tsconfig.esm.mustache @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm" + } +} diff --git a/openapi-template/tsconfig.mustache b/openapi-template/tsconfig.mustache new file mode 100644 index 0000000..a8778b0 --- /dev/null +++ b/openapi-template/tsconfig.mustache @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "{{#supportsES6}}es6{{/supportsES6}}{{^supportsES6}}es5{{/supportsES6}}", +{{#sagasAndRecords}} + "strict": true, +{{/sagasAndRecords}} + "module": "commonjs", + "moduleResolution": "node", + "outDir": "dist", + {{^supportsES6}} + "lib": [ + "es6", + "dom" + ], + {{/supportsES6}} + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +}