Skip to content

Commit 969cea8

Browse files
[Typescript] Fix oneOf (#9628)
* on of * fix map * samples * file to any * samples * add unit tests * clear code
1 parent 2e85ccd commit 969cea8

File tree

2 files changed

+150
-19
lines changed

2 files changed

+150
-19
lines changed

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

+93-19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import io.swagger.v3.oas.models.OpenAPI;
2121
import io.swagger.v3.oas.models.media.ArraySchema;
22+
import io.swagger.v3.oas.models.media.ComposedSchema;
2223
import io.swagger.v3.oas.models.media.Schema;
2324
import io.swagger.v3.oas.models.parameters.Parameter;
2425
import org.apache.commons.lang3.StringUtils;
@@ -32,6 +33,8 @@
3233
import java.io.File;
3334
import java.text.SimpleDateFormat;
3435
import java.util.*;
36+
import java.util.stream.Collectors;
37+
3538
import static org.openapitools.codegen.utils.StringUtils.camelize;
3639
import static org.openapitools.codegen.utils.StringUtils.underscore;
3740

@@ -144,10 +147,12 @@ public TypeScriptClientCodegen() {
144147
typeMapping.put("object", "any");
145148
typeMapping.put("integer", "number");
146149
typeMapping.put("Map", "any");
150+
typeMapping.put("map", "any");
147151
typeMapping.put("date", "string");
148152
typeMapping.put("DateTime", "Date");
149153
typeMapping.put("binary", "any");
150154
typeMapping.put("File", "any");
155+
typeMapping.put("file", "any");
151156
typeMapping.put("ByteArray", "string");
152157
typeMapping.put("UUID", "string");
153158
typeMapping.put("Error", "Error");
@@ -373,40 +378,56 @@ public String toVarName(String name) {
373378
}
374379

375380
@Override
376-
public String toModelName(String name) {
377-
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
381+
public String toModelName(final String name) {
382+
String fullModelName = name;
383+
fullModelName = addPrefix(fullModelName, modelNamePrefix);
384+
fullModelName = addSuffix(fullModelName, modelNameSuffix);
385+
return toTypescriptTypeName(fullModelName, "Model");
386+
}
378387

379-
if (!StringUtils.isEmpty(modelNamePrefix)) {
380-
name = modelNamePrefix + "_" + name;
388+
protected String addPrefix(String name, String prefix) {
389+
if (!StringUtils.isEmpty(prefix)) {
390+
name = prefix + "_" + name;
381391
}
392+
return name;
393+
}
382394

383-
if (!StringUtils.isEmpty(modelNameSuffix)) {
384-
name = name + "_" + modelNameSuffix;
395+
protected String addSuffix(String name, String suffix) {
396+
if (!StringUtils.isEmpty(suffix)) {
397+
name = name + "_" + suffix;
385398
}
386399

400+
return name;
401+
}
402+
403+
protected String toTypescriptTypeName(final String name, String safePrefix) {
404+
ArrayList<String> exceptions = new ArrayList<String>(Arrays.asList("\\|", " "));
405+
String sanName = sanitizeName(name, "(?![| ])\\W", exceptions);
406+
407+
sanName = camelize(sanName);
408+
387409
// model name cannot use reserved keyword, e.g. return
388-
if (isReservedWord(name)) {
389-
String modelName = camelize("model_" + name);
390-
LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName);
410+
// this is unlikely to happen, because we have just camelized the name, while reserved words are usually all lowcase
411+
if (isReservedWord(sanName)) {
412+
String modelName = safePrefix + sanName;
413+
LOGGER.warn(sanName + " (reserved word) cannot be used as model name. Renamed to " + modelName);
391414
return modelName;
392415
}
393416

394417
// model name starts with number
395-
if (name.matches("^\\d.*")) {
396-
String modelName = camelize("model_" + name); // e.g. 200Response => Model200Response (after camelize)
397-
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
418+
if (sanName.matches("^\\d.*")) {
419+
String modelName = safePrefix + sanName; // e.g. 200Response => Model200Response
420+
LOGGER.warn(sanName + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
398421
return modelName;
399422
}
400423

401-
if (languageSpecificPrimitives.contains(name)) {
402-
String modelName = camelize("model_" + name);
403-
LOGGER.warn(name + " (model name matches existing language type) cannot be used as a model name. Renamed to " + modelName);
424+
if (languageSpecificPrimitives.contains(sanName)) {
425+
String modelName = safePrefix + sanName;
426+
LOGGER.warn(sanName + " (model name matches existing language type) cannot be used as a model name. Renamed to " + modelName);
404427
return modelName;
405428
}
406429

407-
// camelize the model name
408-
// phone_number => PhoneNumber
409-
return camelize(name);
430+
return sanName;
410431
}
411432

412433
@Override
@@ -521,7 +542,9 @@ protected boolean isReservedWord(String word) {
521542
public String getSchemaType(Schema p) {
522543
String openAPIType = super.getSchemaType(p);
523544
String type = null;
524-
if (typeMapping.containsKey(openAPIType)) {
545+
if (ModelUtils.isComposedSchema(p)) {
546+
return openAPIType;
547+
} else if (typeMapping.containsKey(openAPIType)) {
525548
type = typeMapping.get(openAPIType);
526549
if (languageSpecificPrimitives.contains(type))
527550
return type;
@@ -853,4 +876,55 @@ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Sc
853876
codegenModel.additionalPropertiesType = getTypeDeclaration((Schema) schema.getAdditionalProperties());
854877
addImport(codegenModel, codegenModel.additionalPropertiesType);
855878
}
879+
880+
@Override
881+
public String toAnyOfName(List<String> names, ComposedSchema composedSchema) {
882+
List<String> types = getTypesFromSchemas(composedSchema.getAnyOf());
883+
884+
return String.join(" | ", types);
885+
}
886+
887+
@Override
888+
public String toOneOfName(List<String> names, ComposedSchema composedSchema) {
889+
List<String> types = getTypesFromSchemas(composedSchema.getOneOf());
890+
891+
return String.join(" | ", types);
892+
}
893+
894+
/**
895+
* Extracts the list of type names from a list of schemas.
896+
* Excludes `AnyType` if there are other valid types extracted.
897+
*
898+
* @param schemas list of schemas
899+
* @return list of types
900+
*/
901+
protected List<String> getTypesFromSchemas(List<Schema> schemas) {
902+
List<Schema> filteredSchemas = schemas.size() > 1
903+
? schemas.stream().filter(schema -> !"AnyType".equals(super.getSchemaType(schema))).collect(Collectors.toList())
904+
: schemas;
905+
906+
return filteredSchemas.stream().map(schema -> {
907+
String schemaType = getSchemaType(schema);
908+
if (ModelUtils.isArraySchema(schema)) {
909+
ArraySchema ap = (ArraySchema) schema;
910+
Schema inner = ap.getItems();
911+
schemaType = schemaType + "<" + getSchemaType(inner) + ">";
912+
}
913+
return schemaType;
914+
}).distinct().collect(Collectors.toList());
915+
}
916+
917+
@Override
918+
protected void addImport(CodegenModel m, String type) {
919+
if (type == null) {
920+
return;
921+
}
922+
923+
String[] parts = type.split("( [|&] )|[<>]");
924+
for (String s : parts) {
925+
if (needToImport(s)) {
926+
m.imports.add(s);
927+
}
928+
}
929+
}
856930
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.openapitools.codegen.typescript;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.media.*;
5+
import org.openapitools.codegen.CodegenModel;
6+
import org.openapitools.codegen.DefaultCodegen;
7+
import org.openapitools.codegen.TestUtils;
8+
import org.openapitools.codegen.languages.TypeScriptClientCodegen;
9+
import org.openapitools.codegen.utils.ModelUtils;
10+
import org.testng.Assert;
11+
import org.testng.annotations.Test;
12+
13+
14+
public class TypeScriptClientModelTest {
15+
16+
@Test(description = "convert an array oneof model")
17+
public void arrayOneOfModelTest() {
18+
final Schema schema = new ArraySchema()
19+
.items(new ComposedSchema()
20+
.addOneOfItem(new StringSchema())
21+
.addOneOfItem(new IntegerSchema().format("int64")))
22+
.description("an array oneof model");
23+
final DefaultCodegen codegen = new TypeScriptClientCodegen();
24+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema);
25+
codegen.setOpenAPI(openAPI);
26+
final CodegenModel cm = codegen.fromModel("sample", schema);
27+
28+
29+
Assert.assertEquals(cm.name, "sample");
30+
Assert.assertEquals(cm.classname, "Sample");
31+
Assert.assertEquals(cm.description, "an array oneof model");
32+
Assert.assertEquals(cm.arrayModelType, "string | number");
33+
Assert.assertEquals(cm.vars.size(), 0);
34+
}
35+
36+
@Test(description = "convert an any of with array oneof model")
37+
public void objectPropertyAnyOfWithArrayOneOfModelTest() {
38+
final Schema schema = new ObjectSchema().addProperties("value",
39+
new ComposedSchema().addAnyOfItem(new StringSchema()).addAnyOfItem(new ArraySchema()
40+
.items(new ComposedSchema()
41+
.addOneOfItem(new StringSchema())
42+
.addOneOfItem(new IntegerSchema().format("int64")))))
43+
.description("an any of with array oneof model");
44+
final DefaultCodegen codegen = new TypeScriptClientCodegen();
45+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema);
46+
codegen.setOpenAPI(openAPI);
47+
final CodegenModel cm = codegen.fromModel("sample", schema);
48+
49+
String s = codegen.getSchemaType((Schema)schema.getProperties().get("value"));
50+
51+
Assert.assertEquals(cm.name, "sample");
52+
Assert.assertEquals(cm.classname, "Sample");
53+
Assert.assertEquals(cm.description, "an any of with array oneof model");
54+
Assert.assertEquals(cm.vars.size(), 1);
55+
Assert.assertEquals(s, "string | Array<string | number>");
56+
}
57+
}

0 commit comments

Comments
 (0)