Skip to content

Commit 17e0b7c

Browse files
joschawing328
andauthored
[typescript] fix: nullable enums should not serialize a null value to a string (OpenAPITools#19540)
* fix: `nullable` enums should not serialize a `null` value to a string * better solution? * Update modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java Co-authored-by: Joscha Feth <[email protected]> * chore: Update circle_parallel.sh --------- Co-authored-by: William Cheng <[email protected]>
1 parent e370b81 commit 17e0b7c

31 files changed

+1531
-0
lines changed

Diff for: CI/circle_parallel.sh

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ elif [ "$NODE_INDEX" = "3" ]; then
9191
#(cd samples/openapi3/client/petstore/typescript/tests/deno && mvn integration-test)
9292
(cd samples/openapi3/client/petstore/typescript/builds/browser && mvn integration-test)
9393
(cd samples/openapi3/client/petstore/typescript/tests/browser && mvn integration-test)
94+
(cd samples/openapi3/client/petstore/typescript/builds/nullable-enum && mvn integration-test)
9495
(cd samples/client/petstore/typescript-fetch/builds/default && mvn integration-test)
9596
(cd samples/client/petstore/typescript-fetch/builds/es6-target && mvn integration-test)
9697
(cd samples/client/petstore/typescript-fetch/builds/with-npm-version && mvn integration-test)
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
generatorName: typescript
2+
outputDir: samples/openapi3/client/petstore/typescript/builds/nullable-enum
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript/19027-regression.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/typescript

Diff for: modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

+11
Original file line numberDiff line numberDiff line change
@@ -3949,6 +3949,12 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
39493949
List<Object> _enum = p.getEnum();
39503950
property._enum = new ArrayList<>();
39513951
for (Object i : _enum) {
3952+
// raw null values in enums are unions for nullable
3953+
// atttributes, not actual enum values, so we remove them here
3954+
if (i == null) {
3955+
property.isNullable = true;
3956+
continue;
3957+
}
39523958
property._enum.add(String.valueOf(i));
39533959
}
39543960
property.isEnum = true;
@@ -6640,6 +6646,11 @@ protected List<Map<String, Object>> buildEnumVars(List<Object> values, String da
66406646
: 0;
66416647

66426648
for (Object value : values) {
6649+
if (value == null) {
6650+
// raw null values in enums are unions for nullable
6651+
// atttributes, not actual enum values, so we remove them here
6652+
continue;
6653+
}
66436654
Map<String, Object> enumVar = new HashMap<>();
66446655
String enumName = truncateIdx == 0
66456656
? String.valueOf(value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 1.0.0
4+
title: Sample for uniqueItems
5+
servers:
6+
- url: http://localhost:3000
7+
paths:
8+
/unique-items:
9+
get:
10+
operationId: unique_items
11+
responses:
12+
200:
13+
description: OK
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/Response'
18+
components:
19+
schemas:
20+
Response:
21+
type: object
22+
properties:
23+
enrichmentSource:
24+
description: The source of the data in this Field (if it is enriched)
25+
enum:
26+
- "affinity-data"
27+
- "dealroom"
28+
- null
29+
example: "affinity-data"
30+
type: "string"
31+
nullable: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.gitignore
2+
DefaultApi.md
3+
README.md
4+
apis/DefaultApi.ts
5+
apis/baseapi.ts
6+
apis/exception.ts
7+
auth/auth.ts
8+
configuration.ts
9+
git_push.sh
10+
http/http.ts
11+
http/isomorphic-fetch.ts
12+
index.ts
13+
middleware.ts
14+
models/ObjectSerializer.ts
15+
models/Response.ts
16+
models/all.ts
17+
package.json
18+
rxjsStub.ts
19+
servers.ts
20+
tsconfig.json
21+
types/ObjectParamAPI.ts
22+
types/ObservableAPI.ts
23+
types/PromiseAPI.ts
24+
util.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.9.0-SNAPSHOT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# .DefaultApi
2+
3+
All URIs are relative to *http://localhost:3000*
4+
5+
Method | HTTP request | Description
6+
------------- | ------------- | -------------
7+
[**uniqueItems**](DefaultApi.md#uniqueItems) | **GET** /unique-items |
8+
9+
10+
# **uniqueItems**
11+
> Response uniqueItems()
12+
13+
14+
### Example
15+
16+
17+
```typescript
18+
import { } from '';
19+
import * as fs from 'fs';
20+
21+
const configuration = .createConfiguration();
22+
const apiInstance = new .DefaultApi(configuration);
23+
24+
let body:any = {};
25+
26+
apiInstance.uniqueItems(body).then((data:any) => {
27+
console.log('API called successfully. Returned data: ' + data);
28+
}).catch((error:any) => console.error(error));
29+
```
30+
31+
32+
### Parameters
33+
This endpoint does not need any parameter.
34+
35+
36+
### Return type
37+
38+
**Response**
39+
40+
### Authorization
41+
42+
No authorization required
43+
44+
### HTTP request headers
45+
46+
- **Content-Type**: Not defined
47+
- **Accept**: application/json
48+
49+
50+
### HTTP response details
51+
| Status code | Description | Response headers |
52+
|-------------|-------------|------------------|
53+
**200** | OK | - |
54+
55+
[[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)
56+
57+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
## @
2+
3+
This generator creates TypeScript/JavaScript client that utilizes fetch-api.
4+
5+
### Building
6+
7+
To build and compile the typescript sources to javascript use:
8+
```
9+
npm install
10+
npm run build
11+
```
12+
13+
### Publishing
14+
15+
First build the package then run ```npm publish```
16+
17+
### Consuming
18+
19+
Navigate to the folder of your consuming project and run one of the following commands.
20+
21+
_published:_
22+
23+
```
24+
npm install @ --save
25+
```
26+
27+
_unPublished (not recommended):_
28+
29+
```
30+
npm install PATH_TO_GENERATED_PACKAGE --save
31+
```
32+
33+
### Usage
34+
35+
Below code snippet shows exemplary usage of the configuration and the API based
36+
on the typical `PetStore` example used for OpenAPI.
37+
38+
```
39+
import * as your_api from 'your_api_package'
40+
41+
// Covers all auth methods included in your OpenAPI yaml definition
42+
const authConfig: your_api.AuthMethodsConfiguration = {
43+
"api_key": "YOUR_API_KEY"
44+
}
45+
46+
// Implements a simple middleware to modify requests before (`pre`) they are sent
47+
// and after (`post`) they have been received
48+
class Test implements your_api.Middleware {
49+
pre(context: your_api.RequestContext): Promise<your_api.RequestContext> {
50+
// Modify context here and return
51+
return Promise.resolve(context);
52+
}
53+
54+
post(context: your_api.ResponseContext): Promise<your_api.ResponseContext> {
55+
return Promise.resolve(context);
56+
}
57+
58+
}
59+
60+
// Create configuration parameter object
61+
const configurationParameters = {
62+
httpApi: new your_api.JQueryHttpLibrary(), // Can also be ignored - default is usually fine
63+
baseServer: your_api.servers[0], // First server is default
64+
authMethods: authConfig, // No auth is default
65+
promiseMiddleware: [new Test()],
66+
}
67+
68+
// Convert to actual configuration
69+
const config = your_api.createConfiguration(configurationParameters);
70+
71+
// Use configuration with your_api
72+
const api = new your_api.PetApi(config);
73+
your_api.Pet p = new your_api.Pet();
74+
p.name = "My new pet";
75+
p.photoUrls = [];
76+
p.tags = [];
77+
p.status = "available";
78+
Promise<your_api.Pet> createdPet = api.addPet(p);
79+
80+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// TODO: better import syntax?
2+
import {BaseAPIRequestFactory, RequiredError, COLLECTION_FORMATS} from './baseapi';
3+
import {Configuration} from '../configuration';
4+
import {RequestContext, HttpMethod, ResponseContext, HttpFile, HttpInfo} from '../http/http';
5+
import {ObjectSerializer} from '../models/ObjectSerializer';
6+
import {ApiException} from './exception';
7+
import {canConsumeForm, isCodeInRange} from '../util';
8+
import {SecurityAuthentication} from '../auth/auth';
9+
10+
11+
import { Response } from '../models/Response';
12+
13+
/**
14+
* no description
15+
*/
16+
export class DefaultApiRequestFactory extends BaseAPIRequestFactory {
17+
18+
/**
19+
*/
20+
public async uniqueItems(_options?: Configuration): Promise<RequestContext> {
21+
let _config = _options || this.configuration;
22+
23+
// Path Params
24+
const localVarPath = '/unique-items';
25+
26+
// Make Request Context
27+
const requestContext = _config.baseServer.makeRequestContext(localVarPath, HttpMethod.GET);
28+
requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8")
29+
30+
31+
32+
const defaultAuth: SecurityAuthentication | undefined = _options?.authMethods?.default || this.configuration?.authMethods?.default
33+
if (defaultAuth?.applySecurityAuthentication) {
34+
await defaultAuth?.applySecurityAuthentication(requestContext);
35+
}
36+
37+
return requestContext;
38+
}
39+
40+
}
41+
42+
export class DefaultApiResponseProcessor {
43+
44+
/**
45+
* Unwraps the actual response sent by the server from the response context and deserializes the response content
46+
* to the expected objects
47+
*
48+
* @params response Response returned by the server for a request to uniqueItems
49+
* @throws ApiException if the response code was not in [200, 299]
50+
*/
51+
public async uniqueItemsWithHttpInfo(response: ResponseContext): Promise<HttpInfo<Response >> {
52+
const contentType = ObjectSerializer.normalizeMediaType(response.headers["content-type"]);
53+
if (isCodeInRange("200", response.httpStatusCode)) {
54+
const body: Response = ObjectSerializer.deserialize(
55+
ObjectSerializer.parse(await response.body.text(), contentType),
56+
"Response", ""
57+
) as Response;
58+
return new HttpInfo(response.httpStatusCode, response.headers, response.body, body);
59+
}
60+
61+
// Work around for missing responses in specification, e.g. for petstore.yaml
62+
if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) {
63+
const body: Response = ObjectSerializer.deserialize(
64+
ObjectSerializer.parse(await response.body.text(), contentType),
65+
"Response", ""
66+
) as Response;
67+
return new HttpInfo(response.httpStatusCode, response.headers, response.body, body);
68+
}
69+
70+
throw new ApiException<string | Blob | undefined>(response.httpStatusCode, "Unknown API Status Code!", await response.getBodyAsAny(), response.headers);
71+
}
72+
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Configuration } from '../configuration'
2+
3+
/**
4+
*
5+
* @export
6+
*/
7+
export const COLLECTION_FORMATS = {
8+
csv: ",",
9+
ssv: " ",
10+
tsv: "\t",
11+
pipes: "|",
12+
};
13+
14+
15+
/**
16+
*
17+
* @export
18+
* @class BaseAPI
19+
*/
20+
export class BaseAPIRequestFactory {
21+
22+
constructor(protected configuration: Configuration) {
23+
}
24+
};
25+
26+
/**
27+
*
28+
* @export
29+
* @class RequiredError
30+
* @extends {Error}
31+
*/
32+
export class RequiredError extends Error {
33+
name: "RequiredError" = "RequiredError";
34+
constructor(public api: string, public method: string, public field: string) {
35+
super("Required parameter " + field + " was null or undefined when calling " + api + "." + method + ".");
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Represents an error caused by an api call i.e. it has attributes for a HTTP status code
3+
* and the returned body object.
4+
*
5+
* Example
6+
* API returns a ErrorMessageObject whenever HTTP status code is not in [200, 299]
7+
* => ApiException(404, someErrorMessageObject)
8+
*
9+
*/
10+
export class ApiException<T> extends Error {
11+
public constructor(public code: number, message: string, public body: T, public headers: { [key: string]: string; }) {
12+
super("HTTP-Code: " + code + "\nMessage: " + message + "\nBody: " + JSON.stringify(body) + "\nHeaders: " +
13+
JSON.stringify(headers))
14+
}
15+
}

0 commit comments

Comments
 (0)