Skip to content

Commit f1a0935

Browse files
authored
[typescript-fetch] oneOf models now consider primitives when converting. Issue #21259 (#21464)
* [typescript-fetch] number, string, and Date now considered in oneOf models. Issue #21259 * Generated samples
1 parent 777b7ee commit f1a0935

File tree

6 files changed

+232
-6
lines changed

6 files changed

+232
-6
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,11 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) {
799799
.filter(Objects::nonNull)
800800
.collect(Collectors.toCollection(TreeSet::new));
801801

802+
cm.oneOfPrimitives = oneOfsList.stream()
803+
.filter(CodegenProperty::getIsPrimitiveType)
804+
.filter(Objects::nonNull)
805+
.collect(Collectors.toCollection(HashSet::new));
806+
802807
if (!cm.oneOf.isEmpty()) {
803808
// For oneOfs only import $refs within the oneOf
804809
cm.imports = cm.imports.stream()
@@ -1484,6 +1489,8 @@ public class ExtendedCodegenModel extends CodegenModel {
14841489
public Set<String> oneOfModels = new TreeSet<>();
14851490
@Getter @Setter
14861491
public Set<String> oneOfArrays = new TreeSet<>();
1492+
@Getter @Setter
1493+
public Set<CodegenProperty> oneOfPrimitives = new HashSet<>();
14871494

14881495
public boolean isEntity; // Is a model containing an "id" property marked as isUniqueId
14891496
public String returnPassthrough;

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

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,64 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
6565
}
6666
{{/-last}}
6767
{{/oneOfArrays}}
68-
68+
{{#oneOfPrimitives}}
69+
{{#isArray}}
70+
{{#items}}
71+
{{#isDateType}}
72+
if (Array.isArray(json)) {
73+
if (json.every(item => !(isNaN(new Date(json).getTime()))) {
74+
return json.map(value => new Date(json);
75+
}
76+
}
77+
{{/isDateType}}
78+
{{#isDateTimeType}}
79+
if (Array.isArray(json)) {
80+
if (json.every(item => !(isNaN(new Date(json).getTime()))) {
81+
return json.map(value => new Date(json);
82+
}
83+
}
84+
{{/isDateTimeType}}
85+
{{#isNumeric}}
86+
if (Array.isArray(json)) {
87+
if (json.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) {
88+
return json;
89+
}
90+
}
91+
{{/isNumeric}}
92+
{{#isString}}
93+
if (Array.isArray(json)) {
94+
if (json.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) {
95+
return json;
96+
}
97+
}
98+
{{/isString}}
99+
{{/items}}
100+
{{/isArray}}
101+
{{^isArray}}
102+
{{#isDateType}}
103+
if(!(isNaN(new Date(json).getTime()))) {
104+
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json));
105+
}
106+
{{/isDateType}}
107+
{{^isDateType}}
108+
{{#isDateTimeType}}
109+
if(!(isNaN(new Date(json).getTime()))) {
110+
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json));
111+
}
112+
{{/isDateTimeType}}
113+
{{/isDateType}}
114+
{{#isNumeric}}
115+
if(typeof json === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
116+
return json;
117+
}
118+
{{/isNumeric}}
119+
{{#isString}}
120+
if(typeof json === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
121+
return json;
122+
}
123+
{{/isString}}
124+
{{/isArray}}
125+
{{/oneOfPrimitives}}
69126
return {} as any;
70127
{{/discriminator}}
71128
}
@@ -113,7 +170,62 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis
113170
}
114171
{{/-last}}
115172
{{/oneOfArrays}}
116-
173+
{{#oneOfPrimitives}}
174+
{{#isArray}}
175+
{{#items}}
176+
{{#isDateType}}
177+
if (Array.isArray(value)) {
178+
if (value.every(item => item instanceof Date) {
179+
return value.map(value => value.toISOString().substring(0,10)));
180+
}
181+
}
182+
{{/isDateType}}
183+
{{#isDateTimeType}}
184+
if (Array.isArray(value)) {
185+
if (value.every(item => item instanceof Date) {
186+
return value.map(value => value.toISOString();
187+
}
188+
}
189+
{{/isDateTimeType}}
190+
{{#isNumeric}}
191+
if (Array.isArray(value)) {
192+
if (value.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
193+
return value;
194+
}
195+
}
196+
{{/isNumeric}}
197+
{{#isString}}
198+
if (Array.isArray(value)) {
199+
if (value.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
200+
return value;
201+
}
202+
}
203+
{{/isString}}
204+
{{/items}}
205+
{{/isArray}}
206+
{{^isArray}}
207+
{{#isDateType}}
208+
if(value instanceof Date) {
209+
return ((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString().substring(0,10));
210+
}
211+
{{/isDateType}}
212+
{{#isDateTimeType}}
213+
if(value instanceof Date) {
214+
return {{^required}}{{#isNullable}}value === null ? null : {{/isNullable}}{{^isNullable}}value == null ? undefined : {{/isNullable}}{{/required}}((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString());
215+
}
216+
{{/isDateTimeType}}
217+
{{#isNumeric}}
218+
if(typeof value === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
219+
return value;
220+
}
221+
{{/isNumeric}}
222+
{{#isString}}
223+
if(typeof value === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) {
224+
return value;
225+
}
226+
{{/isString}}
227+
{{/isArray}}
228+
{{/oneOfPrimitives}}
117229
return {};
118230
{{/discriminator}}
119231
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,39 @@ public void testGeneratedFilenamesInCamelCaseWithAdditionalModelPrefix() throws
347347
TestUtils.assertFileExists(Paths.get(output + "/apis/petControllerApi.ts"));
348348
}
349349

350+
@Test(description = "Issue #21295")
351+
public void givenSchemaIsOneOfAndComposedSchemasArePrimitiveThenReturnStatementsAreCorrect() throws Exception {
352+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
353+
output.deleteOnExit();
354+
String outputPath = output.getAbsolutePath();
355+
356+
357+
TypeScriptFetchClientCodegen clientCodegen = new TypeScriptFetchClientCodegen();
358+
clientCodegen.setOutputDir(outputPath);
359+
360+
DefaultGenerator defaultGenerator = new DefaultGenerator();
361+
defaultGenerator.opts(
362+
new ClientOptInput().openAPI(TestUtils.parseSpec("src/test/resources/bugs/issue_21259.yaml"))
363+
.config(clientCodegen)
364+
).generate();
365+
366+
Path exampleModelPath = Paths.get(outputPath + "/models/MyCustomSpeed.ts");
367+
//FromJSON
368+
TestUtils.assertFileContains(exampleModelPath, "typeof json === 'number'");
369+
TestUtils.assertFileContains(exampleModelPath, "typeof json === 'string'");
370+
TestUtils.assertFileContains(exampleModelPath, "json === 'fixed-value-a' || json === 'fixed-value-b' || json === 'fixed-value-c'");
371+
TestUtils.assertFileContains(exampleModelPath, "isNaN(new Date(json).getTime())");
372+
TestUtils.assertFileContains(exampleModelPath, "json.every(item => typeof item === 'number'");
373+
// TestUtils.assertFileContains(exampleModelPath, "json.every(item => typeof item === 'string' && (item === 'oneof-array-enum-a' || item oneof-array-enum-b || item === oneof-array-enum-c)");
374+
//ToJSON
375+
TestUtils.assertFileContains(exampleModelPath, "typeof value === 'number'");
376+
TestUtils.assertFileContains(exampleModelPath, "typeof value === 'string'");
377+
TestUtils.assertFileContains(exampleModelPath, "value === 'fixed-value-a' || value === 'fixed-value-b' || value === 'fixed-value-c'");
378+
TestUtils.assertFileContains(exampleModelPath, "value instanceof Date");
379+
TestUtils.assertFileContains(exampleModelPath, "value.every(item => typeof item === 'number'");
380+
// TestUtils.assertFileContains(exampleModelPath, "value.every(item => typeof item === 'string' && (item === 'oneof-array-enum-a' || item oneof-array-enum-b || item === oneof-array-enum-c)");
381+
}
382+
350383
private static File generate(Map<String, Object> properties) throws IOException {
351384
File output = Files.createTempDirectory("test").toFile();
352385
output.deleteOnExit();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#Modified from the original
2+
openapi: 3.0.1
3+
info:
4+
title: Minimal API for Bug Report
5+
version: v1
6+
paths:
7+
/test:
8+
post:
9+
summary: Test endpoint with MyCustomSpeed
10+
requestBody:
11+
required: true
12+
content:
13+
application/json:
14+
schema:
15+
$ref: '#/components/schemas/TestPayload'
16+
responses:
17+
'200':
18+
description: OK
19+
components:
20+
schemas:
21+
MyNumericValue:
22+
type: object
23+
properties:
24+
lmnop:
25+
type: number
26+
description: A numeric value (e.g., 0 to 1).
27+
MyCustomSpeed:
28+
oneOf:
29+
- $ref: '#/components/schemas/MyNumericValue'
30+
- type: string
31+
enum:
32+
- "fixed-value-a"
33+
- "fixed-value-b"
34+
- "fixed-value-c"
35+
- type: string
36+
format: date
37+
- type: string
38+
format: date-time
39+
- type: integer
40+
format: int64
41+
enum: [10, 20, 30]
42+
- type: array
43+
items:
44+
type: number
45+
- type: array
46+
items:
47+
- type: object
48+
- type: array
49+
items:
50+
- type: string
51+
enum:
52+
# It seems enums within arrays don't work. Leaving this here, though
53+
- "oneof-array-enum-a"
54+
- "oneof-array-enum-b"
55+
- "oneof-array-enum-c"
56+
- type:
57+
description: A value that can be a number or a specific string.
58+
TestPayload:
59+
type: object
60+
properties:
61+
speed_setting:
62+
$ref: '#/components/schemas/MyCustomSpeed'

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ export function TestArrayResponseFromJSONTyped(json: any, ignoreDiscriminator: b
5353
}
5454
return json;
5555
}
56-
56+
if (Array.isArray(json)) {
57+
if (json.every(item => typeof item === 'string')) {
58+
return json;
59+
}
60+
}
5761
return {} as any;
5862
}
5963

@@ -76,7 +80,11 @@ export function TestArrayResponseToJSONTyped(value?: TestArrayResponse | null, i
7680
}
7781
return value;
7882
}
79-
83+
if (Array.isArray(value)) {
84+
if (value.every(item => typeof item === 'string') {
85+
return value;
86+
}
87+
}
8088
return {};
8189
}
8290

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export function TestResponseFromJSONTyped(json: any, ignoreDiscriminator: boolea
5151
if (instanceOfTestB(json)) {
5252
return TestBFromJSONTyped(json, true);
5353
}
54-
54+
if(typeof json === 'string') {
55+
return json;
56+
}
5557
return {} as any;
5658
}
5759

@@ -72,7 +74,9 @@ export function TestResponseToJSONTyped(value?: TestResponse | null, ignoreDiscr
7274
if (instanceOfTestB(value)) {
7375
return TestBToJSON(value as TestB);
7476
}
75-
77+
if(typeof value === 'string') {
78+
return value;
79+
}
7680
return {};
7781
}
7882

0 commit comments

Comments
 (0)