[GoClient] Fixed StringIndexOutOfBoundsException when multiple properties has the same required $ref oneOf objects (#11973)

* Fixed StringIndexOutOfBoundsException when the object has multiple required fields has $ref to the same oneOf object

* fix bad depth

Co-authored-by: Kanda <kanda@synctera.com>
This commit is contained in:
K Zhang
2022-03-27 03:12:59 -04:00
committed by GitHub
parent 0a9429f1a8
commit efefbaf7d8
3 changed files with 120 additions and 23 deletions

View File

@@ -460,7 +460,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
objs = super.postProcessOperationsWithModels(objs, allModels);
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
HashMap<String, CodegenModel> modelMaps = new HashMap<String, CodegenModel>();
HashMap<String, Integer> processedModelMaps = new HashMap<String, Integer>();
HashMap<String, ArrayList<Integer>> processedModelMaps = new HashMap<>();
for (ModelMap modelMap : allModels) {
CodegenModel m = modelMap.getModel();
@@ -488,14 +488,14 @@ public class GoClientCodegen extends AbstractGoCodegen {
return objs;
}
private String constructExampleCode(CodegenParameter codegenParameter, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
private String constructExampleCode(CodegenParameter codegenParameter, HashMap<String, CodegenModel> modelMaps, HashMap<String, ArrayList<Integer>> processedModelMap) {
if (codegenParameter.isArray) { // array
String prefix = codegenParameter.dataType;
String dataType = StringUtils.removeStart(codegenParameter.dataType, "[]");
if (modelMaps.containsKey(dataType)) {
prefix = "[]" + goImportAlias + "." + dataType;
}
return prefix + "{" + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap) + "}";
return prefix + "{" + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap, 0) + "}";
} else if (codegenParameter.isMap) {
String prefix = codegenParameter.dataType;
String dataType = StringUtils.removeStart(codegenParameter.dataType, "map[string][]");
@@ -505,7 +505,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
if (codegenParameter.items == null) {
return prefix + "{ ... }";
}
return prefix + "{\"key\": " + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap) + "}";
return prefix + "{\"key\": " + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap, 0) + "}";
} else if (codegenParameter.isPrimitiveType) { // primitive type
if (codegenParameter.isString) {
if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
@@ -522,7 +522,9 @@ public class GoClientCodegen extends AbstractGoCodegen {
} else if (codegenParameter.isUri) { // URL
return "\"https://example.com\"";
} else if (codegenParameter.isDateTime || codegenParameter.isDate) { // datetime or date
processedModelMap.put("time.Time", 1);
ArrayList<Integer> v = new ArrayList<>();
v.add(1);
processedModelMap.put("time.Time", v);
return "time.Now()";
} else if (codegenParameter.isFile) {
return "os.NewFile(1234, \"some_file\")";
@@ -536,7 +538,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
} else { // model
// look up the model
if (modelMaps.containsKey(codegenParameter.dataType)) {
return constructExampleCode(modelMaps.get(codegenParameter.dataType), modelMaps, processedModelMap);
return constructExampleCode(modelMaps.get(codegenParameter.dataType), modelMaps, processedModelMap, 0);
} else if (codegenParameter.isEmail) { // email
if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
return "\"" + codegenParameter.example + "\"";
@@ -544,7 +546,9 @@ public class GoClientCodegen extends AbstractGoCodegen {
return "\"" + codegenParameter.paramName + "@example.com\"";
}
} else if (codegenParameter.isDateTime || codegenParameter.isDate) { // datetime or date
processedModelMap.put("time.Time", 1);
ArrayList<Integer> v = new ArrayList<>();
v.add(1);
processedModelMap.put("time.Time", v);
return "time.Now()";
} else {
//LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType);
@@ -553,7 +557,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
}
}
private String constructExampleCode(CodegenProperty codegenProperty, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
private String constructExampleCode(CodegenProperty codegenProperty, HashMap<String, CodegenModel> modelMaps, HashMap<String, ArrayList<Integer>> processedModelMap, int depth) {
if (codegenProperty.isArray) { // array
String prefix = codegenProperty.dataType;
String dataType = StringUtils.removeStart(codegenProperty.dataType, "[]");
@@ -564,7 +568,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
// We can't easily generate a pointer inline, so just use nil in that case
return prefix + "{nil}";
}
return prefix + "{" + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "}";
return prefix + "{" + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap, depth+1) + "}";
} else if (codegenProperty.isMap) { // map
String prefix = codegenProperty.dataType;
String dataType = StringUtils.removeStart(codegenProperty.dataType, "map[string][]");
@@ -574,7 +578,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
if (codegenProperty.items == null) {
return prefix + "{ ... }";
}
return prefix + "{\"key\": " + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "}";
return prefix + "{\"key\": " + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap, depth+1) + "}";
} else if (codegenProperty.isPrimitiveType) { // primitive type
if (codegenProperty.isString) {
if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
@@ -591,7 +595,9 @@ public class GoClientCodegen extends AbstractGoCodegen {
} else if (codegenProperty.isUri) { // URL
return "\"https://example.com\"";
} else if (codegenProperty.isDateTime || codegenProperty.isDate) { // datetime or date
processedModelMap.put("time.Time", 1);
ArrayList<Integer> v = new ArrayList<>();
v.add(1);
processedModelMap.put("time.Time", v);
return "time.Now()";
} else { // numeric
String example;
@@ -606,7 +612,7 @@ public class GoClientCodegen extends AbstractGoCodegen {
} else {
// look up the model
if (modelMaps.containsKey(codegenProperty.dataType)) {
return constructExampleCode(modelMaps.get(codegenProperty.dataType), modelMaps, processedModelMap);
return constructExampleCode(modelMaps.get(codegenProperty.dataType), modelMaps, processedModelMap, depth+1);
} else if (codegenProperty.isEmail) { // email
if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
return "\"" + codegenProperty.example + "\"";
@@ -614,7 +620,9 @@ public class GoClientCodegen extends AbstractGoCodegen {
return "\"" + codegenProperty.name + "@example.com\"";
}
} else if (codegenProperty.isDateTime || codegenProperty.isDate) { // datetime or date
processedModelMap.put("time.Time", 1);
ArrayList<Integer> v = new ArrayList<>();
v.add(1);
processedModelMap.put("time.Time", v);
return "time.Now()";
} else {
//LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenProperty.dataType);
@@ -623,17 +631,20 @@ public class GoClientCodegen extends AbstractGoCodegen {
}
}
private String constructExampleCode(CodegenModel codegenModel, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
private String constructExampleCode(CodegenModel codegenModel, HashMap<String, CodegenModel> modelMaps, HashMap<String, ArrayList<Integer>> processedModelMap, int depth) {
// break infinite recursion. Return, in case a model is already processed in the current context.
String model = codegenModel.name;
if (processedModelMap.containsKey(model)) {
int count = processedModelMap.get(model);
if (count == 1) {
processedModelMap.put(model, 2);
} else if (count == 2) {
ArrayList<Integer> depthList = processedModelMap.get(model);
if (depthList.size() == 1) {
if (depthList.get(0) != depth) {
depthList.add(depth);
processedModelMap.put(model, depthList);
}
} else if (depthList.size() == 2) {
return "";
} else {
throw new RuntimeException("Invalid count when constructing example: " + count);
throw new RuntimeException("Invalid count when constructing example: " + depthList.size());
}
} else if (codegenModel.isEnum) {
Map<String, Object> allowableValues = codegenModel.allowableValues;
@@ -645,16 +656,18 @@ public class GoClientCodegen extends AbstractGoCodegen {
return goImportAlias + "." + model + "(" + example + ")";
} else if (codegenModel.oneOf != null && !codegenModel.oneOf.isEmpty()) {
String subModel = (String) codegenModel.oneOf.toArray()[0];
String oneOf = constructExampleCode(modelMaps.get(subModel), modelMaps, processedModelMap).substring(1);
String oneOf = constructExampleCode(modelMaps.get(subModel), modelMaps, processedModelMap, depth+1).substring(1);
return goImportAlias + "." + model + "{" + subModel + ": " + oneOf + "}";
} else {
processedModelMap.put(model, 1);
ArrayList<Integer> v = new ArrayList<>();
v.add(depth);
processedModelMap.put(model, v);
}
List<String> propertyExamples = new ArrayList<>();
for (CodegenProperty codegenProperty : codegenModel.requiredVars) {
propertyExamples.add(constructExampleCode(codegenProperty, modelMaps, processedModelMap));
propertyExamples.add(constructExampleCode(codegenProperty, modelMaps, processedModelMap, depth+1));
}
return "*" + goImportAlias + ".New" + toModelName(model) + "(" + StringUtils.join(propertyExamples, ", ") + ")";
}
}
}

View File

@@ -158,4 +158,23 @@ public class GoClientCodegenTest {
TestUtils.assertFileContains(Paths.get(output + "/model_example.go"), "Child NullableChild");
}
@Test
public void testMultipleRequiredPropertiesHasSameOneOfObject() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("go")
.setInputSpec("src/test/resources/3_0/petstore-multiple-required-properties-has-same-oneOf-object.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
System.out.println(files);
files.forEach(File::deleteOnExit);
Path docFile = Paths.get(output + "/docs/PetApi.md");
TestUtils.assertFileContains(docFile, "openapiclient.pet{Cat: openapiclient.NewCat(\"Attr_example\")}, openapiclient.pet{Cat: openapiclient.NewCat(\"Attr_example\")}, openapiclient.pet{Cat: openapiclient.NewCat(\"Attr_example\")}");
}
}

View File

@@ -0,0 +1,65 @@
openapi: 3.0.1
info:
title: My title
description: API under test
version: 1.0.7
servers:
- url: https://localhost:9999/root
paths:
/pet_preference:
post:
operationId: postPreference
tags:
- pet
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/petPreference'
responses:
201:
description: OK
components:
schemas:
dog:
type: object
required:
- attr
properties:
attr:
type: string
enum:
- DOG
cat:
type: object
required:
- attr
properties:
attr:
type: string
enum:
- CAT
pet:
oneOf:
- $ref: '#/components/schemas/dog'
- $ref: '#/components/schemas/cat'
discriminator:
propertyName: attr
mapping:
DOG: '#/components/schemas/dog'
CAT: '#/components/schemas/cat'
petPreference:
type: object
required:
- pet1
- pet2
- pet3
properties:
pet1:
$ref: '#/components/schemas/pet'
pet2:
$ref: '#/components/schemas/pet'
pet3:
$ref: '#/components/schemas/pet'