Skip to content

Commit a0de89f

Browse files
committed
feat: enhance oneOf handling in TypeScript code generators with string and array support
1 parent b2f164d commit a0de89f

5 files changed

Lines changed: 134 additions & 20 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,19 @@ public void setIsEnum(boolean isEnum) {
961961
this.isEnum = isEnum;
962962
}
963963

964+
@SuppressWarnings("unchecked")
965+
public List<String> getAllowableValuesList() {
966+
return Optional.ofNullable(this.getAllowableValues())
967+
.map(allowableValues -> allowableValues.get("values"))
968+
.flatMap(valuesList -> {
969+
try {
970+
return Optional.ofNullable((List<String>)valuesList);
971+
} catch (ClassCastException e) {
972+
return Optional.empty();
973+
}
974+
})
975+
.orElse(Collections.emptyList());
976+
}
964977

965978
@Override
966979
public String toString() {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,6 +2716,11 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
27162716
m.anyOf.add(languageType);
27172717
}
27182718
} else if (composed.getOneOf() != null) {
2719+
if (ModelUtils.isStringSchema(interfaceSchema) && ModelUtils.isEnumSchema(interfaceSchema)) {
2720+
// use string union literals
2721+
languageType = getEnumStringLiteralUnionType(interfaceSchema);
2722+
}
2723+
27192724
if (m.oneOf.contains(languageType)) {
27202725
LOGGER.debug("{} (oneOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
27212726
} else {
@@ -2831,6 +2836,13 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
28312836
// end of code block for composed schema
28322837
}
28332838

2839+
@SuppressWarnings("unchecked")
2840+
private static String getEnumStringLiteralUnionType(Schema interfaceSchema) {
2841+
return (String) interfaceSchema.getEnum().stream()
2842+
.map(enumName -> "\"" + (String)enumName + "\"")
2843+
.collect(Collectors.joining(" | "));
2844+
}
2845+
28342846
/**
28352847
* Combines all previously-detected type entries for a schema with newly-discovered ones, to ensure
28362848
* that schema for items like enum include all possible values.

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

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -782,16 +782,39 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) {
782782
}
783783
}
784784
}
785+
786+
cm.hasStringOneOf = cm.oneOf.contains("string");
787+
cm.hasStringArrayOneOf = cm.oneOf.contains("Array<string>");
788+
789+
List<CodegenProperty> oneOfsList = Optional.ofNullable(cm.getComposedSchemas())
790+
.map(CodegenComposedSchemas::getOneOf)
791+
.orElse(Collections.emptyList());
792+
793+
cm.oneOfModels = oneOfsList.stream()
794+
.filter(CodegenProperty::getIsModel)
795+
.map(CodegenProperty::getBaseType)
796+
.filter(Objects::nonNull)
797+
.collect(Collectors.toCollection(TreeSet::new));
798+
799+
cm.oneOfStringEnums = oneOfsList.stream()
800+
.filter(CodegenProperty::getIsEnum)
801+
.map(CodegenProperty::getAllowableValuesList)
802+
.flatMap(Collection::stream)
803+
.collect(Collectors.toCollection(TreeSet::new));
804+
805+
cm.oneOfArrays = oneOfsList.stream()
806+
.filter(CodegenProperty::getIsArray)
807+
.map(CodegenProperty::getComplexType)
808+
.filter(Objects::nonNull)
809+
.collect(Collectors.toCollection(TreeSet::new));
810+
785811
if (!cm.oneOf.isEmpty()) {
786812
// 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;
813+
cm.imports = cm.imports.stream()
814+
.filter(im -> cm.oneOfModels.contains(im) || cm.oneOfArrays.contains(im))
815+
.collect(Collectors.toCollection(TreeSet::new));
794816
}
817+
795818
return cm;
796819
}
797820

@@ -1474,6 +1497,16 @@ public String toString() {
14741497
public class ExtendedCodegenModel extends CodegenModel {
14751498
@Getter @Setter
14761499
public Set<String> modelImports = new TreeSet<String>();
1500+
1501+
@Getter @Setter
1502+
public Set<String> oneOfModels = new TreeSet<>();
1503+
@Getter @Setter
1504+
public Set<String> oneOfStringEnums = new TreeSet<>();
1505+
@Getter @Setter
1506+
public Set<String> oneOfArrays = new TreeSet<>();
1507+
1508+
public boolean hasStringOneOf;
1509+
public boolean hasStringArrayOneOf;
14771510
public boolean isEntity; // Is a model containing an "id" property marked as isUniqueId
14781511
public String returnPassthrough;
14791512
public boolean hasReturnPassthroughVoid;
@@ -1570,6 +1603,7 @@ public ExtendedCodegenModel(CodegenModel cm) {
15701603
this.setItems(cm.getItems());
15711604
this.setAdditionalProperties(cm.getAdditionalProperties());
15721605
this.setIsModel(cm.getIsModel());
1606+
this.setComposedSchemas(cm.getComposedSchemas());
15731607
}
15741608

15751609
@Override

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.TreeSet;
35+
import java.util.stream.Collectors;
3536

3637
public class TypeScriptReduxQueryClientCodegen extends AbstractTypeScriptClientCodegen {
3738

@@ -145,13 +146,9 @@ public ModelsMap postProcessModels(ModelsMap objs) {
145146
}
146147
if (!cm.oneOf.isEmpty()) {
147148
// For oneOfs only import $refs within the oneOf
148-
TreeSet<String> oneOfRefs = new TreeSet<>();
149-
for (String im : cm.imports) {
150-
if (cm.oneOf.contains(im)) {
151-
oneOfRefs.add(im);
152-
}
153-
}
154-
cm.imports = oneOfRefs;
149+
cm.imports = cm.imports.stream()
150+
.filter(cm.oneOf::contains)
151+
.collect(Collectors.toCollection(TreeSet::new));
155152
}
156153
}
157154

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

Lines changed: 64 additions & 6 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,40 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
3131
}
3232
{{/discriminator}}
3333
{{^discriminator}}
34-
{{#oneOf}}
34+
{{#oneOfStringEnums}}
35+
if (json === "{{{.}}}") {
36+
return json;
37+
}
38+
{{/oneOfStringEnums}}
39+
{{#hasStringOneOf}}
40+
if (json instanceof String) {
41+
return json as {{classname}};
42+
}
43+
{{/hasStringOneOf}}
44+
if (!(json instanceof Object)){
45+
return {} as any;
46+
}
47+
{{#hasStringArrayOneOf}}
48+
if (Array.isArray(json) && json.every(item => item instanceof String)) {
49+
return json as Array<string>;
50+
}
51+
{{/hasStringArrayOneOf}}
52+
{{#oneOfModels}}
3553
if (instanceOf{{{.}}}(json)) {
3654
return {{{.}}}FromJSONTyped(json, true);
3755
}
38-
{{/oneOf}}
56+
{{/oneOfModels}}
57+
{{#oneOfArrays}}
58+
{{#-first}}
59+
if (Array.isArray(json) && json.every(item => item instanceof Object)) {
60+
{{/-first}}
61+
if (json.every(item => instanceOf{{{.}}}(item as Object))) {
62+
return json.map(value => {{{.}}}FromJSONTyped(value, true));
63+
}
64+
{{#-last}}
65+
}
66+
{{/-last}}
67+
{{/oneOfArrays}}
3968

4069
return {} as any;
4170
{{/discriminator}}
@@ -61,11 +90,40 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis
6190
{{/discriminator}}
6291

6392
{{^discriminator}}
64-
{{#oneOf}}
93+
{{#oneOfStringEnums}}
94+
if (value === "{{{.}}}") {
95+
return value;
96+
}
97+
{{/oneOfStringEnums}}
98+
{{#hasStringOneOf}}
99+
if (value instanceof String) {
100+
return value;
101+
}
102+
{{/hasStringOneOf}}
103+
if (!(value instanceof Object)){
104+
return {};
105+
}
106+
{{#hasStringArrayOneOf}}
107+
if (Array.isArray(value) && value.every(item => item instanceof String)) {
108+
return value;
109+
}
110+
{{/hasStringArrayOneOf}}
111+
{{#oneOfModels}}
65112
if (instanceOf{{{.}}}(value)) {
66113
return {{{.}}}ToJSON(value as {{{.}}});
67114
}
68-
{{/oneOf}}
115+
{{/oneOfModels}}
116+
{{#oneOfArrays}}
117+
{{#-first}}
118+
if (Array.isArray(value) && value.every(item => item instanceof Object)) {
119+
{{/-first}}
120+
if (value.every(item => instanceOf{{{.}}}(item as Object))) {
121+
return value.map(value => {{{.}}}ToJSON(value as {{{.}}}));
122+
}
123+
{{#-last}}
124+
}
125+
{{/-last}}
126+
{{/oneOfArrays}}
69127

70128
return {};
71129
{{/discriminator}}

0 commit comments

Comments
 (0)