Skip to content

Commit a94b8f9

Browse files
Fix typescript-fetch broken files when mixing basic types and refs in oneOf (OpenAPITools#21057)
* feat: add test-array endpoint and TestArrayResponse schema to oneOf.yaml * feat: enhance oneOf handling in TypeScript code generators with string and array support * fix: correct type checks for string and object arrays in modelOneOf.mustache * refactor: remove oneOfStringEnums handling and simplify oneOf logic in TypeScript code generators * chore: update samples * refactor: remove unnecessary string oneOf checks in TypeScriptFetchClientCodegen
1 parent 79b1cc5 commit a94b8f9

File tree

8 files changed

+213
-17
lines changed

8 files changed

+213
-17
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -782,16 +782,30 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) {
782782
}
783783
}
784784
}
785+
786+
List<CodegenProperty> oneOfsList = Optional.ofNullable(cm.getComposedSchemas())
787+
.map(CodegenComposedSchemas::getOneOf)
788+
.orElse(Collections.emptyList());
789+
790+
cm.oneOfModels = oneOfsList.stream()
791+
.filter(CodegenProperty::getIsModel)
792+
.map(CodegenProperty::getBaseType)
793+
.filter(Objects::nonNull)
794+
.collect(Collectors.toCollection(TreeSet::new));
795+
796+
cm.oneOfArrays = oneOfsList.stream()
797+
.filter(CodegenProperty::getIsArray)
798+
.map(CodegenProperty::getComplexType)
799+
.filter(Objects::nonNull)
800+
.collect(Collectors.toCollection(TreeSet::new));
801+
785802
if (!cm.oneOf.isEmpty()) {
786803
// For oneOfs only import $refs within the oneOf
787-
TreeSet<String> oneOfRefs = new TreeSet<>();
788-
for (String im : cm.imports) {
789-
if (cm.oneOf.contains(im)) {
790-
oneOfRefs.add(im);
791-
}
792-
}
793-
cm.imports = oneOfRefs;
804+
cm.imports = cm.imports.stream()
805+
.filter(im -> cm.oneOfModels.contains(im) || cm.oneOfArrays.contains(im))
806+
.collect(Collectors.toCollection(TreeSet::new));
794807
}
808+
795809
return cm;
796810
}
797811

@@ -1474,6 +1488,12 @@ public String toString() {
14741488
public class ExtendedCodegenModel extends CodegenModel {
14751489
@Getter @Setter
14761490
public Set<String> modelImports = new TreeSet<String>();
1491+
1492+
@Getter @Setter
1493+
public Set<String> oneOfModels = new TreeSet<>();
1494+
@Getter @Setter
1495+
public Set<String> oneOfArrays = new TreeSet<>();
1496+
14771497
public boolean isEntity; // Is a model containing an "id" property marked as isUniqueId
14781498
public String returnPassthrough;
14791499
public boolean hasReturnPassthroughVoid;
@@ -1570,6 +1590,7 @@ public ExtendedCodegenModel(CodegenModel cm) {
15701590
this.setItems(cm.getItems());
15711591
this.setAdditionalProperties(cm.getAdditionalProperties());
15721592
this.setIsModel(cm.getIsModel());
1593+
this.setComposedSchemas(cm.getComposedSchemas());
15731594
}
15741595

15751596
@Override

modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{{#hasImports}}
2-
{{#oneOf}}
2+
{{#imports}}
33
import type { {{{.}}} } from './{{.}}{{importFileExtension}}';
44
import {
55
instanceOf{{{.}}},
66
{{{.}}}FromJSON,
77
{{{.}}}FromJSONTyped,
88
{{{.}}}ToJSON,
99
} from './{{.}}{{importFileExtension}}';
10-
{{/oneOf}}
10+
{{/imports}}
1111

1212
{{/hasImports}}
1313
{{>modelOneOfInterfaces}}
@@ -31,11 +31,30 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
3131
}
3232
{{/discriminator}}
3333
{{^discriminator}}
34-
{{#oneOf}}
34+
{{#oneOfModels}}
35+
{{#-first}}
36+
if (typeof json !== 'object') {
37+
return json;
38+
}
39+
{{/-first}}
3540
if (instanceOf{{{.}}}(json)) {
3641
return {{{.}}}FromJSONTyped(json, true);
3742
}
38-
{{/oneOf}}
43+
{{/oneOfModels}}
44+
{{#oneOfArrays}}
45+
{{#-first}}
46+
if (Array.isArray(json)) {
47+
if (json.every(item => typeof item === 'object')) {
48+
{{/-first}}
49+
if (json.every(item => instanceOf{{{.}}}(item))) {
50+
return json.map(value => {{{.}}}FromJSONTyped(value, true));
51+
}
52+
{{#-last}}
53+
}
54+
return json;
55+
}
56+
{{/-last}}
57+
{{/oneOfArrays}}
3958

4059
return {} as any;
4160
{{/discriminator}}
@@ -59,13 +78,31 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis
5978
return json;
6079
}
6180
{{/discriminator}}
62-
6381
{{^discriminator}}
64-
{{#oneOf}}
82+
{{#oneOfModels}}
83+
{{#-first}}
84+
if (typeof value !== 'object') {
85+
return value;
86+
}
87+
{{/-first}}
6588
if (instanceOf{{{.}}}(value)) {
6689
return {{{.}}}ToJSON(value as {{{.}}});
6790
}
68-
{{/oneOf}}
91+
{{/oneOfModels}}
92+
{{#oneOfArrays}}
93+
{{#-first}}
94+
if (Array.isArray(value)) {
95+
if (value.every(item => typeof item === 'object')) {
96+
{{/-first}}
97+
if (value.every(item => instanceOf{{{.}}}(item))) {
98+
return value.map(value => {{{.}}}ToJSON(value as {{{.}}}));
99+
}
100+
{{#-last}}
101+
}
102+
return value;
103+
}
104+
{{/-last}}
105+
{{/oneOfArrays}}
69106

70107
return {};
71108
{{/discriminator}}

modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,34 @@ paths:
1515
application/json:
1616
schema:
1717
$ref: '#/components/schemas/TestResponse'
18+
/test-array:
19+
get:
20+
operationId: testArray
21+
responses:
22+
200:
23+
description: OK
24+
content:
25+
application/json:
26+
schema:
27+
$ref: '#/components/schemas/TestArrayResponse'
1828
components:
1929
schemas:
30+
TestArrayResponse:
31+
oneOf:
32+
- type: array
33+
items:
34+
$ref: "#/components/schemas/TestA"
35+
- type: array
36+
items:
37+
$ref: "#/components/schemas/TestB"
38+
- type: array
39+
items:
40+
type: string
2041
TestResponse:
2142
oneOf:
2243
- $ref: "#/components/schemas/TestA"
2344
- $ref: "#/components/schemas/TestB"
45+
- type: string
2446
TestA:
2547
type: object
2648
properties:
@@ -34,4 +56,4 @@ components:
3456
bar:
3557
type: string
3658
required:
37-
- bar
59+
- bar

samples/client/petstore/typescript-fetch/builds/oneOf/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ apis/DefaultApi.ts
22
apis/index.ts
33
index.ts
44
models/TestA.ts
5+
models/TestArrayResponse.ts
56
models/TestB.ts
67
models/TestResponse.ts
78
models/index.ts

samples/client/petstore/typescript-fetch/builds/oneOf/apis/DefaultApi.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515

1616
import * as runtime from '../runtime';
1717
import type {
18+
TestArrayResponse,
1819
TestResponse,
1920
} from '../models/index';
2021
import {
22+
TestArrayResponseFromJSON,
23+
TestArrayResponseToJSON,
2124
TestResponseFromJSON,
2225
TestResponseToJSON,
2326
} from '../models/index';
@@ -51,4 +54,28 @@ export class DefaultApi extends runtime.BaseAPI {
5154
return await response.value();
5255
}
5356

57+
/**
58+
*/
59+
async testArrayRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<TestArrayResponse>> {
60+
const queryParameters: any = {};
61+
62+
const headerParameters: runtime.HTTPHeaders = {};
63+
64+
const response = await this.request({
65+
path: `/test-array`,
66+
method: 'GET',
67+
headers: headerParameters,
68+
query: queryParameters,
69+
}, initOverrides);
70+
71+
return new runtime.JSONApiResponse(response, (jsonValue) => TestArrayResponseFromJSON(jsonValue));
72+
}
73+
74+
/**
75+
*/
76+
async testArray(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<TestArrayResponse> {
77+
const response = await this.testArrayRaw(initOverrides);
78+
return await response.value();
79+
}
80+
5481
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
/**
4+
* testing oneOf without discriminator
5+
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6+
*
7+
* The version of the OpenAPI document: 1.0.0
8+
*
9+
*
10+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11+
* https://openapi-generator.tech
12+
* Do not edit the class manually.
13+
*/
14+
15+
import type { TestA } from './TestA';
16+
import {
17+
instanceOfTestA,
18+
TestAFromJSON,
19+
TestAFromJSONTyped,
20+
TestAToJSON,
21+
} from './TestA';
22+
import type { TestB } from './TestB';
23+
import {
24+
instanceOfTestB,
25+
TestBFromJSON,
26+
TestBFromJSONTyped,
27+
TestBToJSON,
28+
} from './TestB';
29+
30+
/**
31+
* @type TestArrayResponse
32+
*
33+
* @export
34+
*/
35+
export type TestArrayResponse = Array<TestA> | Array<TestB> | Array<string>;
36+
37+
export function TestArrayResponseFromJSON(json: any): TestArrayResponse {
38+
return TestArrayResponseFromJSONTyped(json, false);
39+
}
40+
41+
export function TestArrayResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): TestArrayResponse {
42+
if (json == null) {
43+
return json;
44+
}
45+
if (Array.isArray(json)) {
46+
if (json.every(item => typeof item === 'object')) {
47+
if (json.every(item => instanceOfTestA(item))) {
48+
return json.map(value => TestAFromJSONTyped(value, true));
49+
}
50+
if (json.every(item => instanceOfTestB(item))) {
51+
return json.map(value => TestBFromJSONTyped(value, true));
52+
}
53+
}
54+
return json;
55+
}
56+
57+
return {} as any;
58+
}
59+
60+
export function TestArrayResponseToJSON(json: any): any {
61+
return TestArrayResponseToJSONTyped(json, false);
62+
}
63+
64+
export function TestArrayResponseToJSONTyped(value?: TestArrayResponse | null, ignoreDiscriminator: boolean = false): any {
65+
if (value == null) {
66+
return value;
67+
}
68+
if (Array.isArray(value)) {
69+
if (value.every(item => typeof item === 'object')) {
70+
if (value.every(item => instanceOfTestA(item))) {
71+
return value.map(value => TestAToJSON(value as TestA));
72+
}
73+
if (value.every(item => instanceOfTestB(item))) {
74+
return value.map(value => TestBToJSON(value as TestB));
75+
}
76+
}
77+
return value;
78+
}
79+
80+
return {};
81+
}
82+

samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
*
3333
* @export
3434
*/
35-
export type TestResponse = TestA | TestB;
35+
export type TestResponse = TestA | TestB | string;
3636

3737
export function TestResponseFromJSON(json: any): TestResponse {
3838
return TestResponseFromJSONTyped(json, false);
@@ -42,6 +42,9 @@ export function TestResponseFromJSONTyped(json: any, ignoreDiscriminator: boolea
4242
if (json == null) {
4343
return json;
4444
}
45+
if (typeof json !== 'object') {
46+
return json;
47+
}
4548
if (instanceOfTestA(json)) {
4649
return TestAFromJSONTyped(json, true);
4750
}
@@ -60,7 +63,9 @@ export function TestResponseToJSONTyped(value?: TestResponse | null, ignoreDiscr
6063
if (value == null) {
6164
return value;
6265
}
63-
66+
if (typeof value !== 'object') {
67+
return value;
68+
}
6469
if (instanceOfTestA(value)) {
6570
return TestAToJSON(value as TestA);
6671
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* tslint:disable */
22
/* eslint-disable */
33
export * from './TestA';
4+
export * from './TestArrayResponse';
45
export * from './TestB';
56
export * from './TestResponse';

0 commit comments

Comments
 (0)