diff --git a/docs/generators.md b/docs/generators.md index efd633f162ef..8fe3d453a2c2 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -75,6 +75,7 @@ The following generators are available: * [typescript-angular](generators/typescript-angular.md) * [typescript-aurelia](generators/typescript-aurelia.md) * [typescript-axios](generators/typescript-axios.md) +* [typescript-axios-slim](generators/typescript-axios-slim.md) * [typescript-fetch](generators/typescript-fetch.md) * [typescript-inversify](generators/typescript-inversify.md) * [typescript-jquery](generators/typescript-jquery.md) diff --git a/docs/generators/typescript-axios-slim.md b/docs/generators/typescript-axios-slim.md new file mode 100644 index 000000000000..f9bb663d3145 --- /dev/null +++ b/docs/generators/typescript-axios-slim.md @@ -0,0 +1,302 @@ +--- +title: Documentation for the typescript-axios-slim Generator +--- + +## METADATA + +| Property | Value | Notes | +| -------- | ----- | ----- | +| generator name | typescript-axios-slim | pass this to the generate command after -g | +| generator stability | STABLE | | +| generator type | CLIENT | | +| generator language | Typescript | | +| generator default templating engine | mustache | | +| helpTxt | Generates a TypeScript client library using axios (slim direct API style with valibot validation and enforced single request parameter object). | | + +## CONFIG OPTIONS +These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details. + +| Option | Description | Values | Default | +| ------ | ----------- | ------ | ------- | +|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false| +|apiPackage|package for generated api classes| |null| +|axiosVersion|Use this property to override the axios version in package.json| |^1.13.5| +|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|
**false**
The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.
**true**
Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.
|true| +|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true| +|enumNameSuffix|Suffix that will be appended to all enum names.| |Enum| +|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase| +|enumPropertyNamingReplaceSpecialChar|Set to true to replace '-' and '+' symbols with 'minus_' and 'plus_' in enum of type string| |false| +|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|
**false**
No changes to the enum's are made, this is the default option.
**true**
With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.
|false| +|importFileExtension|File extension to use with relative imports. Set it to '.js' or '.mjs' when using [ESM](https://nodejs.org/api/esm.html).| || +|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|
**true**
The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.
**false**
The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.
|true| +|licenseName|The name of the license| |Unlicense| +|modelPackage|package for generated models| |null| +|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null| +|npmRepository|Use this property to set an url of your private npmRepo in the package.json| |null| +|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0| +|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false| +|paramNaming|Naming convention for parameters: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase| +|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false| +|snapshot|When setting this property to true, the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm| |false| +|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| +|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| +|stringEnums|Generate string enums instead of objects for enum values.| |false| +|supportsES6|Generate code that conforms to ES6.| |false| +|useSquareBracketsInArrayNames|Setting this property to true will add brackets to array attribute names, e.g. my_values[].| |false| +|withAWSV4Signature|whether to include AWS v4 signature support| |false| +|withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false| +|withNodeImports|Setting this property to true adds imports for NodeJS| |false| +|withSeparateModelsAndApi|Put the model and api in separate folders and in separate classes. This requires in addition a value for 'apiPackage' and 'modelPackage'| |false| +|withoutPrefixEnums|Don't prefix enum names with class names| |false| + +## IMPORT MAPPING + +| Type/Alias | Imports | +| ---------- | ------- | + + +## INSTANTIATION TYPES + +| Type/Alias | Instantiated By | +| ---------- | --------------- | +|array|Array| + + +## LANGUAGE PRIMITIVES + + + +## RESERVED WORDS + + + +## FEATURE SET + + +### Client Modification Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasePath|✓|ToolingExtension +|Authorizations|✗|ToolingExtension +|UserAgent|✗|ToolingExtension +|MockServer|✗|ToolingExtension + +### Data Type Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Custom|✗|OAS2,OAS3 +|Int32|✓|OAS2,OAS3 +|Int64|✓|OAS2,OAS3 +|Float|✓|OAS2,OAS3 +|Double|✓|OAS2,OAS3 +|Decimal|✓|ToolingExtension +|String|✓|OAS2,OAS3 +|Byte|✓|OAS2,OAS3 +|Binary|✓|OAS2,OAS3 +|Boolean|✓|OAS2,OAS3 +|Date|✓|OAS2,OAS3 +|DateTime|✓|OAS2,OAS3 +|Password|✓|OAS2,OAS3 +|File|✓|OAS2 +|Uuid|✗| +|Array|✓|OAS2,OAS3 +|Null|✗|OAS3 +|AnyType|✗|OAS2,OAS3 +|Object|✓|OAS2,OAS3 +|Maps|✓|ToolingExtension +|CollectionFormat|✓|OAS2 +|CollectionFormatMulti|✓|OAS2 +|Enum|✓|OAS2,OAS3 +|ArrayOfEnum|✓|ToolingExtension +|ArrayOfModel|✓|ToolingExtension +|ArrayOfCollectionOfPrimitives|✓|ToolingExtension +|ArrayOfCollectionOfModel|✓|ToolingExtension +|ArrayOfCollectionOfEnum|✓|ToolingExtension +|MapOfEnum|✓|ToolingExtension +|MapOfModel|✓|ToolingExtension +|MapOfCollectionOfPrimitives|✓|ToolingExtension +|MapOfCollectionOfModel|✓|ToolingExtension +|MapOfCollectionOfEnum|✓|ToolingExtension + +### Documentation Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Readme|✓|ToolingExtension +|Model|✓|ToolingExtension +|Api|✓|ToolingExtension + +### Global Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Host|✓|OAS2,OAS3 +|BasePath|✓|OAS2,OAS3 +|Info|✓|OAS2,OAS3 +|Schemes|✗|OAS2,OAS3 +|PartialSchemes|✓|OAS2,OAS3 +|Consumes|✓|OAS2 +|Produces|✓|OAS2 +|ExternalDocumentation|✓|OAS2,OAS3 +|Examples|✓|OAS2,OAS3 +|XMLStructureDefinitions|✗|OAS2,OAS3 +|MultiServer|✗|OAS3 +|ParameterizedServer|✗|OAS3 +|ParameterStyling|✗|OAS3 +|Callbacks|✗|OAS3 +|LinkObjects|✗|OAS3 + +### Parameter Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Path|✓|OAS2,OAS3 +|Query|✓|OAS2,OAS3 +|Header|✓|OAS2,OAS3 +|Body|✓|OAS2 +|FormUnencoded|✓|OAS2 +|FormMultipart|✓|OAS2 +|Cookie|✓|OAS3 + +### Schema Support Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Simple|✓|OAS2,OAS3 +|Composite|✓|OAS2,OAS3 +|Polymorphism|✓|OAS2,OAS3 +|Union|✗|OAS3 +|allOf|✗|OAS2,OAS3 +|anyOf|✗|OAS3 +|oneOf|✗|OAS3 +|not|✗|OAS3 + +### Security Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasicAuth|✓|OAS2,OAS3 +|ApiKey|✓|OAS2,OAS3 +|OpenIDConnect|✗|OAS3 +|BearerToken|✓|OAS3 +|OAuth2_Implicit|✓|OAS2,OAS3 +|OAuth2_Password|✗|OAS2,OAS3 +|OAuth2_ClientCredentials|✗|OAS2,OAS3 +|OAuth2_AuthorizationCode|✗|OAS2,OAS3 +|SignatureAuth|✗|OAS3 +|AWSV4Signature|✓|ToolingExtension + +### Wire Format Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|JSON|✓|OAS2,OAS3 +|XML|✓|OAS2,OAS3 +|PROTOBUF|✗|ToolingExtension +|Custom|✗|OAS2,OAS3 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosSlimClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosSlimClientCodegen.java new file mode 100644 index 000000000000..4357b364e5cb --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosSlimClientCodegen.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +public class TypeScriptAxiosSlimClientCodegen extends TypeScriptAxiosClientCodegen { + + public TypeScriptAxiosSlimClientCodegen() { + super(); + outputFolder = "generated-code/typescript-axios-slim"; + embeddedTemplateDir = templateDir = "typescript-axios-slim"; + cliOptions.removeIf(option -> USE_SINGLE_REQUEST_PARAMETER.equals(option.getOpt())); + additionalProperties.put(USE_SINGLE_REQUEST_PARAMETER, true); + } + + @Override + public void processOpts() { + additionalProperties.put(USE_SINGLE_REQUEST_PARAMETER, true); + super.processOpts(); + additionalProperties.put(USE_SINGLE_REQUEST_PARAMETER, true); + } + + @Override + public String getName() { + return "typescript-axios-slim"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript client library using axios (slim direct API style with valibot validation and enforced single request parameter object)."; + } +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 04da8d20435b..900863e0310e 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -154,6 +154,7 @@ org.openapitools.codegen.languages.TypeScriptClientCodegen org.openapitools.codegen.languages.TypeScriptAngularClientCodegen org.openapitools.codegen.languages.TypeScriptAureliaClientCodegen org.openapitools.codegen.languages.TypeScriptAxiosClientCodegen +org.openapitools.codegen.languages.TypeScriptAxiosSlimClientCodegen org.openapitools.codegen.languages.TypeScriptFetchClientCodegen org.openapitools.codegen.languages.TypeScriptInversifyClientCodegen org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/README.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/README.mustache new file mode 100644 index 000000000000..da9dcbe640f6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/README.mustache @@ -0,0 +1,98 @@ +## {{npmName}}@{{npmVersion}} + +This generator creates TypeScript/JavaScript client that utilizes [axios](https://github.com/axios/axios). The generated Node module can be used in the following environments: + +> `typescript-axios-slim` intentionally differs from `typescript-axios`: it removes `AxiosParamCreator`, `Fp`, and `Factory` layers, always uses a single request-parameter object per operation, and emits direct class methods with request-parameter schema validation. + +Environment +* Node.js +* Webpack +* Browserify + +Language level +* ES5 - you must have a Promises/A+ library installed +* ES6 + +Module system +* CommonJS +* ES6 module system + +It can be used in both TypeScript and JavaScript. In TypeScript, the definition will be automatically resolved via `package.json`. ([Reference](https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html)) + +### Building + +To build and compile the typescript sources to javascript use: +``` +npm install +npm run build +``` + +### Publishing + +First build the package then run `npm publish` + +### Consuming + +navigate to the folder of your consuming project and run one of the following commands. + +_published:_ + +``` +npm install {{npmName}}@{{npmVersion}} --save +``` + +_unPublished (not recommended):_ + +``` +npm install PATH_TO_GENERATED_PACKAGE --save +``` + +### Documentation for 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}} + +### Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + + +## Documentation For Authorization + +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{{name}}} + +{{#isApiKey}} +- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}} +{{#isBasicBasic}} +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} +- **Type**: Bearer authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} +- **Type**: HTTP signature authentication +{{/isHttpSignature}} +{{/isBasic}} +{{#isOAuth}} +- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/api.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/api.mustache new file mode 100644 index 000000000000..349008b390ed --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/api.mustache @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +{{^withSeparateModelsAndApi}} +import type { Configuration } from './configuration{{importFileExtension}}'; +import type { AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +{{#withNodeImports}} +// URLSearchParams not necessarily used +// @ts-ignore +import { URL, URLSearchParams } from 'url'; +{{#multipartFormData}} +import FormData from 'form-data' +{{/multipartFormData}} +{{/withNodeImports}} +import * as v from 'valibot'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, validateRequestParameters, withParams, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, replaceWithSerializableTypeIfNeeded{{#withAWSV4Signature}}, setAWS4SignatureInterceptor{{/withAWSV4Signature}} } from './common{{importFileExtension}}'; +import type { RequestArgs } from './base{{importFileExtension}}'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base{{importFileExtension}}'; + +{{#models}} +{{#model}}{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^isEnum}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/isEnum}}{{/model}} +{{/models}} +{{#apiInfo}}{{#apis}} +{{>apiInner}} + +{{/apis}}{{/apiInfo}} +{{/withSeparateModelsAndApi}}{{#withSeparateModelsAndApi}} +{{#apiInfo}}{{#apis}}{{#operations}}export * from './{{tsApiPackage}}/{{classFilename}}{{importFileExtension}}'; +{{/operations}}{{/apis}}{{/apiInfo}} +{{/withSeparateModelsAndApi}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/apiInner.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/apiInner.mustache new file mode 100644 index 000000000000..358e6e8799d4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/apiInner.mustache @@ -0,0 +1,338 @@ +{{#withSeparateModelsAndApi}} +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +import type { Configuration } from '{{apiRelativeToRoot}}configuration{{importFileExtension}}'; +import type { AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +{{#withNodeImports}} +// URLSearchParams not necessarily used +// @ts-ignore +import { URL, URLSearchParams } from 'url'; +{{#multipartFormData}} +import FormData from 'form-data' +{{/multipartFormData}} +{{/withNodeImports}} +import * as v from 'valibot'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, validateRequestParameters, withParams, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, replaceWithSerializableTypeIfNeeded{{#withAWSV4Signature}}, setAWS4SignatureInterceptor{{/withAWSV4Signature}} } from '{{apiRelativeToRoot}}common{{importFileExtension}}'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from '{{apiRelativeToRoot}}base{{importFileExtension}}'; +{{#imports}} +// @ts-ignore +import type { {{classname}} } from '{{apiRelativeToRoot}}{{tsModelPackage}}{{#importFileExtension}}/index{{importFileExtension}}{{/importFileExtension}}'; +{{/imports}} +{{/withSeparateModelsAndApi}} +{{^withSeparateModelsAndApi}} +{{/withSeparateModelsAndApi}} + +{{#operations}} +{{#operation}} +{{#allParams.0}} +const {{nickname}}RequestSchema = v.object({ +{{#allParams}} + {{paramName}}: {{#required}}{{#isString}}v.string(){{/isString}}{{#isBoolean}}v.boolean(){{/isBoolean}}{{#isNumeric}}v.number(){{/isNumeric}}{{#isLong}}v.number(){{/isLong}}{{#isInteger}}v.number(){{/isInteger}}{{#isFloat}}v.number(){{/isFloat}}{{#isDouble}}v.number(){{/isDouble}}{{#isArray}}v.array(v.unknown()){{/isArray}}{{#isDate}}v.union([v.date(), v.string()]){{/isDate}}{{#isDateTime}}v.union([v.date(), v.string()]){{/isDateTime}}{{^isString}}{{^isBoolean}}{{^isNumeric}}{{^isLong}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{^isArray}}{{^isDate}}{{^isDateTime}}v.unknown(){{/isDateTime}}{{/isDate}}{{/isArray}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isLong}}{{/isNumeric}}{{/isBoolean}}{{/isString}}{{/required}}{{^required}}v.optional({{#isString}}v.string(){{/isString}}{{#isBoolean}}v.boolean(){{/isBoolean}}{{#isNumeric}}v.number(){{/isNumeric}}{{#isLong}}v.number(){{/isLong}}{{#isInteger}}v.number(){{/isInteger}}{{#isFloat}}v.number(){{/isFloat}}{{#isDouble}}v.number(){{/isDouble}}{{#isArray}}v.array(v.unknown()){{/isArray}}{{#isDate}}v.union([v.date(), v.string()]){{/isDate}}{{#isDateTime}}v.union([v.date(), v.string()]){{/isDateTime}}{{^isString}}{{^isBoolean}}{{^isNumeric}}{{^isLong}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{^isArray}}{{^isDate}}{{^isDateTime}}v.unknown(){{/isDateTime}}{{/isDate}}{{/isArray}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isLong}}{{/isNumeric}}{{/isBoolean}}{{/isString}}){{/required}}{{^-last}},{{/-last}} +{{/allParams}} +}); + +{{/allParams.0}} +{{/operation}} +{{/operations}} + +{{#operations}} +{{#withInterfaces}} +/** + * {{classname}} - interface{{#description}} + * {{&description}}{{/description}} + */ +export interface {{classname}}Interface { +{{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + {{nickname}}({{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{classname}}{{operationIdCamelCase}}Request, {{/allParams.0}}options?: RawAxiosRequestConfig): Promise<{{{returnType}}}{{^returnType}}void{{/returnType}}>; + +{{/operation}} +} + +{{/withInterfaces}} +{{#useSingleRequestParameter}} +{{#operation}} +{{#allParams.0}} +export interface {{classname}}{{operationIdCamelCase}}Request { + {{#allParams}} + {{#description}} + /** + * {{description}} + */ + {{/description}} + readonly {{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}} + {{^-last}} + + {{/-last}} + {{/allParams}} +} + +{{/allParams.0}} +{{/operation}} +{{/useSingleRequestParameter}} +/** + * {{classname}} - object-oriented interface{{#description}} + * {{{.}}}{{/description}} + */ +{{#withInterfaces}} +export class {{classname}} extends BaseAPI implements {{classname}}Interface { +{{/withInterfaces}} +{{^withInterfaces}} +export class {{classname}} extends BaseAPI { +{{/withInterfaces}} + {{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + public async {{nickname}}({{#allParams.0}}requestParameters: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}options: RawAxiosRequestConfig = {}): Promise<{{{returnType}}}{{^returnType}}void{{/returnType}}> { + {{#allParams.0}} + validateRequestParameters('{{nickname}}', {{nickname}}RequestSchema, requestParameters); + {{/allParams.0}} + const localVarPath = {{#pathParams}}withParams(`{{{path}}}`, { {{#pathParams}}"{{baseName}}": requestParameters.{{paramName}}, {{/pathParams}} }){{/pathParams}}{{^pathParams}}`{{{path}}}`{{/pathParams}}; + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + const baseOptions = this.configuration ? this.configuration.baseOptions : undefined; + const localVarRequestOptions = { method: '{{httpMethod}}', ...baseOptions, ...options } as RawAxiosRequestConfig; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any;{{#vendorExtensions}}{{#hasFormParams}} + const localVarFormParams = new {{^multipartFormData}}URLSearchParams(){{/multipartFormData}}{{#multipartFormData}}((this.configuration && this.configuration.formDataCtor) || FormData)(){{/multipartFormData}};{{/hasFormParams}}{{/vendorExtensions}} + + {{#authMethods}} + {{#isApiKey}} + {{#withAWSV4Signature}} + await setAWS4SignatureInterceptor(this.axios, this.configuration) + {{/withAWSV4Signature}} + {{#isKeyInHeader}} + await setApiKeyToObject(localVarHeaderParameter, "{{keyParamName}}", this.configuration) + {{/isKeyInHeader}} + {{#isKeyInQuery}} + await setApiKeyToObject(localVarQueryParameter, "{{keyParamName}}", this.configuration) + {{/isKeyInQuery}} + {{/isApiKey}} + {{#isBasicBasic}} + setBasicAuthToObject(localVarRequestOptions, this.configuration) + {{/isBasicBasic}} + {{#isBasicBearer}} + await setBearerAuthToObject(localVarHeaderParameter, this.configuration) + {{/isBasicBearer}} + {{#isOAuth}} + await setOAuthToObject(localVarHeaderParameter, "{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}], this.configuration) + {{/isOAuth}} + + {{/authMethods}} + {{#queryParams}} + const {{paramName}} = requestParameters.{{paramName}}; + {{#isArray}} + if ({{paramName}}) { + {{#isCollectionFormatMulti}} + {{#uniqueItems}} + localVarQueryParameter['{{baseName}}'] = Array.from({{paramName}}); + {{/uniqueItems}} + {{^uniqueItems}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/uniqueItems}} + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + {{#uniqueItems}} + localVarQueryParameter['{{baseName}}'] = Array.from({{paramName}}).join(COLLECTION_FORMATS.{{collectionFormat}}); + {{/uniqueItems}} + {{^uniqueItems}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS.{{collectionFormat}}); + {{/uniqueItems}} + {{/isCollectionFormatMulti}} + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} !== undefined) { + {{#isDateTime}} + localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any instanceof Date) ? + ({{paramName}} as any).toISOString() : + {{paramName}}; + {{/isDateTime}} + {{^isDateTime}} + {{#isDate}} + localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any instanceof Date) ? + ({{paramName}} as any).toISOString().substring(0, 10) : + {{paramName}}; + {{/isDate}} + {{^isDate}} + {{#isExplode}} + {{#isPrimitiveType}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{^isEnumRef}} + {{^isEnum}} + for (const [key, value] of Object.entries({{paramName}} as any)) { + localVarQueryParameter[key] = value; + } + {{/isEnum}} + {{/isEnumRef}} + {{#isEnum}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isEnum}} + {{#isEnumRef}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isEnumRef}} + {{/isPrimitiveType}} + {{/isExplode}} + {{^isExplode}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isExplode}} + {{/isDate}} + {{/isDateTime}} + } + {{/isArray}} + + {{/queryParams}} + {{#vendorExtensions}} + {{#formParams}} + const {{paramName}} = requestParameters.{{paramName}}; + {{#isArray}} + if ({{paramName}}) { + {{#isCollectionFormatMulti}} + {{#contentType}} + localVarFormParams.append('{{baseName}}', new Blob([JSON.stringify({{paramName}}, replaceWithSerializableTypeIfNeeded)], { type: "{{contentType}}", })); + {{/contentType}} + {{^contentType}} + {{paramName}}.forEach((element) => { + localVarFormParams.{{#multipartFormData}}append{{/multipartFormData}}{{^multipartFormData}}set{{/multipartFormData}}('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', element as any); + }) + {{/contentType}} + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + localVarFormParams.{{#multipartFormData}}append{{/multipartFormData}}{{^multipartFormData}}set{{/multipartFormData}}('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', {{paramName}}.join(COLLECTION_FORMATS.{{collectionFormat}})); + {{/isCollectionFormatMulti}} + }{{/isArray}} + {{^isArray}} + if ({{paramName}} !== undefined) { {{^multipartFormData}} + localVarFormParams.set('{{baseName}}', {{paramName}} as any);{{/multipartFormData}}{{#multipartFormData}}{{#isPrimitiveType}}{{^isBoolean}} + localVarFormParams.append('{{baseName}}', {{paramName}} as any);{{/isBoolean}}{{/isPrimitiveType}}{{#isPrimitiveType}}{{#isBoolean}} + localVarFormParams.append('{{baseName}}', String({{paramName}}) as any);{{/isBoolean}}{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isEnumRef}} + localVarFormParams.append('{{baseName}}', {{paramName}} as any);{{/isEnumRef}}{{^isEnumRef}} + localVarFormParams.append('{{baseName}}', new Blob([JSON.stringify({{paramName}}, replaceWithSerializableTypeIfNeeded)], { type: "application/json", }));{{/isEnumRef}}{{/isPrimitiveType}}{{/multipartFormData}} + }{{/isArray}} + {{/formParams}} + {{/vendorExtensions}} + {{#vendorExtensions}} + {{#hasFormParams}} + {{^multipartFormData}} + localVarHeaderParameter['Content-Type'] = 'application/x-www-form-urlencoded'; + {{/multipartFormData}} + {{#multipartFormData}} + localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; + {{/multipartFormData}} + {{/hasFormParams}} + {{/vendorExtensions}} + {{#bodyParam}} + const {{paramName}} = requestParameters.{{paramName}}; + {{^consumes}} + localVarHeaderParameter['Content-Type'] = 'application/json'; + {{/consumes}} + {{#consumes.0}} + localVarHeaderParameter['Content-Type'] = '{{{mediaType}}}'; + {{/consumes.0}} + {{/bodyParam}} + {{#hasProduces}} + localVarHeaderParameter['Accept'] = '{{#produces}}{{{mediaType}}}{{^-last}},{{/-last}}{{/produces}}'; + {{/hasProduces}} + + {{#headerParams}} + const {{paramName}} = requestParameters.{{paramName}}; + {{#isArray}} + if ({{paramName}}) { + {{#uniqueItems}} + let mapped = Array.from({{paramName}}).map(value => ("{{{dataType}}}" !== "Set") ? JSON.stringify(value, replaceWithSerializableTypeIfNeeded) : (value || "")); + {{/uniqueItems}} + {{^uniqueItems}} + let mapped = {{paramName}}.map(value => ("{{{dataType}}}" !== "Array") ? JSON.stringify(value, replaceWithSerializableTypeIfNeeded) : (value || "")); + {{/uniqueItems}} + localVarHeaderParameter['{{baseName}}'] = mapped.join(COLLECTION_FORMATS["{{collectionFormat}}"]); + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} != null) { + {{#isString}} + localVarHeaderParameter['{{baseName}}'] = String({{paramName}}); + {{/isString}} + {{^isString}} + localVarHeaderParameter['{{baseName}}'] = typeof {{paramName}} === 'string' + ? {{paramName}} + : JSON.stringify({{paramName}}, replaceWithSerializableTypeIfNeeded); + {{/isString}} + } + {{/isArray}} + {{/headerParams}} + + setSearchParams(localVarUrlObj, localVarQueryParameter); + const headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions,{{#hasFormParams}}{{#multipartFormData}} ...(localVarFormParams as any).getHeaders?.(),{{/multipartFormData}}{{/hasFormParams}} ...options.headers }; + {{#hasFormParams}} + localVarRequestOptions.data = localVarFormParams{{#vendorExtensions}}{{^multipartFormData}}.toString(){{/multipartFormData}}{{/vendorExtensions}}; + {{/hasFormParams}} + {{#bodyParam}} + localVarRequestOptions.data = serializeDataIfNeeded({{paramName}}, localVarRequestOptions, this.configuration) + {{/bodyParam}} + + const localVarOperationServerIndex = this.configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['{{classname}}.{{nickname}}']?.[localVarOperationServerIndex]?.url; + const effectiveBasePath = localVarOperationServerBasePath || this.basePath || BASE_PATH; + localVarRequestOptions.url = (this.axios.defaults.baseURL ? '' : this.configuration?.basePath ?? effectiveBasePath) + toPathString(localVarUrlObj); + + const localVarResponse = await this.axios.request<{{{returnType}}}{{^returnType}}void{{/returnType}}>(localVarRequestOptions); + return localVarResponse.data; + } + {{^-last}} + + {{/-last}} + {{/operation}} +} +{{/operations}} + +{{#operations}} +{{#operation}} +{{#allParams}} +{{#isEnum}} +{{#stringEnums}} +export enum {{operationIdCamelCase}}{{enumName}} { +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}} +{{^stringEnums}} +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}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/api_doc.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/api_doc.mustache new file mode 100644 index 000000000000..0de91d7cfe4b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/api_doc.mustache @@ -0,0 +1,78 @@ +# {{classname}}{{#description}} + +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +|Method | HTTP request | Description| +|------------- | ------------- | -------------| +{{#operations}}{{#operation}}|[**{{operationId}}**](#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}| +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} +# **{{{operationId}}}** +> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}}) + +{{#notes}} +{{{notes}}} +{{/notes}} + +### Example + +```typescript +import { + {{classname}}, + Configuration{{#allParams}}{{#isModel}}, + {{{dataType}}}{{/isModel}}{{/allParams}} +} from '{{#npmName}}{{.}}{{/npmName}}{{^npmName}}./api{{/npmName}}'; + +const configuration = new Configuration(); +const apiInstance = new {{classname}}(configuration); +{{#hasParams}}{{#allParams}} +let {{paramName}}: {{{dataType}}}; //{{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/allParams}} + +const { status, data } = await apiInstance.{{{operationId}}}({{#allParams}} + {{paramName}}{{^-last}},{{/-last}}{{/allParams}} +); +{{/hasParams}}{{^hasParams}} +const { status, data } = await apiInstance.{{{operationId}}}(); +{{/hasParams}} +``` + +### Parameters +{{^hasParams}}This endpoint does not have any parameters.{{/hasParams}}{{#allParams}}{{#-last}} +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}{{^defaultValue}}| **{{paramName}}** | {{^isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}| {{description}} | | +{{/defaultValue}}{{/allParams}}{{#allParams}}{{#defaultValue}}| **{{paramName}}** | {{^isPrimitiveType}}{{^isEnum}}**{{dataType}}**{{/isEnum}}{{/isPrimitiveType}}{{#isPrimitiveType}}[**{{dataType}}**]{{/isPrimitiveType}}{{#isEnum}}{{#allowableValues}}{{#enumVars}}{{#-first}}**Array<{{/-first}}{{value}}{{^-last}} | {{/-last}}{{#-last}}>**{{/-last}}{{/enumVars}}{{/allowableValues}}{{/isEnum}} | {{description}} |{{^required}} (optional){{/required}} defaults to {{{.}}}| +{{/defaultValue}}{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-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#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/baseApi.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/baseApi.mustache new file mode 100644 index 000000000000..2bfb779f8194 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/baseApi.mustache @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +import type { Configuration } from './configuration{{importFileExtension}}'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "{{{basePath}}}".replace(/\/+$/, ""); + +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +export interface RequestArgs { + url: string; + options: RawAxiosRequestConfig; +} + +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath ?? basePath; + } + } +}; + +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} + +interface ServerMap { + [key: string]: { + url: string, + description: string, + }[]; +} + +export const operationServerMap: ServerMap = { + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + {{#servers}} + {{#-first}} + "{{{classname}}}.{{{nickname}}}": [ + {{/-first}} + { + url: "{{{url}}}", + description: "{{{description}}}{{^description}}No description provided{{/description}}", + {{#variables}} + {{#-first}} + variables: { + {{/-first}} + {{{name}}}: { + description: "{{{description}}}{{^description}}No description provided{{/description}}", + default_value: "{{{defaultValue}}}", + {{#enumValues}} + {{#-first}} + enum_values: [ + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ] + {{/-last}} + {{/enumValues}} + }{{^-last}},{{/-last}} + {{#-last}} + } + {{/-last}} + {{/variables}} + }{{^-last}},{{/-last}} + {{#-last}} + ], + {{/-last}} + {{/servers}} + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/common.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/common.mustache new file mode 100755 index 000000000000..d21186af6408 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/common.mustache @@ -0,0 +1,165 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +import type { Configuration } from "./configuration{{importFileExtension}}"; +import type { RequestArgs } from "./base{{importFileExtension}}"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import * as v from 'valibot'; +{{#withAWSV4Signature}} +import { aws4Interceptor } from "aws4-axios"; +{{/withAWSV4Signature}} +import { RequiredError } from "./base{{importFileExtension}}"; +{{#withNodeImports}} +import { URL, URLSearchParams } from 'url'; +{{/withNodeImports}} + +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +export const validateRequestParameters = function (functionName: string, schema: v.GenericSchema, requestParameters: unknown) { + const validationResult = v.safeParse(schema, requestParameters); + if (!validationResult.success) { + const firstIssue = validationResult.issues[0]; + const issuePath = firstIssue?.path?.map((pathItem: any) => String(pathItem.key)).join('.') || 'requestParameters'; + throw new RequiredError("requestParameters", `Invalid request parameters when calling ${functionName} at ${issuePath}.`); + } +} + +export const withParams = function (path: string, params: T) { + const paramMap = params as any; + return path.replace(/\{([^}]+)\}/g, (_, key: string) => { + const value = paramMap[key]; + if (value === undefined || value === null) { + return `{${key}}`; + } + return encodeURIComponent(String(value)); + }); +} + +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +{{#withAWSV4Signature}} +const aws4SignedInstances = new WeakSet(); + +export const setAWS4SignatureInterceptor = async function (axiosInstance: AxiosInstance, configuration?: Configuration) { + if (configuration && configuration.awsv4 && !aws4SignedInstances.has(axiosInstance)) { + const interceptor = aws4Interceptor({ + options: { + region: configuration.awsv4?.options?.region ?? process.env.AWS_REGION ?? 'us-east-1', + service: configuration.awsv4?.options?.service ?? 'execute-api', + }, + credentials: { + accessKeyId: configuration.awsv4?.credentials?.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: configuration.awsv4?.credentials?.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY, + sessionToken: configuration.awsv4?.credentials?.sessionToken ?? process.env.AWS_SESSION_TOKEN + }, + }); + axiosInstance.interceptors.request.use(interceptor); + aws4SignedInstances.add(axiosInstance); + } +} +{{/withAWSV4Signature}} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter) || parameter instanceof Set) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * JSON serialization helper function which replaces instances of unserializable types with serializable ones. + * This function will run for every key-value pair encountered by JSON.stringify while traversing an object. + * Converting a set to a string will return an empty object, so an intermediate conversion to an array is required. + */ +// @ts-ignore +export const replaceWithSerializableTypeIfNeeded = function(key: string, value: any) { + if (value instanceof Set) { + return Array.from(value); + } else { + return value; + } +} + +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}, replaceWithSerializableTypeIfNeeded) + : (value || ""); +} + +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (axios.defaults.baseURL ? '' : configuration?.basePath ?? basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/configuration.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/configuration.mustache new file mode 100644 index 000000000000..577907a1574b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/configuration.mustache @@ -0,0 +1,114 @@ +/* tslint:disable */ +{{>licenseInfo}} + +interface AWSv4Configuration { + options?: { + region?: string + service?: string + } + credentials?: { + accessKeyId?: string + secretAccessKey?: string, + sessionToken?: string + } +} + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + awsv4?: AWSv4Configuration; + basePath?: string; + serverIndex?: number; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + */ + username?: string; + /** + * parameter for basic security + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * parameter for aws4 signature security + * @param {Object} AWS4Signature - AWS4 Signature security + * @param {string} options.region - aws region + * @param {string} options.service - name of the service. + * @param {string} credentials.accessKeyId - aws access key id + * @param {string} credentials.secretAccessKey - aws access key + * @param {string} credentials.sessionToken - aws session token + * @memberof Configuration + */ + awsv4?: AWSv4Configuration; + /** + * override base path + */ + basePath?: string; + /** + * override server index + */ + serverIndex?: number; + /** + * base options for axios calls + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.awsv4 = param.awsv4; + this.basePath = param.basePath; + this.serverIndex = param.serverIndex; + this.baseOptions = { + ...param.baseOptions, + headers: { + {{#httpUserAgent}} + 'User-Agent': "{{httpUserAgent}}", + {{/httpUserAgent}} + ...param.baseOptions?.headers, + }, + }; + this.formDataCtor = param.formDataCtor; + } + + /** + * 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. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/git_push.sh.mustache new file mode 100755 index 000000000000..0e3776ae6dd4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/git_push.sh.mustache @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/gitignore b/modules/openapi-generator/src/main/resources/typescript-axios-slim/gitignore new file mode 100644 index 000000000000..149b57654723 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/index.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/index.mustache new file mode 100644 index 000000000000..fca806d2a6c5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/index.mustache @@ -0,0 +1,8 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +export * from "./api{{importFileExtension}}"; +export * from "./configuration{{importFileExtension}}"; +{{#withSeparateModelsAndApi}}export * from "./{{tsModelPackage}}{{#importFileExtension}}/index{{importFileExtension}}{{/importFileExtension}}";{{/withSeparateModelsAndApi}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/licenseInfo.mustache new file mode 100644 index 000000000000..be193d0c4fe8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/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/modules/openapi-generator/src/main/resources/typescript-axios-slim/model.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/model.mustache new file mode 100644 index 000000000000..4c4d4a73bdc8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/model.mustache @@ -0,0 +1,17 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +{{#withSeparateModelsAndApi}}{{#hasAllOf}}{{#allOf}} +// May contain unused imports in some cases +// @ts-ignore +import type { {{class}} } from './{{filename}}{{importFileExtension}}';{{/allOf}}{{/hasAllOf}}{{#hasOneOf}}{{#oneOf}} +// May contain unused imports in some cases +// @ts-ignore +import type { {{class}} } from './{{filename}}{{importFileExtension}}';{{/oneOf}}{{/hasOneOf}}{{^hasAllOf}}{{^hasOneOf}}{{#imports}} +// May contain unused imports in some cases +// @ts-ignore +import type { {{class}} } from './{{filename}}{{importFileExtension}}';{{/imports}}{{/hasOneOf}}{{/hasAllOf}}{{/withSeparateModelsAndApi}} +{{#models}}{{#model}} +{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#allOf}}{{#-first}}{{>modelAllOf}}{{/-first}}{{/allOf}}{{^isEnum}}{{^oneOf}}{{^allOf}}{{>modelGeneric}}{{/allOf}}{{/oneOf}}{{/isEnum}} +{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelAllOf.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelAllOf.mustache new file mode 100644 index 000000000000..aee84ee6bd59 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelAllOf.mustache @@ -0,0 +1,5 @@ +/** + * @type {{classname}}{{#description}} + * {{{.}}}{{/description}} + */ +export type {{classname}} = {{#allOf}}{{{.}}}{{^-last}} & {{/-last}}{{/allOf}}; diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelEnum.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelEnum.mustache new file mode 100644 index 000000000000..8473f122bbec --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelEnum.mustache @@ -0,0 +1,19 @@ +{{#description}} +/** + * {{{description}}} + */ +{{/description}} +{{#isBoolean}} +export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}} +{{/isBoolean}} + +{{^isBoolean}} +{{^stringEnums}} +{{>modelObjectEnum}} + +{{/stringEnums}} +{{#stringEnums}} +{{>modelStringEnum}} + +{{/stringEnums}} +{{/isBoolean}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelGeneric.mustache new file mode 100644 index 000000000000..16d43b58d66b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelGeneric.mustache @@ -0,0 +1,65 @@ +{{#description}} +/** + * {{{description}}} + */ +{{/description}} +export interface {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ +{{#additionalPropertiesType}} + [key: string]: {{{additionalPropertiesType}}}{{#additionalPropertiesIsAnyType}}{{#hasVars}} | any{{/hasVars}}{{/additionalPropertiesIsAnyType}}; + +{{/additionalPropertiesType}} +{{#vars}} + {{#description}} + /** + * {{{description}}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ + {{/description}} + {{^description}} + {{#deprecated}} + /** + * @deprecated + */ + {{/deprecated}} + {{/description}} + '{{baseName}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; +{{/vars}} +}{{#hasEnums}} + +{{#vars}} +{{#isEnum}} +{{#stringEnums}} +export enum {{enumName}} { +{{#allowableValues}} + {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}} +{{^stringEnums}} +export const {{enumName}} = { +{{#allowableValues}} + {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; + +export type {{enumName}} = typeof {{enumName}}[keyof typeof {{enumName}}]; +{{/stringEnums}} +{{/isEnum}} +{{/vars}} +{{/hasEnums}} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelIndex.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelIndex.mustache new file mode 100644 index 000000000000..7b40cb45cd83 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelIndex.mustache @@ -0,0 +1,2 @@ +{{#models}}{{#model}}export * from './{{classFilename}}{{importFileExtension}}';{{/model}} +{{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelObjectEnum.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelObjectEnum.mustache new file mode 100644 index 000000000000..ce2c8208f37c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelObjectEnum.mustache @@ -0,0 +1,14 @@ +export const {{classname}} = { +{{#allowableValues}} + {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; + +export type {{classname}} = typeof {{classname}}[keyof typeof {{classname}}]; diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelOneOf.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelOneOf.mustache new file mode 100644 index 000000000000..3fefd465c88f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelOneOf.mustache @@ -0,0 +1,14 @@ +/** + * @type {{classname}}{{#description}} + * {{{.}}}{{/description}} + */ +export type {{classname}} = {{#discriminator}}{{! + +discriminator with mapped models - TypeScript discriminating union +}}{{#mappedModels}}{ {{discriminator.propertyName}}: '{{mappingName}}' } & {{modelName}}{{^-last}} | {{/-last}}{{/mappedModels}}{{! + +discriminator only - fallback to not use the discriminator. Default model names are available but possibility of having null/nullable values could introduce more edge cases +}}{{^mappedModels}}{{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}}{{/mappedModels}}{{/discriminator}}{{! + +plain oneOf +}}{{^discriminator}}{{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}}{{/discriminator}}; diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelStringEnum.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelStringEnum.mustache new file mode 100644 index 000000000000..c2886f750f5c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/modelStringEnum.mustache @@ -0,0 +1,12 @@ +export enum {{classname}} { +{{#allowableValues}} + {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/model_doc.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/model_doc.mustache new file mode 100644 index 000000000000..f492a35e7caf --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/model_doc.mustache @@ -0,0 +1,34 @@ +{{#models}}{{#model}}# {{classname}} + +{{#description}}{{&description}} +{{/description}} + +{{^isEnum}} +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} + +## Example + +```typescript +import { {{classname}} } from '{{#npmName}}{{.}}{{/npmName}}{{^npmName}}./api{{/npmName}}'; + +const instance: {{classname}} = { +{{#vars}} + {{name}}, +{{/vars}} +}; +``` + +{{/isEnum}} +{{#isEnum}} +## Enum +{{#allowableValues}}{{#enumVars}} +* `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} +{{/isEnum}} +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) +{{/model}}{{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/npmignore b/modules/openapi-generator/src/main/resources/typescript-axios-slim/npmignore new file mode 100644 index 000000000000..999d88df6939 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/npmignore @@ -0,0 +1 @@ +# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/package.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/package.mustache new file mode 100644 index 000000000000..bc5c2af21771 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/package.mustache @@ -0,0 +1,44 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "author": "OpenAPI-Generator Contributors", + "repository": { + "type": "git", + "url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git" + }, + "keywords": [ + "axios", + "typescript", + "openapi-client", + "openapi-generator", + "{{npmName}}" + ], + "license": "{{licenseName}}", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", +{{#supportsES6}} + "module": "./dist/esm/index.js", + "sideEffects": false, +{{/supportsES6}} + "scripts": { + "build": "tsc{{#supportsES6}} && tsc -p tsconfig.esm.json{{/supportsES6}}", + "prepare": "npm run build" + }, + "dependencies": { + "axios": "{{axiosVersion}}", + "valibot": "^1.1.0" + {{#withAWSV4Signature}} + "aws4-axios": "^3.3.4" + {{/withAWSV4Signature}} + }, + "devDependencies": { + "@types/node": "12.11.5 - 12.20.42", + "typescript": "^4.0 || ^5.0" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig": { + "registry": "{{npmRepository}}" + } +{{/npmRepository}} +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.esm.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.esm.mustache new file mode 100644 index 000000000000..2c0331cce040 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.esm.mustache @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm" + } +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.mustache b/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.mustache new file mode 100644 index 000000000000..d0ebbd47a6b6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-axios-slim/tsconfig.mustache @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "{{#supportsES6}}ES6{{/supportsES6}}{{^supportsES6}}ES5{{/supportsES6}}", + "module": "commonjs", + "noImplicitAny": true, + "outDir": "dist", + "rootDir": ".", + {{#supportsES6}} + "moduleResolution": "node", + {{/supportsES6}} + {{^supportsES6}} + "lib": [ + "es6", + "dom" + ], + {{/supportsES6}} + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosSlimParityTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosSlimParityTest.java new file mode 100644 index 000000000000..18c3ae75cf14 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosSlimParityTest.java @@ -0,0 +1,369 @@ +package org.openapitools.codegen.typescript.axios; + +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.typescript.TypeScriptGroups; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +@Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_AXIOS}) +public class TypeScriptAxiosSlimParityTest { + private static final String EDGE_CASE_SPEC = "src/test/resources/3_0/typescript-axios-slim/identity-edge-cases.yaml"; + + private static final Pattern REQUEST_INTERFACE_PATTERN = Pattern.compile("export interface (\\w+Request) \\{([\\s\\S]*?)\\n\\}"); + private static final Pattern API_INTERFACE_PATTERN = Pattern.compile("export interface (\\w+Interface) \\{([\\s\\S]*?)\\n\\}"); + private static final Pattern API_INTERFACE_METHOD_PATTERN = Pattern.compile("^\\s*(\\w+)\\((.*)\\):\\s*(.+);\\s*$", Pattern.MULTILINE); + private static final Pattern ENUM_DECL_PATTERN = Pattern.compile("export enum (\\w+) \\{([\\s\\S]*?)\\n\\}"); + private static final Pattern CONST_ENUM_DECL_PATTERN = Pattern.compile("export const (\\w+) = \\{([\\s\\S]*?)\\} as const;"); + private static final Pattern WRAPPED_RETURN_TYPE_PATTERN = Pattern.compile("^(AxiosPromise|Promise|AxiosResponse)\\s*<\\s*(.+)\\s*>$"); + + private static final List API_DIR_CANDIDATES = Arrays.asList("api", "apis"); + private static final List MODEL_DIR_CANDIDATES = Arrays.asList("model", "models"); + + private static final Set EXPECTED_EDGE_METHODS = new TreeSet<>(Arrays.asList( + "getUserByCompany", + "createUserByCompany", + "deleteUserByCompany", + "aliasLookup", + "listReports", + "submitForm", + "uploadEvidence", + "healthCheck", + "searchUsers", + "getUnionPayload", + "getWithQueryApiKey" + )); + + private static final Consumer NO_CUSTOMIZER = cfg -> { + }; + + @Test(description = "identity: comprehensive edge-case spec across option matrix") + public void shouldKeepIdentityForComprehensiveEdgeCaseSpecAcrossOptionMatrix() throws Exception { + List scenarios = Arrays.asList( + new OptionScenario("default", NO_CUSTOMIZER), + new OptionScenario("string-enums", cfg -> cfg.addAdditionalProperty("stringEnums", true)), + new OptionScenario("node-imports", cfg -> cfg.addAdditionalProperty("withNodeImports", true)), + new OptionScenario("aws-v4-signature", cfg -> cfg.addAdditionalProperty("withAWSV4Signature", true)), + new OptionScenario("separate-models-and-api", cfg -> cfg + .addAdditionalProperty("withSeparateModelsAndApi", true) + .addAdditionalProperty("apiPackage", "api") + .addAdditionalProperty("modelPackage", "model")), + new OptionScenario("import-js-extension", cfg -> cfg.addAdditionalProperty("importFileExtension", ".js")), + new OptionScenario("square-bracket-form-arrays", cfg -> cfg.addAdditionalProperty("useSquareBracketsInArrayNames", true)) + ); + + for (OptionScenario scenario : scenarios) { + IdentitySurface axiosSurface = generateIdentity("typescript-axios", EDGE_CASE_SPEC, scenario.customizer); + IdentitySurface slimSurface = generateIdentity("typescript-axios-slim", EDGE_CASE_SPEC, scenario.customizer); + + assertIdentitySurfaceEquals(scenario.name, axiosSurface, slimSurface); + assertTrue(axiosSurface.allMethodNames().containsAll(EXPECTED_EDGE_METHODS), + "Scenario " + scenario.name + " did not generate the expected operation set"); + } + } + + @Test(description = "identity: known regression fixtures (req/res surface)") + public void shouldKeepIdentityAcrossKnownRegressionFixtures() throws Exception { + List scenarios = Arrays.asList( + new SpecScenario("petstore", "src/test/resources/3_0/petstore.yaml", NO_CUSTOMIZER), + new SpecScenario("nullable-required", "src/test/resources/3_0/petstore-with-nullable-required.yaml", NO_CUSTOMIZER), + new SpecScenario("multiple-2xx", "src/test/resources/3_0/petstore-multiple-2xx-responses.yaml", NO_CUSTOMIZER), + new SpecScenario("query-form", "src/test/resources/3_0/query-param-form.yaml", NO_CUSTOMIZER), + new SpecScenario("query-deep-object", "src/test/resources/3_0/query-param-deep-object.yaml", NO_CUSTOMIZER), + new SpecScenario("deepobject", "src/test/resources/3_0/deepobject.yaml", NO_CUSTOMIZER), + new SpecScenario("parameter-name-mapping", "src/test/resources/3_0/name-parameter-mappings.yaml", NO_CUSTOMIZER), + new SpecScenario("shared-parameters-3_1", "src/test/resources/3_1/common-parameters.yaml", NO_CUSTOMIZER), + new SpecScenario("multipart-enum-3_1", "src/test/resources/3_1/enum-in-multipart.yaml", NO_CUSTOMIZER), + new SpecScenario("map-array-inner-enum", "src/test/resources/3_0/issue_19393_map_of_inner_enum.yaml", NO_CUSTOMIZER), + new SpecScenario("generic-type-mapping", "src/test/resources/3_1/issue_21317.yaml", cfg -> cfg + .addTypeMapping("UserSummary", "Pick") + .addTypeMapping("object", "Record")) + ); + + for (SpecScenario scenario : scenarios) { + IdentitySurface axiosSurface = generateIdentity("typescript-axios", scenario.specPath, scenario.customizer); + IdentitySurface slimSurface = generateIdentity("typescript-axios-slim", scenario.specPath, scenario.customizer); + assertIdentitySurfaceEquals(scenario.name, axiosSurface, slimSurface); + } + } + + @Test(description = "identity: mapped generic response signature is preserved") + public void shouldPreserveMappedGenericResponseSignature() throws Exception { + Consumer mappedTypeCustomizer = cfg -> cfg + .addTypeMapping("UserSummary", "Pick") + .addTypeMapping("object", "Record"); + + IdentitySurface axiosSurface = generateIdentity("typescript-axios", "src/test/resources/3_1/issue_21317.yaml", mappedTypeCustomizer); + IdentitySurface slimSurface = generateIdentity("typescript-axios-slim", "src/test/resources/3_1/issue_21317.yaml", mappedTypeCustomizer); + + assertIdentitySurfaceEquals("generic-type-mapping-signature", axiosSurface, slimSurface); + } + + @Test(description = "slim: object-oriented methods return payload data directly") + public void shouldReturnPayloadDataFromObjectOrientedMethods() throws Exception { + IdentitySurface slimSurface = generateIdentity("typescript-axios-slim", EDGE_CASE_SPEC, NO_CUSTOMIZER); + String apiSource = String.join(" ", slimSurface.apiFiles.values()); + + assertTrue(apiSource.contains("): Promise<"), "Slim API methods should return Promise payload types"); + assertFalse(apiSource.contains("AxiosPromise<"), "Slim API interface should not expose AxiosPromise return wrappers"); + assertFalse(apiSource.contains("Promise>"); + assertTrue(apiSource.contains("return localVarResponse.data;"), "Slim API class should resolve axios response data directly"); + } + + @Test(description = "slim: useSingleRequestParameter remains enabled even if configured false") + public void shouldForceSingleRequestParameterForSlimGenerator() throws Exception { + Consumer forceSingleRequestFalse = cfg -> cfg.addAdditionalProperty("useSingleRequestParameter", false); + + IdentitySurface axiosSurface = generateIdentity("typescript-axios", EDGE_CASE_SPEC, NO_CUSTOMIZER); + IdentitySurface slimSurface = generateIdentity("typescript-axios-slim", EDGE_CASE_SPEC, forceSingleRequestFalse); + + assertIdentitySurfaceEquals("forced-single-request-parameter", axiosSurface, slimSurface); + } + + private IdentitySurface generateIdentity(String generatorName, String specPath, Consumer customizer) throws Exception { + File output = Files.createTempDirectory("typescript_axios_identity_").toFile().getCanonicalFile(); + output.deleteOnExit(); + + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(generatorName) + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath()) + .addAdditionalProperty("withInterfaces", true) + .addAdditionalProperty("useSingleRequestParameter", true); + customizer.accept(configurator); + + ClientOptInput clientOptInput = configurator.toClientOptInput(); + new DefaultGenerator().opts(clientOptInput).generate(); + + return extractIdentity(output.toPath()); + } + + private IdentitySurface extractIdentity(Path outputDir) throws IOException { + IdentitySurface surface = new IdentitySurface(); + + List apiFiles = collectApiFiles(outputDir); + assertFalse(apiFiles.isEmpty(), "No API source files were generated under " + outputDir); + + for (Path apiFile : apiFiles) { + String content = Files.readString(apiFile); + String relativePath = normalizePath(outputDir.relativize(apiFile)); + surface.apiFiles.put(relativePath, normalize(content)); + + extractRequestInterfaces(surface.requestInterfaces, content); + extractApiInterfaceMethods(surface.apiInterfaceMethods, content); + extractOperationEnums(surface.operationEnums, content); + } + + List modelFiles = collectModelFiles(outputDir); + for (Path modelFile : modelFiles) { + String relativePath = normalizePath(outputDir.relativize(modelFile)); + surface.modelFiles.put(relativePath, normalize(Files.readString(modelFile))); + } + + assertTrue(!surface.requestInterfaces.isEmpty() || !surface.apiInterfaceMethods.isEmpty(), + "No comparable request/response surface was extracted from generated sources under " + outputDir); + + return surface; + } + + private List collectApiFiles(Path outputDir) throws IOException { + LinkedHashSet files = new LinkedHashSet<>(); + Path rootApi = outputDir.resolve("api.ts"); + if (Files.exists(rootApi)) { + files.add(rootApi); + } + + for (String dirName : API_DIR_CANDIDATES) { + Path dir = outputDir.resolve(dirName); + if (Files.isDirectory(dir)) { + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith(".ts")) + .forEach(files::add); + } + } + + List sorted = new ArrayList<>(files); + Collections.sort(sorted); + return sorted; + } + + private List collectModelFiles(Path outputDir) throws IOException { + LinkedHashSet files = new LinkedHashSet<>(); + for (String dirName : MODEL_DIR_CANDIDATES) { + Path dir = outputDir.resolve(dirName); + if (Files.isDirectory(dir)) { + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith(".ts")) + .forEach(files::add); + } + } + + List sorted = new ArrayList<>(files); + Collections.sort(sorted); + return sorted; + } + + private void extractRequestInterfaces(Map target, String apiSource) { + Matcher matcher = REQUEST_INTERFACE_PATTERN.matcher(apiSource); + while (matcher.find()) { + target.put(matcher.group(1), normalize(matcher.group(2))); + } + } + + private void extractApiInterfaceMethods(Map> target, String apiSource) { + Matcher interfaceMatcher = API_INTERFACE_PATTERN.matcher(apiSource); + while (interfaceMatcher.find()) { + String interfaceName = interfaceMatcher.group(1); + String interfaceBody = interfaceMatcher.group(2); + Matcher methodMatcher = API_INTERFACE_METHOD_PATTERN.matcher(interfaceBody); + while (methodMatcher.find()) { + String methodName = methodMatcher.group(1); + String params = normalize(methodMatcher.group(2)); + String returnType = normalizeComparableReturnType(methodMatcher.group(3)); + target.computeIfAbsent(interfaceName, ignored -> new TreeSet<>()) + .add(methodName + "(" + params + "):" + returnType); + } + } + } + + private String normalizeComparableReturnType(String returnType) { + String current = normalize(returnType); + while (true) { + Matcher wrapperMatcher = WRAPPED_RETURN_TYPE_PATTERN.matcher(current); + if (!wrapperMatcher.matches()) { + return current; + } + + String wrapperType = wrapperMatcher.group(1); + String innerType = wrapperMatcher.group(2).trim(); + if ("AxiosResponse".equals(wrapperType)) { + innerType = firstTopLevelTypeArgument(innerType); + } + + if (innerType.equals(current)) { + return innerType; + } + current = innerType; + } + } + + private String firstTopLevelTypeArgument(String typeArguments) { + int depth = 0; + for (int i = 0; i < typeArguments.length(); i++) { + char current = typeArguments.charAt(i); + if (current == '<') { + depth++; + } else if (current == '>') { + depth = Math.max(0, depth - 1); + } else if (current == ',' && depth == 0) { + return typeArguments.substring(0, i).trim(); + } + } + + return typeArguments.trim(); + } + + private void extractOperationEnums(Map target, String apiSource) { + Matcher enumMatcher = ENUM_DECL_PATTERN.matcher(apiSource); + while (enumMatcher.find()) { + target.put("enum:" + enumMatcher.group(1), normalize(enumMatcher.group(2))); + } + + Matcher constEnumMatcher = CONST_ENUM_DECL_PATTERN.matcher(apiSource); + while (constEnumMatcher.find()) { + target.put("const:" + constEnumMatcher.group(1), normalize(constEnumMatcher.group(2))); + } + } + + private void assertIdentitySurfaceEquals(String scenarioName, IdentitySurface expectedAxios, IdentitySurface actualSlim) { + assertEquals(actualSlim.requestInterfaces, expectedAxios.requestInterfaces, + scenarioName + ": request interface identity mismatch"); + assertEquals(actualSlim.apiInterfaceMethods, expectedAxios.apiInterfaceMethods, + scenarioName + ": API interface method identity mismatch"); + assertEquals(actualSlim.operationEnums, expectedAxios.operationEnums, + scenarioName + ": enum identity mismatch"); + assertEquals(actualSlim.modelFiles, expectedAxios.modelFiles, + scenarioName + ": model file identity mismatch"); + } + + private static String normalize(String content) { + return content + .replace("\r\n", "\n") + .replace('\r', '\n') + .replaceAll("\\s+", " ") + .trim(); + } + + private static String normalizePath(Path path) { + return path.toString().replace('\\', '/'); + } + + private static final class IdentitySurface { + private final Map requestInterfaces = new TreeMap<>(); + private final Map> apiInterfaceMethods = new TreeMap<>(); + private final Map operationEnums = new TreeMap<>(); + private final Map modelFiles = new TreeMap<>(); + private final Map apiFiles = new TreeMap<>(); + + private Set allMethodNames() { + Set names = new TreeSet<>(); + + for (Set signatures : apiInterfaceMethods.values()) { + for (String signature : signatures) { + names.add(methodNameFromSignature(signature)); + } + } + return names; + } + + private String methodNameFromSignature(String signature) { + int index = signature.indexOf('('); + return index >= 0 ? signature.substring(0, index) : signature; + } + } + + private static final class OptionScenario { + private final String name; + private final Consumer customizer; + + private OptionScenario(String name, Consumer customizer) { + this.name = name; + this.customizer = customizer; + } + } + + private static final class SpecScenario { + private final String name; + private final String specPath; + private final Consumer customizer; + + private SpecScenario(String name, String specPath, Consumer customizer) { + this.name = name; + this.specPath = specPath; + this.customizer = customizer; + } + } +} diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript-axios-slim/identity-edge-cases.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript-axios-slim/identity-edge-cases.yaml new file mode 100644 index 000000000000..9e1a7faf4c1e --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/typescript-axios-slim/identity-edge-cases.yaml @@ -0,0 +1,414 @@ +openapi: 3.0.3 +info: + title: TypeScript Axios Slim Identity Edge Cases + version: 1.0.0 +servers: + - url: https://example.test + +paths: + /companies/{company-id}/users/{user_id}: + get: + tags: + - companies + operationId: getUserByCompany + parameters: + - $ref: '#/components/parameters/CompanyId' + - $ref: '#/components/parameters/UserId' + - name: include_deleted + in: query + required: false + schema: + type: boolean + - name: tags + in: query + required: false + style: form + explode: false + schema: + type: array + items: + type: string + - name: attributes + in: query + required: false + style: deepObject + explode: true + schema: + type: object + additionalProperties: + type: string + - name: X-Trace-Id + in: header + required: false + schema: + type: string + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - ApiKeyAuthHeader: [] + - BearerAuth: [] + post: + tags: + - companies + operationId: createUserByCompany + parameters: + - $ref: '#/components/parameters/CompanyId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserCreateRequest' + responses: + '201': + description: created + content: + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - OAuth2: + - users:write + delete: + tags: + - companies + operationId: deleteUserByCompany + parameters: + - $ref: '#/components/parameters/CompanyId' + - $ref: '#/components/parameters/UserId' + - name: X-Flags + in: header + required: false + schema: + type: array + items: + type: string + responses: + '204': + description: deleted + + /companies/{company-id}/aliases/{user-id}: + get: + tags: + - companies + operationId: aliasLookup + parameters: + - $ref: '#/components/parameters/CompanyId' + - name: user-id + in: path + required: true + schema: + type: string + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/User' + + /reports: + get: + tags: + - analytics + operationId: listReports + parameters: + - name: reportDate + in: query + required: false + schema: + type: string + format: date + - name: at + in: query + required: false + schema: + type: string + format: date-time + - name: status + in: query + required: false + schema: + type: string + enum: [pending, approved, rejected] + - name: page + in: query + required: false + schema: + type: integer + format: int32 + nullable: true + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/ReportList' + + /submit: + post: + tags: + - forms + operationId: submitForm + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - name + - count + properties: + name: + type: string + count: + type: integer + format: int32 + labels: + type: array + items: + type: string + happenedAt: + type: string + format: date + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/SubmitResponse' + + /upload: + post: + tags: + - forms + operationId: uploadEvidence + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - file + - category + properties: + file: + type: string + format: binary + category: + type: string + enum: [image, document] + tags: + type: array + items: + type: string + metadata: + type: object + additionalProperties: + type: string + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/UploadResult' + + /health: + get: + tags: + - system + operationId: healthCheck + responses: + '204': + description: no content + + /search: + get: + tags: + - analytics + operationId: searchUsers + parameters: + - name: term + in: query + required: false + schema: + type: string + nullable: true + - name: limit + in: query + required: false + schema: + type: integer + format: int32 + default: 10 + responses: + '200': + description: ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + + /union: + get: + tags: + - analytics + operationId: getUnionPayload + responses: + '200': + description: ok + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/UnionA' + - $ref: '#/components/schemas/UnionB' + + /secured/query-key: + get: + tags: + - security + operationId: getWithQueryApiKey + security: + - ApiKeyAuthQuery: [] + responses: + '200': + description: ok + content: + application/json: + schema: + type: string + +components: + parameters: + CompanyId: + name: company-id + in: path + required: true + schema: + type: string + UserId: + name: user_id + in: path + required: true + schema: + type: string + + securitySchemes: + ApiKeyAuthHeader: + type: apiKey + in: header + name: x-api-key + ApiKeyAuthQuery: + type: apiKey + in: query + name: api_key + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + OAuth2: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://example.test/oauth/token + scopes: + users:write: create users + + schemas: + User: + type: object + required: + - id + - email + properties: + id: + type: string + email: + type: string + displayName: + type: string + nullable: true + UserCreateRequest: + type: object + required: + - email + properties: + email: + type: string + displayName: + type: string + ErrorResponse: + type: object + required: + - code + properties: + code: + type: string + message: + type: string + ReportList: + type: object + properties: + items: + type: array + items: + type: string + SubmitResponse: + type: object + properties: + accepted: + type: boolean + UploadResult: + type: object + properties: + fileId: + type: string + kind: + type: string + enum: [image, document] + UnionA: + type: object + required: + - kind + properties: + kind: + type: string + enum: [A] + aValue: + type: string + UnionB: + type: object + required: + - kind + properties: + kind: + type: string + enum: [B] + bValue: + type: integer + format: int32