From cecf7cab37d32ac00e9d3fdf2316ae7aac1f9c69 Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Fri, 6 Sep 2024 14:24:27 +0100 Subject: [PATCH 1/4] fix: `nullable` enums should not serialize a `null` value to a string --- ...typescript-consolidated-nullable-enum.yaml | 4 + .../openapitools/codegen/DefaultCodegen.java | 5 +- .../resources/typescript/model/model.mustache | 2 + .../codegen/php/PhpModelTest.java | 4 + .../fetch/TypeScriptFetchModelTest.java | 4 + .../3_0/typescript/19027-regression.yaml | 31 ++ .../builds/nullable-enum/.gitignore | 1 + .../nullable-enum/.openapi-generator-ignore | 23 ++ .../nullable-enum/.openapi-generator/FILES | 24 ++ .../nullable-enum/.openapi-generator/VERSION | 1 + .../builds/nullable-enum/DefaultApi.md | 57 ++++ .../typescript/builds/nullable-enum/README.md | 80 ++++++ .../builds/nullable-enum/apis/DefaultApi.ts | 73 +++++ .../builds/nullable-enum/apis/baseapi.ts | 37 +++ .../builds/nullable-enum/apis/exception.ts | 15 + .../builds/nullable-enum/auth/auth.ts | 51 ++++ .../builds/nullable-enum/configuration.ts | 82 ++++++ .../builds/nullable-enum/git_push.sh | 51 ++++ .../builds/nullable-enum/http/http.ts | 254 ++++++++++++++++ .../nullable-enum/http/isomorphic-fetch.ts | 32 +++ .../typescript/builds/nullable-enum/index.ts | 12 + .../builds/nullable-enum/middleware.ts | 66 +++++ .../nullable-enum/models/ObjectSerializer.ts | 270 ++++++++++++++++++ .../builds/nullable-enum/models/Response.ts | 45 +++ .../builds/nullable-enum/models/all.ts | 1 + .../builds/nullable-enum/package.json | 42 +++ .../builds/nullable-enum/rxjsStub.ts | 27 ++ .../builds/nullable-enum/servers.ts | 55 ++++ .../builds/nullable-enum/tsconfig.json | 28 ++ .../nullable-enum/types/ObjectParamAPI.ts | 33 +++ .../nullable-enum/types/ObservableAPI.ts | 50 ++++ .../builds/nullable-enum/types/PromiseAPI.ts | 37 +++ .../typescript/builds/nullable-enum/util.ts | 37 +++ 33 files changed, 1533 insertions(+), 1 deletion(-) create mode 100644 bin/configs/typescript-consolidated-nullable-enum.yaml create mode 100644 modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/.gitignore create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator-ignore create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/FILES create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/VERSION create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/DefaultApi.md create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/README.md create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/DefaultApi.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/baseapi.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/exception.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/auth/auth.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/configuration.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/git_push.sh create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/http.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/isomorphic-fetch.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/index.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/middleware.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/ObjectSerializer.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/all.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/package.json create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/rxjsStub.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/servers.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/tsconfig.json create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObjectParamAPI.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObservableAPI.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/PromiseAPI.ts create mode 100644 samples/openapi3/client/petstore/typescript/builds/nullable-enum/util.ts diff --git a/bin/configs/typescript-consolidated-nullable-enum.yaml b/bin/configs/typescript-consolidated-nullable-enum.yaml new file mode 100644 index 000000000000..7695b05d4c42 --- /dev/null +++ b/bin/configs/typescript-consolidated-nullable-enum.yaml @@ -0,0 +1,4 @@ +generatorName: typescript +outputDir: samples/openapi3/client/petstore/typescript/builds/nullable-enum +inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml +templateDir: modules/openapi-generator/src/main/resources/typescript diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 6e252298d08a..f27e4a400e48 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3947,7 +3947,9 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo List _enum = p.getEnum(); property._enum = new ArrayList<>(); for (Object i : _enum) { - property._enum.add(String.valueOf(i)); + if(i != null) { + property._enum.add(String.valueOf(i)); + } } property.isEnum = true; property.isInnerEnum = true; @@ -6641,6 +6643,7 @@ protected List> buildEnumVars(List values, String da enumVar.put("name", finalEnumName); enumVar.put("value", toEnumValue(String.valueOf(value), dataType)); + enumVar.put("isValueNull", value == null); enumVar.put("isString", isDataTypeString(dataType)); // TODO: add isNumeric enumVars.add(enumVar); diff --git a/modules/openapi-generator/src/main/resources/typescript/model/model.mustache b/modules/openapi-generator/src/main/resources/typescript/model/model.mustache index 4f30fea765b0..0d7ea3d6aa72 100644 --- a/modules/openapi-generator/src/main/resources/typescript/model/model.mustache +++ b/modules/openapi-generator/src/main/resources/typescript/model/model.mustache @@ -89,7 +89,9 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ export enum {{classname}}{{enumName}} { {{#allowableValues}} {{#enumVars}} + {{^isValueNull}} {{name}} = {{{value}}}{{^-last}},{{/-last}} + {{/isValueNull}} {{/enumVars}} {{/allowableValues}} } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java index 39b3bb7870e8..70bfb85f2b60 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java @@ -315,10 +315,12 @@ public void enumArrayModelTest() { fish.put("name", "FISH"); fish.put("value", "\'fish\'"); fish.put("isString", true); + fish.put("isValueNull", false); HashMap crab= new HashMap(); crab.put("name", "CRAB"); crab.put("value", "\'crab\'"); crab.put("isString", true); + crab.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(fish, crab)); // assert inner items @@ -351,10 +353,12 @@ public void enumModelValueTest() { one.put("name", "1"); one.put("value", "1"); one.put("isString", false); + one.put("isValueNull", false); HashMap minusOne = new HashMap(); minusOne.put("name", "MINUS_1"); minusOne.put("value", "-1"); minusOne.put("isString", false); + minusOne.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(one, minusOne)); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java index a7a3e644f0bd..082998e12fdc 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java @@ -384,10 +384,12 @@ public void enumArrayModelTest() { fish.put("name", "Fish"); fish.put("value", "'fish'"); fish.put("isString", false); + fish.put("isValueNull", false); HashMap crab = new HashMap(); crab.put("name", "Crab"); crab.put("value", "'crab'"); crab.put("isString", false); + crab.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(fish, crab)); // assert inner items @@ -424,10 +426,12 @@ public void enumModelValueTest() { one.put("name", "NUMBER_1"); one.put("value", "1"); one.put("isString", false); + one.put("isValueNull", false); HashMap minusOne = new HashMap(); minusOne.put("name", "NUMBER_MINUS_1"); minusOne.put("value", "-1"); minusOne.put("isString", false); + minusOne.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(one, minusOne)); //IMPORTANT: these are not final enum values, which may be further updated diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml new file mode 100644 index 000000000000..315e6249be49 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Sample for uniqueItems +servers: + - url: http://localhost:3000 +paths: + /unique-items: + get: + operationId: unique_items + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Response' +components: + schemas: + Response: + type: object + properties: + enrichmentSource: + description: The source of the data in this Field (if it is enriched) + enum: + - "affinity-data" + - "dealroom" + - null + example: "affinity-data" + type: "string" + nullable: true diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.gitignore b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.gitignore @@ -0,0 +1 @@ +dist diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator-ignore b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/FILES b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/FILES new file mode 100644 index 000000000000..c1e2d850dd8b --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/FILES @@ -0,0 +1,24 @@ +.gitignore +DefaultApi.md +README.md +apis/DefaultApi.ts +apis/baseapi.ts +apis/exception.ts +auth/auth.ts +configuration.ts +git_push.sh +http/http.ts +http/isomorphic-fetch.ts +index.ts +middleware.ts +models/ObjectSerializer.ts +models/Response.ts +models/all.ts +package.json +rxjsStub.ts +servers.ts +tsconfig.json +types/ObjectParamAPI.ts +types/ObservableAPI.ts +types/PromiseAPI.ts +util.ts diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/VERSION b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/VERSION new file mode 100644 index 000000000000..17f2442ff3bc --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.9.0-SNAPSHOT diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/DefaultApi.md b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/DefaultApi.md new file mode 100644 index 000000000000..8c21f808c3db --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/DefaultApi.md @@ -0,0 +1,57 @@ +# .DefaultApi + +All URIs are relative to *http://localhost:3000* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**uniqueItems**](DefaultApi.md#uniqueItems) | **GET** /unique-items | + + +# **uniqueItems** +> Response uniqueItems() + + +### Example + + +```typescript +import { } from ''; +import * as fs from 'fs'; + +const configuration = .createConfiguration(); +const apiInstance = new .DefaultApi(configuration); + +let body:any = {}; + +apiInstance.uniqueItems(body).then((data:any) => { + console.log('API called successfully. Returned data: ' + data); +}).catch((error:any) => console.error(error)); +``` + + +### Parameters +This endpoint does not need any parameter. + + +### Return type + +**Response** + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | OK | - | + +[[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) + + diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/README.md b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/README.md new file mode 100644 index 000000000000..f1f2196cf0f9 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/README.md @@ -0,0 +1,80 @@ +## @ + +This generator creates TypeScript/JavaScript client that utilizes fetch-api. + +### 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 @ --save +``` + +_unPublished (not recommended):_ + +``` +npm install PATH_TO_GENERATED_PACKAGE --save +``` + +### Usage + +Below code snippet shows exemplary usage of the configuration and the API based +on the typical `PetStore` example used for OpenAPI. + +``` +import * as your_api from 'your_api_package' + +// Covers all auth methods included in your OpenAPI yaml definition +const authConfig: your_api.AuthMethodsConfiguration = { + "api_key": "YOUR_API_KEY" +} + +// Implements a simple middleware to modify requests before (`pre`) they are sent +// and after (`post`) they have been received +class Test implements your_api.Middleware { + pre(context: your_api.RequestContext): Promise { + // Modify context here and return + return Promise.resolve(context); + } + + post(context: your_api.ResponseContext): Promise { + return Promise.resolve(context); + } + +} + +// Create configuration parameter object +const configurationParameters = { + httpApi: new your_api.JQueryHttpLibrary(), // Can also be ignored - default is usually fine + baseServer: your_api.servers[0], // First server is default + authMethods: authConfig, // No auth is default + promiseMiddleware: [new Test()], +} + +// Convert to actual configuration +const config = your_api.createConfiguration(configurationParameters); + +// Use configuration with your_api +const api = new your_api.PetApi(config); +your_api.Pet p = new your_api.Pet(); +p.name = "My new pet"; +p.photoUrls = []; +p.tags = []; +p.status = "available"; +Promise createdPet = api.addPet(p); + +``` diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/DefaultApi.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/DefaultApi.ts new file mode 100644 index 000000000000..db62475226d0 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/DefaultApi.ts @@ -0,0 +1,73 @@ +// TODO: better import syntax? +import {BaseAPIRequestFactory, RequiredError, COLLECTION_FORMATS} from './baseapi'; +import {Configuration} from '../configuration'; +import {RequestContext, HttpMethod, ResponseContext, HttpFile, HttpInfo} from '../http/http'; +import {ObjectSerializer} from '../models/ObjectSerializer'; +import {ApiException} from './exception'; +import {canConsumeForm, isCodeInRange} from '../util'; +import {SecurityAuthentication} from '../auth/auth'; + + +import { Response } from '../models/Response'; + +/** + * no description + */ +export class DefaultApiRequestFactory extends BaseAPIRequestFactory { + + /** + */ + public async uniqueItems(_options?: Configuration): Promise { + let _config = _options || this.configuration; + + // Path Params + const localVarPath = '/unique-items'; + + // Make Request Context + const requestContext = _config.baseServer.makeRequestContext(localVarPath, HttpMethod.GET); + requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8") + + + + const defaultAuth: SecurityAuthentication | undefined = _options?.authMethods?.default || this.configuration?.authMethods?.default + if (defaultAuth?.applySecurityAuthentication) { + await defaultAuth?.applySecurityAuthentication(requestContext); + } + + return requestContext; + } + +} + +export class DefaultApiResponseProcessor { + + /** + * Unwraps the actual response sent by the server from the response context and deserializes the response content + * to the expected objects + * + * @params response Response returned by the server for a request to uniqueItems + * @throws ApiException if the response code was not in [200, 299] + */ + public async uniqueItemsWithHttpInfo(response: ResponseContext): Promise> { + const contentType = ObjectSerializer.normalizeMediaType(response.headers["content-type"]); + if (isCodeInRange("200", response.httpStatusCode)) { + const body: Response = ObjectSerializer.deserialize( + ObjectSerializer.parse(await response.body.text(), contentType), + "Response", "" + ) as Response; + return new HttpInfo(response.httpStatusCode, response.headers, response.body, body); + } + + // Work around for missing responses in specification, e.g. for petstore.yaml + if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) { + const body: Response = ObjectSerializer.deserialize( + ObjectSerializer.parse(await response.body.text(), contentType), + "Response", "" + ) as Response; + return new HttpInfo(response.httpStatusCode, response.headers, response.body, body); + } + + throw new ApiException(response.httpStatusCode, "Unknown API Status Code!", await response.getBodyAsAny(), response.headers); + } + +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/baseapi.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/baseapi.ts new file mode 100644 index 000000000000..ce1e2dbc47e1 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/baseapi.ts @@ -0,0 +1,37 @@ +import { Configuration } from '../configuration' + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPIRequestFactory { + + constructor(protected configuration: Configuration) { + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError"; + constructor(public api: string, public method: string, public field: string) { + super("Required parameter " + field + " was null or undefined when calling " + api + "." + method + "."); + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/exception.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/exception.ts new file mode 100644 index 000000000000..9365d33a8f7e --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/apis/exception.ts @@ -0,0 +1,15 @@ +/** + * Represents an error caused by an api call i.e. it has attributes for a HTTP status code + * and the returned body object. + * + * Example + * API returns a ErrorMessageObject whenever HTTP status code is not in [200, 299] + * => ApiException(404, someErrorMessageObject) + * + */ +export class ApiException extends Error { + public constructor(public code: number, message: string, public body: T, public headers: { [key: string]: string; }) { + super("HTTP-Code: " + code + "\nMessage: " + message + "\nBody: " + JSON.stringify(body) + "\nHeaders: " + + JSON.stringify(headers)) + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/auth/auth.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/auth/auth.ts new file mode 100644 index 000000000000..be50274b9d65 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/auth/auth.ts @@ -0,0 +1,51 @@ +import { RequestContext } from "../http/http"; + +/** + * Interface authentication schemes. + */ +export interface SecurityAuthentication { + /* + * @return returns the name of the security authentication as specified in OAI + */ + getName(): string; + + /** + * Applies the authentication scheme to the request context + * + * @params context the request context which should use this authentication scheme + */ + applySecurityAuthentication(context: RequestContext): void | Promise; +} + +export interface TokenProvider { + getToken(): Promise | string; +} + + +export type AuthMethods = { + "default"?: SecurityAuthentication, +} + +export type ApiKeyConfiguration = string; +export type HttpBasicConfiguration = { "username": string, "password": string }; +export type HttpBearerConfiguration = { tokenProvider: TokenProvider }; +export type OAuth2Configuration = { accessToken: string }; + +export type AuthMethodsConfiguration = { + "default"?: SecurityAuthentication, +} + +/** + * Creates the authentication methods from a swagger description. + * + */ +export function configureAuthMethods(config: AuthMethodsConfiguration | undefined): AuthMethods { + let authMethods: AuthMethods = {} + + if (!config) { + return authMethods; + } + authMethods["default"] = config["default"] + + return authMethods; +} \ No newline at end of file diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/configuration.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/configuration.ts new file mode 100644 index 000000000000..7acb56e66477 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/configuration.ts @@ -0,0 +1,82 @@ +import { HttpLibrary } from "./http/http"; +import { Middleware, PromiseMiddleware, PromiseMiddlewareWrapper } from "./middleware"; +import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorphic-fetch"; +import { BaseServerConfiguration, server1 } from "./servers"; +import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth"; + +export interface Configuration { + readonly baseServer: BaseServerConfiguration; + readonly httpApi: HttpLibrary; + readonly middleware: Middleware[]; + readonly authMethods: AuthMethods; +} + + +/** + * Interface with which a configuration object can be configured. + */ +export interface ConfigurationParameters { + /** + * Default server to use - a list of available servers (according to the + * OpenAPI yaml definition) is included in the `servers` const in `./servers`. You can also + * create your own server with the `ServerConfiguration` class from the same + * file. + */ + baseServer?: BaseServerConfiguration; + /** + * HTTP library to use e.g. IsomorphicFetch. This can usually be skipped as + * all generators come with a default library. + * If available, additional libraries can be imported from `./http/*` + */ + httpApi?: HttpLibrary; + + /** + * The middlewares which will be applied to requests and responses. You can + * add any number of middleware components to modify requests before they + * are sent or before they are deserialized by implementing the `Middleware` + * interface defined in `./middleware` + */ + middleware?: Middleware[]; + /** + * Configures middleware functions that return promises instead of + * Observables (which are used by `middleware`). Otherwise allows for the + * same functionality as `middleware`, i.e., modifying requests before they + * are sent and before they are deserialized. + */ + promiseMiddleware?: PromiseMiddleware[]; + /** + * Configuration for the available authentication methods (e.g., api keys) + * according to the OpenAPI yaml definition. For the definition, please refer to + * `./auth/auth` + */ + authMethods?: AuthMethodsConfiguration +} + +/** + * Provide your `ConfigurationParameters` to this function to get a `Configuration` + * object that can be used to configure your APIs (in the constructor or + * for each request individually). + * + * If a property is not included in conf, a default is used: + * - baseServer: server1 + * - httpApi: IsomorphicFetchHttpLibrary + * - middleware: [] + * - promiseMiddleware: [] + * - authMethods: {} + * + * @param conf partial configuration + */ +export function createConfiguration(conf: ConfigurationParameters = {}): Configuration { + const configuration: Configuration = { + baseServer: conf.baseServer !== undefined ? conf.baseServer : server1, + httpApi: conf.httpApi || new DefaultHttpLibrary(), + middleware: conf.middleware || [], + authMethods: configureAuthMethods(conf.authMethods) + }; + if (conf.promiseMiddleware) { + conf.promiseMiddleware.forEach( + m => configuration.middleware.push(new PromiseMiddlewareWrapper(m)) + ); + } + return configuration; +} \ No newline at end of file diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/git_push.sh b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/git_push.sh new file mode 100644 index 000000000000..b253029754ed --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/git_push.sh @@ -0,0 +1,51 @@ +#!/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" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/http.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/http.ts new file mode 100644 index 000000000000..f38a52ec1300 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/http.ts @@ -0,0 +1,254 @@ +import { Observable, from } from '../rxjsStub'; + +export * from './isomorphic-fetch'; + +/** + * Represents an HTTP method. + */ +export enum HttpMethod { + GET = "GET", + HEAD = "HEAD", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + CONNECT = "CONNECT", + OPTIONS = "OPTIONS", + TRACE = "TRACE", + PATCH = "PATCH" +} + +/** + * Represents an HTTP file which will be transferred from or to a server. + */ +export type HttpFile = Blob & { readonly name: string }; + +export class HttpException extends Error { + public constructor(msg: string) { + super(msg); + } +} + +/** + * Represents the body of an outgoing HTTP request. + */ +export type RequestBody = undefined | string | FormData | URLSearchParams; + +function ensureAbsoluteUrl(url: string) { + if (url.startsWith("http://") || url.startsWith("https://")) { + return url; + } + return window.location.origin + url; +} + +/** + * Represents an HTTP request context + */ +export class RequestContext { + private headers: { [key: string]: string } = {}; + private body: RequestBody = undefined; + private url: URL; + + /** + * Creates the request context using a http method and request resource url + * + * @param url url of the requested resource + * @param httpMethod http method + */ + public constructor(url: string, private httpMethod: HttpMethod) { + this.url = new URL(ensureAbsoluteUrl(url)); + } + + /* + * Returns the url set in the constructor including the query string + * + */ + public getUrl(): string { + return this.url.toString().endsWith("/") ? + this.url.toString().slice(0, -1) + : this.url.toString(); + } + + /** + * Replaces the url set in the constructor with this url. + * + */ + public setUrl(url: string) { + this.url = new URL(ensureAbsoluteUrl(url)); + } + + /** + * Sets the body of the http request either as a string or FormData + * + * Note that setting a body on a HTTP GET, HEAD, DELETE, CONNECT or TRACE + * request is discouraged. + * https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.7.3.1 + * + * @param body the body of the request + */ + public setBody(body: RequestBody) { + this.body = body; + } + + public getHttpMethod(): HttpMethod { + return this.httpMethod; + } + + public getHeaders(): { [key: string]: string } { + return this.headers; + } + + public getBody(): RequestBody { + return this.body; + } + + public setQueryParam(name: string, value: string) { + this.url.searchParams.set(name, value); + } + + public appendQueryParam(name: string, value: string) { + this.url.searchParams.append(name, value); + } + + /** + * Sets a cookie with the name and value. NO check for duplicate cookies is performed + * + */ + public addCookie(name: string, value: string): void { + if (!this.headers["Cookie"]) { + this.headers["Cookie"] = ""; + } + this.headers["Cookie"] += name + "=" + value + "; "; + } + + public setHeaderParam(key: string, value: string): void { + this.headers[key] = value; + } +} + +export interface ResponseBody { + text(): Promise; + binary(): Promise; +} + +/** + * Helper class to generate a `ResponseBody` from binary data + */ +export class SelfDecodingBody implements ResponseBody { + constructor(private dataSource: Promise) {} + + binary(): Promise { + return this.dataSource; + } + + async text(): Promise { + const data: Blob = await this.dataSource; + // @ts-ignore + if (data.text) { + // @ts-ignore + return data.text(); + } + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener("load", () => resolve(reader.result as string)); + reader.addEventListener("error", () => reject(reader.error)); + reader.readAsText(data); + }); + } +} + +export class ResponseContext { + public constructor( + public httpStatusCode: number, + public headers: { [key: string]: string }, + public body: ResponseBody + ) {} + + /** + * Parse header value in the form `value; param1="value1"` + * + * E.g. for Content-Type or Content-Disposition + * Parameter names are converted to lower case + * The first parameter is returned with the key `""` + */ + public getParsedHeader(headerName: string): { [parameter: string]: string } { + const result: { [parameter: string]: string } = {}; + if (!this.headers[headerName]) { + return result; + } + + const parameters = this.headers[headerName].split(";"); + for (const parameter of parameters) { + let [key, value] = parameter.split("=", 2); + key = key.toLowerCase().trim(); + if (value === undefined) { + result[""] = key; + } else { + value = value.trim(); + if (value.startsWith('"') && value.endsWith('"')) { + value = value.substring(1, value.length - 1); + } + result[key] = value; + } + } + return result; + } + + public async getBodyAsFile(): Promise { + const data = await this.body.binary(); + const fileName = this.getParsedHeader("content-disposition")["filename"] || ""; + const contentType = this.headers["content-type"] || ""; + try { + return new File([data], fileName, { type: contentType }); + } catch (error) { + /** Fallback for when the File constructor is not available */ + return Object.assign(data, { + name: fileName, + type: contentType + }); + } + } + + /** + * Use a heuristic to get a body of unknown data structure. + * Return as string if possible, otherwise as binary. + */ + public getBodyAsAny(): Promise { + try { + return this.body.text(); + } catch {} + + try { + return this.body.binary(); + } catch {} + + return Promise.resolve(undefined); + } +} + +export interface HttpLibrary { + send(request: RequestContext): Observable; +} + +export interface PromiseHttpLibrary { + send(request: RequestContext): Promise; +} + +export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary { + return { + send(request: RequestContext): Observable { + return from(promiseHttpLibrary.send(request)); + } + } +} + +export class HttpInfo extends ResponseContext { + public constructor( + public httpStatusCode: number, + public headers: { [key: string]: string }, + public body: ResponseBody, + public data: T, + ) { + super(httpStatusCode, headers, body); + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/isomorphic-fetch.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/isomorphic-fetch.ts new file mode 100644 index 000000000000..3af85f3d902c --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/http/isomorphic-fetch.ts @@ -0,0 +1,32 @@ +import {HttpLibrary, RequestContext, ResponseContext} from './http'; +import { from, Observable } from '../rxjsStub'; +import "whatwg-fetch"; + +export class IsomorphicFetchHttpLibrary implements HttpLibrary { + + public send(request: RequestContext): Observable { + let method = request.getHttpMethod().toString(); + let body = request.getBody(); + + const resultPromise = fetch(request.getUrl(), { + method: method, + body: body as any, + headers: request.getHeaders(), + credentials: "same-origin" + }).then((resp: any) => { + const headers: { [name: string]: string } = {}; + resp.headers.forEach((value: string, name: string) => { + headers[name] = value; + }); + + const body = { + text: () => resp.text(), + binary: () => resp.blob() + }; + return new ResponseContext(resp.status, headers, body); + }); + + return from>(resultPromise); + + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/index.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/index.ts new file mode 100644 index 000000000000..89069bd11889 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/index.ts @@ -0,0 +1,12 @@ +export * from "./http/http"; +export * from "./auth/auth"; +export * from "./models/all"; +export { createConfiguration } from "./configuration" +export { Configuration } from "./configuration" +export * from "./apis/exception"; +export * from "./servers"; +export { RequiredError } from "./apis/baseapi"; + +export { PromiseMiddleware as Middleware } from './middleware'; +export { PromiseDefaultApi as DefaultApi } from './types/PromiseAPI'; + diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/middleware.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/middleware.ts new file mode 100644 index 000000000000..524f93f016b2 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/middleware.ts @@ -0,0 +1,66 @@ +import {RequestContext, ResponseContext} from './http/http'; +import { Observable, from } from './rxjsStub'; + +/** + * Defines the contract for a middleware intercepting requests before + * they are sent (but after the RequestContext was created) + * and before the ResponseContext is unwrapped. + * + */ +export interface Middleware { + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ + pre(context: RequestContext): Observable; + /** + * Modifies the returned response before it is deserialized. + * + * @param context ResponseContext of a sent request + * @returns an observable of the modified response context + */ + post(context: ResponseContext): Observable; +} + +export class PromiseMiddlewareWrapper implements Middleware { + + public constructor(private middleware: PromiseMiddleware) { + + } + + pre(context: RequestContext): Observable { + return from(this.middleware.pre(context)); + } + + post(context: ResponseContext): Observable { + return from(this.middleware.post(context)); + } + +} + +/** + * Defines the contract for a middleware intercepting requests before + * they are sent (but after the RequestContext was created) + * and before the ResponseContext is unwrapped. + * + */ +export interface PromiseMiddleware { + /** + * Modifies the request before the request is sent. + * + * @param context RequestContext of a request which is about to be sent to the server + * @returns an observable of the updated request context + * + */ + pre(context: RequestContext): Promise; + /** + * Modifies the returned response before it is deserialized. + * + * @param context ResponseContext of a sent request + * @returns an observable of the modified response context + */ + post(context: ResponseContext): Promise; +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/ObjectSerializer.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/ObjectSerializer.ts new file mode 100644 index 000000000000..463933cf57f3 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/ObjectSerializer.ts @@ -0,0 +1,270 @@ +export * from '../models/Response'; + +import { Response, ResponseEnrichmentSourceEnum } from '../models/Response'; + +/* tslint:disable:no-unused-variable */ +let primitives = [ + "string", + "boolean", + "double", + "integer", + "long", + "float", + "number", + "any" + ]; + +let enumsMap: Set = new Set([ + "ResponseEnrichmentSourceEnum", +]); + +let typeMap: {[index: string]: any} = { + "Response": Response, +} + +type MimeTypeDescriptor = { + type: string; + subtype: string; + subtypeTokens: string[]; +}; + +/** + * Every mime-type consists of a type, subtype, and optional parameters. + * The subtype can be composite, including information about the content format. + * For example: `application/json-patch+json`, `application/merge-patch+json`. + * + * This helper transforms a string mime-type into an internal representation. + * This simplifies the implementation of predicates that in turn define common rules for parsing or stringifying + * the payload. + */ +const parseMimeType = (mimeType: string): MimeTypeDescriptor => { + const [type, subtype] = mimeType.split('/'); + return { + type, + subtype, + subtypeTokens: subtype.split('+'), + }; +}; + +type MimeTypePredicate = (mimeType: string) => boolean; + +// This factory creates a predicate function that checks a string mime-type against defined rules. +const mimeTypePredicateFactory = (predicate: (descriptor: MimeTypeDescriptor) => boolean): MimeTypePredicate => (mimeType) => predicate(parseMimeType(mimeType)); + +// Use this factory when you need to define a simple predicate based only on type and, if applicable, subtype. +const mimeTypeSimplePredicateFactory = (type: string, subtype?: string): MimeTypePredicate => mimeTypePredicateFactory((descriptor) => { + if (descriptor.type !== type) return false; + if (subtype != null && descriptor.subtype !== subtype) return false; + return true; +}); + +// Creating a set of named predicates that will help us determine how to handle different mime-types +const isTextLikeMimeType = mimeTypeSimplePredicateFactory('text'); +const isJsonMimeType = mimeTypeSimplePredicateFactory('application', 'json'); +const isJsonLikeMimeType = mimeTypePredicateFactory((descriptor) => descriptor.type === 'application' && descriptor.subtypeTokens.some((item) => item === 'json')); +const isOctetStreamMimeType = mimeTypeSimplePredicateFactory('application', 'octet-stream'); +const isFormUrlencodedMimeType = mimeTypeSimplePredicateFactory('application', 'x-www-form-urlencoded'); + +// Defining a list of mime-types in the order of prioritization for handling. +const supportedMimeTypePredicatesWithPriority: MimeTypePredicate[] = [ + isJsonMimeType, + isJsonLikeMimeType, + isTextLikeMimeType, + isOctetStreamMimeType, + isFormUrlencodedMimeType, +]; + +export class ObjectSerializer { + public static findCorrectType(data: any, expectedType: string) { + if (data == undefined) { + return expectedType; + } else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) { + return expectedType; + } else if (expectedType === "Date") { + return expectedType; + } else { + if (enumsMap.has(expectedType)) { + return expectedType; + } + + if (!typeMap[expectedType]) { + return expectedType; // w/e we don't know the type + } + + // Check the discriminator + let discriminatorProperty = typeMap[expectedType].discriminator; + if (discriminatorProperty == null) { + return expectedType; // the type does not have a discriminator. use it. + } else { + if (data[discriminatorProperty]) { + var discriminatorType = data[discriminatorProperty]; + let mapping = typeMap[expectedType].mapping; + if (mapping != undefined && mapping[discriminatorType]) { + return mapping[discriminatorType]; // use the type given in the discriminator + } else if(typeMap[discriminatorType]) { + return discriminatorType; + } else { + return expectedType; // discriminator did not map to a type + } + } else { + return expectedType; // discriminator was not present (or an empty string) + } + } + } + } + + public static serialize(data: any, type: string, format: string) { + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let date of data) { + transformedData.push(ObjectSerializer.serialize(date, subType, format)); + } + return transformedData; + } else if (type === "Date") { + if (format == "date") { + let month = data.getMonth()+1 + month = month < 10 ? "0" + month.toString() : month.toString() + let day = data.getDate(); + day = day < 10 ? "0" + day.toString() : day.toString(); + + return data.getFullYear() + "-" + month + "-" + day; + } else { + return data.toISOString(); + } + } else { + if (enumsMap.has(type)) { + return data; + } + if (!typeMap[type]) { // in case we dont know the type + return data; + } + + // Get the actual type of this object + type = this.findCorrectType(data, type); + + // get the map for the correct type. + let attributeTypes = typeMap[type].getAttributeTypeMap(); + let instance: {[index: string]: any} = {}; + for (let attributeType of attributeTypes) { + instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.name], attributeType.type, attributeType.format); + } + return instance; + } + } + + public static deserialize(data: any, type: string, format: string) { + // polymorphism may change the actual type. + type = ObjectSerializer.findCorrectType(data, type); + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let date of data) { + transformedData.push(ObjectSerializer.deserialize(date, subType, format)); + } + return transformedData; + } else if (type === "Date") { + return new Date(data); + } else { + if (enumsMap.has(type)) {// is Enum + return data; + } + + if (!typeMap[type]) { // dont know the type + return data; + } + let instance = new typeMap[type](); + let attributeTypes = typeMap[type].getAttributeTypeMap(); + for (let attributeType of attributeTypes) { + let value = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type, attributeType.format); + if (value !== undefined) { + instance[attributeType.name] = value; + } + } + return instance; + } + } + + + /** + * Normalize media type + * + * We currently do not handle any media types attributes, i.e. anything + * after a semicolon. All content is assumed to be UTF-8 compatible. + */ + public static normalizeMediaType(mediaType: string | undefined): string | undefined { + if (mediaType === undefined) { + return undefined; + } + return mediaType.split(";")[0].trim().toLowerCase(); + } + + /** + * From a list of possible media types, choose the one we can handle best. + * + * The order of the given media types does not have any impact on the choice + * made. + */ + public static getPreferredMediaType(mediaTypes: Array): string { + /** According to OAS 3 we should default to json */ + if (mediaTypes.length === 0) { + return "application/json"; + } + + const normalMediaTypes = mediaTypes.map(this.normalizeMediaType); + + for (const predicate of supportedMimeTypePredicatesWithPriority) { + for (const mediaType of normalMediaTypes) { + if (mediaType != null && predicate(mediaType)) { + return mediaType; + } + } + } + + throw new Error("None of the given media types are supported: " + mediaTypes.join(", ")); + } + + /** + * Convert data to a string according the given media type + */ + public static stringify(data: any, mediaType: string): string { + if (isTextLikeMimeType(mediaType)) { + return String(data); + } + + if (isJsonLikeMimeType(mediaType)) { + return JSON.stringify(data); + } + + throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.stringify."); + } + + /** + * Parse data from a string according to the given media type + */ + public static parse(rawData: string, mediaType: string | undefined) { + if (mediaType === undefined) { + throw new Error("Cannot parse content. No Content-Type defined."); + } + + if (isTextLikeMimeType(mediaType)) { + return rawData; + } + + if (isJsonLikeMimeType(mediaType)) { + return JSON.parse(rawData); + } + + throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.parse."); + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts new file mode 100644 index 000000000000..a2ca9bfaac16 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts @@ -0,0 +1,45 @@ +/** + * Sample for uniqueItems + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { HttpFile } from '../http/http'; + +export class Response { + /** + * The source of the data in this Field (if it is enriched) + */ + 'enrichmentSource'?: ResponseEnrichmentSourceEnum | null; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "enrichmentSource", + "baseName": "enrichmentSource", + "type": "ResponseEnrichmentSourceEnum", + "format": "" + } ]; + + static getAttributeTypeMap() { + return Response.attributeTypeMap; + } + + public constructor() { + } +} + +export enum ResponseEnrichmentSourceEnum { + AffinityData = 'affinity-data', + Dealroom = 'dealroom', +} + diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/all.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/all.ts new file mode 100644 index 000000000000..fb6a6246f9a8 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/all.ts @@ -0,0 +1 @@ +export * from '../models/Response' diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/package.json b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/package.json new file mode 100644 index 000000000000..ea853317fbd4 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/package.json @@ -0,0 +1,42 @@ +{ + "name": "", + "version": "", + "description": "OpenAPI client for ", + "author": "OpenAPI-Generator Contributors", + "repository": { + "type": "git", + "url": "https://github.com/GIT_USER_ID/GIT_REPO_ID.git" + }, + "keywords": [ + "fetch", + "typescript", + "openapi-client", + "openapi-generator" + ], + "license": "Unlicense", + "main": "./dist/index.js", + "type": "commonjs", + "exports": { + ".": { + "require": "./dist/index.js", + "types": "./dist/index.d.js" + } + }, + "files": [ + "dist" + ], + "typings": "./dist/index.d.ts", + "scripts": { + "build": "tsc", + "prepare": "npm run build" + }, + "dependencies": { + "whatwg-fetch": "^3.0.0", + "es6-promise": "^4.2.4", + "url-parse": "^1.4.3" + }, + "devDependencies": { + "typescript": "^4.0", + "@types/url-parse": "1.4.4" + } +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/rxjsStub.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/rxjsStub.ts new file mode 100644 index 000000000000..4c73715a2486 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/rxjsStub.ts @@ -0,0 +1,27 @@ +export class Observable { + constructor(private promise: Promise) {} + + toPromise() { + return this.promise; + } + + pipe(callback: (value: T) => S | Promise): Observable { + return new Observable(this.promise.then(callback)); + } +} + +export function from(promise: Promise) { + return new Observable(promise); +} + +export function of(value: T) { + return new Observable(Promise.resolve(value)); +} + +export function mergeMap(callback: (value: T) => Observable) { + return (value: T) => callback(value).toPromise(); +} + +export function map(callback: any) { + return callback; +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/servers.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/servers.ts new file mode 100644 index 000000000000..5975a0c5b325 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/servers.ts @@ -0,0 +1,55 @@ +import { RequestContext, HttpMethod } from "./http/http"; + +export interface BaseServerConfiguration { + makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext; +} + +/** + * + * Represents the configuration of a server including its + * url template and variable configuration based on the url. + * + */ +export class ServerConfiguration implements BaseServerConfiguration { + public constructor(private url: string, private variableConfiguration: T) {} + + /** + * Sets the value of the variables of this server. Variables are included in + * the `url` of this ServerConfiguration in the form `{variableName}` + * + * @param variableConfiguration a partial variable configuration for the + * variables contained in the url + */ + public setVariables(variableConfiguration: Partial) { + Object.assign(this.variableConfiguration, variableConfiguration); + } + + public getConfiguration(): T { + return this.variableConfiguration + } + + private getUrl() { + let replacedUrl = this.url; + for (const key in this.variableConfiguration) { + var re = new RegExp("{" + key + "}","g"); + replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]); + } + return replacedUrl + } + + /** + * Creates a new request context for this server using the url with variables + * replaced with their respective values and the endpoint of the request appended. + * + * @param endpoint the endpoint to be queried on the server + * @param httpMethod httpMethod to be used + * + */ + public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext { + return new RequestContext(this.getUrl() + endpoint, httpMethod); + } +} + +export const server1 = new ServerConfiguration<{ }>("http://localhost:3000", { }) + +export const servers = [server1]; diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/tsconfig.json b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/tsconfig.json new file mode 100644 index 000000000000..aa173eb68f32 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "strict": true, + /* Basic Options */ + "target": "es5", + "moduleResolution": "node", + "declaration": true, + + /* Additional Checks */ + "noUnusedLocals": false, /* Report errors on unused locals. */ // TODO: reenable (unused imports!) + "noUnusedParameters": false, /* Report errors on unused parameters. */ // TODO: set to true again + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + "removeComments": true, + "sourceMap": true, + "outDir": "./dist", + "noLib": false, + "lib": [ "es6", "dom" ], + }, + "exclude": [ + "dist", + "node_modules" + ], + "filesGlob": [ + "./**/*.ts", + ] +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObjectParamAPI.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObjectParamAPI.ts new file mode 100644 index 000000000000..2c5db71a9382 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObjectParamAPI.ts @@ -0,0 +1,33 @@ +import { ResponseContext, RequestContext, HttpFile, HttpInfo } from '../http/http'; +import { Configuration} from '../configuration' + +import { Response } from '../models/Response'; + +import { ObservableDefaultApi } from "./ObservableAPI"; +import { DefaultApiRequestFactory, DefaultApiResponseProcessor} from "../apis/DefaultApi"; + +export interface DefaultApiUniqueItemsRequest { +} + +export class ObjectDefaultApi { + private api: ObservableDefaultApi + + public constructor(configuration: Configuration, requestFactory?: DefaultApiRequestFactory, responseProcessor?: DefaultApiResponseProcessor) { + this.api = new ObservableDefaultApi(configuration, requestFactory, responseProcessor); + } + + /** + * @param param the request object + */ + public uniqueItemsWithHttpInfo(param: DefaultApiUniqueItemsRequest = {}, options?: Configuration): Promise> { + return this.api.uniqueItemsWithHttpInfo( options).toPromise(); + } + + /** + * @param param the request object + */ + public uniqueItems(param: DefaultApiUniqueItemsRequest = {}, options?: Configuration): Promise { + return this.api.uniqueItems( options).toPromise(); + } + +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObservableAPI.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObservableAPI.ts new file mode 100644 index 000000000000..02bae11fe29c --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/ObservableAPI.ts @@ -0,0 +1,50 @@ +import { ResponseContext, RequestContext, HttpFile, HttpInfo } from '../http/http'; +import { Configuration} from '../configuration' +import { Observable, of, from } from '../rxjsStub'; +import {mergeMap, map} from '../rxjsStub'; +import { Response } from '../models/Response'; + +import { DefaultApiRequestFactory, DefaultApiResponseProcessor} from "../apis/DefaultApi"; +export class ObservableDefaultApi { + private requestFactory: DefaultApiRequestFactory; + private responseProcessor: DefaultApiResponseProcessor; + private configuration: Configuration; + + public constructor( + configuration: Configuration, + requestFactory?: DefaultApiRequestFactory, + responseProcessor?: DefaultApiResponseProcessor + ) { + this.configuration = configuration; + this.requestFactory = requestFactory || new DefaultApiRequestFactory(configuration); + this.responseProcessor = responseProcessor || new DefaultApiResponseProcessor(); + } + + /** + */ + public uniqueItemsWithHttpInfo(_options?: Configuration): Observable> { + const requestContextPromise = this.requestFactory.uniqueItems(_options); + + // build promise chain + let middlewarePreObservable = from(requestContextPromise); + for (const middleware of this.configuration.middleware) { + middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx))); + } + + return middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => this.configuration.httpApi.send(ctx))). + pipe(mergeMap((response: ResponseContext) => { + let middlewarePostObservable = of(response); + for (const middleware of this.configuration.middleware) { + middlewarePostObservable = middlewarePostObservable.pipe(mergeMap((rsp: ResponseContext) => middleware.post(rsp))); + } + return middlewarePostObservable.pipe(map((rsp: ResponseContext) => this.responseProcessor.uniqueItemsWithHttpInfo(rsp))); + })); + } + + /** + */ + public uniqueItems(_options?: Configuration): Observable { + return this.uniqueItemsWithHttpInfo(_options).pipe(map((apiResponse: HttpInfo) => apiResponse.data)); + } + +} diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/PromiseAPI.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/PromiseAPI.ts new file mode 100644 index 000000000000..a133e29a6f4d --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/types/PromiseAPI.ts @@ -0,0 +1,37 @@ +import { ResponseContext, RequestContext, HttpFile, HttpInfo } from '../http/http'; +import { Configuration} from '../configuration' + +import { Response } from '../models/Response'; +import { ObservableDefaultApi } from './ObservableAPI'; + +import { DefaultApiRequestFactory, DefaultApiResponseProcessor} from "../apis/DefaultApi"; +export class PromiseDefaultApi { + private api: ObservableDefaultApi + + public constructor( + configuration: Configuration, + requestFactory?: DefaultApiRequestFactory, + responseProcessor?: DefaultApiResponseProcessor + ) { + this.api = new ObservableDefaultApi(configuration, requestFactory, responseProcessor); + } + + /** + */ + public uniqueItemsWithHttpInfo(_options?: Configuration): Promise> { + const result = this.api.uniqueItemsWithHttpInfo(_options); + return result.toPromise(); + } + + /** + */ + public uniqueItems(_options?: Configuration): Promise { + const result = this.api.uniqueItems(_options); + return result.toPromise(); + } + + +} + + + diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/util.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/util.ts new file mode 100644 index 000000000000..96ea3dfdc770 --- /dev/null +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/util.ts @@ -0,0 +1,37 @@ +/** + * Returns if a specific http code is in a given code range + * where the code range is defined as a combination of digits + * and "X" (the letter X) with a length of 3 + * + * @param codeRange string with length 3 consisting of digits and "X" (the letter X) + * @param code the http status code to be checked against the code range + */ +export function isCodeInRange(codeRange: string, code: number): boolean { + // This is how the default value is encoded in OAG + if (codeRange === "0") { + return true; + } + if (codeRange == code.toString()) { + return true; + } else { + const codeString = code.toString(); + if (codeString.length != codeRange.length) { + return false; + } + for (let i = 0; i < codeString.length; i++) { + if (codeRange.charAt(i) != "X" && codeRange.charAt(i) != codeString.charAt(i)) { + return false; + } + } + return true; + } +} + +/** +* Returns if it can consume form +* +* @param consumes array +*/ +export function canConsumeForm(contentTypes: string[]): boolean { + return contentTypes.indexOf('multipart/form-data') !== -1 +} From 01209d532668c0026f22c60fd5de9f90427c2b0f Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Fri, 6 Sep 2024 22:05:15 +0100 Subject: [PATCH 2/4] better solution? --- .../org/openapitools/codegen/DefaultCodegen.java | 13 ++++++++++--- .../main/resources/typescript/model/model.mustache | 2 -- .../org/openapitools/codegen/php/PhpModelTest.java | 4 ---- .../typescript/fetch/TypeScriptFetchModelTest.java | 4 ---- .../builds/nullable-enum/models/Response.ts | 2 +- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index f27e4a400e48..9fbceffb2ef3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3947,9 +3947,12 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo List _enum = p.getEnum(); property._enum = new ArrayList<>(); for (Object i : _enum) { - if(i != null) { - property._enum.add(String.valueOf(i)); + // raw null values in enums are unions for nullable + // atttributes, not actual enum values, so we remove them here + if (i == null) { + continue; } + property._enum.add(String.valueOf(i)); } property.isEnum = true; property.isInnerEnum = true; @@ -6630,6 +6633,11 @@ protected List> buildEnumVars(List values, String da : 0; for (Object value : values) { + if (value == null) { + // raw null values in enums are unions for nullable + // atttributes, not actual enum values, so we remove them here + continue; + } Map enumVar = new HashMap<>(); String enumName = truncateIdx == 0 ? String.valueOf(value) @@ -6643,7 +6651,6 @@ protected List> buildEnumVars(List values, String da enumVar.put("name", finalEnumName); enumVar.put("value", toEnumValue(String.valueOf(value), dataType)); - enumVar.put("isValueNull", value == null); enumVar.put("isString", isDataTypeString(dataType)); // TODO: add isNumeric enumVars.add(enumVar); diff --git a/modules/openapi-generator/src/main/resources/typescript/model/model.mustache b/modules/openapi-generator/src/main/resources/typescript/model/model.mustache index 0d7ea3d6aa72..4f30fea765b0 100644 --- a/modules/openapi-generator/src/main/resources/typescript/model/model.mustache +++ b/modules/openapi-generator/src/main/resources/typescript/model/model.mustache @@ -89,9 +89,7 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ export enum {{classname}}{{enumName}} { {{#allowableValues}} {{#enumVars}} - {{^isValueNull}} {{name}} = {{{value}}}{{^-last}},{{/-last}} - {{/isValueNull}} {{/enumVars}} {{/allowableValues}} } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java index 70bfb85f2b60..39b3bb7870e8 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java @@ -315,12 +315,10 @@ public void enumArrayModelTest() { fish.put("name", "FISH"); fish.put("value", "\'fish\'"); fish.put("isString", true); - fish.put("isValueNull", false); HashMap crab= new HashMap(); crab.put("name", "CRAB"); crab.put("value", "\'crab\'"); crab.put("isString", true); - crab.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(fish, crab)); // assert inner items @@ -353,12 +351,10 @@ public void enumModelValueTest() { one.put("name", "1"); one.put("value", "1"); one.put("isString", false); - one.put("isValueNull", false); HashMap minusOne = new HashMap(); minusOne.put("name", "MINUS_1"); minusOne.put("value", "-1"); minusOne.put("isString", false); - minusOne.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(one, minusOne)); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java index 082998e12fdc..a7a3e644f0bd 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java @@ -384,12 +384,10 @@ public void enumArrayModelTest() { fish.put("name", "Fish"); fish.put("value", "'fish'"); fish.put("isString", false); - fish.put("isValueNull", false); HashMap crab = new HashMap(); crab.put("name", "Crab"); crab.put("value", "'crab'"); crab.put("isString", false); - crab.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(fish, crab)); // assert inner items @@ -426,12 +424,10 @@ public void enumModelValueTest() { one.put("name", "NUMBER_1"); one.put("value", "1"); one.put("isString", false); - one.put("isValueNull", false); HashMap minusOne = new HashMap(); minusOne.put("name", "NUMBER_MINUS_1"); minusOne.put("value", "-1"); minusOne.put("isString", false); - minusOne.put("isValueNull", false); Assert.assertEquals(prope.allowableValues.get("enumVars"), Arrays.asList(one, minusOne)); //IMPORTANT: these are not final enum values, which may be further updated diff --git a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts index a2ca9bfaac16..80fb76ed3b99 100644 --- a/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts +++ b/samples/openapi3/client/petstore/typescript/builds/nullable-enum/models/Response.ts @@ -40,6 +40,6 @@ export class Response { export enum ResponseEnrichmentSourceEnum { AffinityData = 'affinity-data', - Dealroom = 'dealroom', + Dealroom = 'dealroom' } From 72ef346340ff5e68416d793d6931e18107e4ac4e Mon Sep 17 00:00:00 2001 From: William Cheng Date: Tue, 24 Sep 2024 21:31:38 +0800 Subject: [PATCH 3/4] Update modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java Co-authored-by: Joscha Feth --- .../src/main/java/org/openapitools/codegen/DefaultCodegen.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 9fbceffb2ef3..991aab29bf57 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3950,6 +3950,7 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo // raw null values in enums are unions for nullable // atttributes, not actual enum values, so we remove them here if (i == null) { + property.isNullable = true; continue; } property._enum.add(String.valueOf(i)); From bbe6d3457ed1eac7184975716bd296a3e33d8a62 Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Wed, 25 Sep 2024 01:29:55 +1000 Subject: [PATCH 4/4] chore: Update circle_parallel.sh --- CI/circle_parallel.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/CI/circle_parallel.sh b/CI/circle_parallel.sh index e2fb1a6e83ab..1c7e52b27330 100755 --- a/CI/circle_parallel.sh +++ b/CI/circle_parallel.sh @@ -91,6 +91,7 @@ elif [ "$NODE_INDEX" = "3" ]; then #(cd samples/openapi3/client/petstore/typescript/tests/deno && mvn integration-test) (cd samples/openapi3/client/petstore/typescript/builds/browser && mvn integration-test) (cd samples/openapi3/client/petstore/typescript/tests/browser && mvn integration-test) + (cd samples/openapi3/client/petstore/typescript/builds/nullable-enum && mvn integration-test) (cd samples/client/petstore/typescript-fetch/builds/default && mvn integration-test) (cd samples/client/petstore/typescript-fetch/builds/es6-target && mvn integration-test) (cd samples/client/petstore/typescript-fetch/builds/with-npm-version && mvn integration-test)