From efefbaf7d8bc74661c8a524a146b1529d62241ee Mon Sep 17 00:00:00 2001 From: K Zhang Date: Sun, 27 Mar 2022 03:12:59 -0400 Subject: [PATCH] [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 --- .../codegen/languages/GoClientCodegen.java | 59 ++++++++++------- .../codegen/go/GoClientCodegenTest.java | 19 ++++++ ...ired-properties-has-same-oneOf-object.yaml | 65 +++++++++++++++++++ 3 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/petstore-multiple-required-properties-has-same-oneOf-object.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java index cfa851ba9b2c..03768178951e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java @@ -460,7 +460,7 @@ public class GoClientCodegen extends AbstractGoCodegen { objs = super.postProcessOperationsWithModels(objs, allModels); Map operations = (Map) objs.get("operations"); HashMap modelMaps = new HashMap(); - HashMap processedModelMaps = new HashMap(); + HashMap> 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 modelMaps, HashMap processedModelMap) { + private String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps, HashMap> 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 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 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 modelMaps, HashMap processedModelMap) { + private String constructExampleCode(CodegenProperty codegenProperty, HashMap modelMaps, HashMap> 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 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 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 modelMaps, HashMap processedModelMap) { + private String constructExampleCode(CodegenModel codegenModel, HashMap modelMaps, HashMap> 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 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 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 v = new ArrayList<>(); + v.add(depth); + processedModelMap.put(model, v); } List 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, ", ") + ")"; } -} +} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java index 3de21addf616..632308463303 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java @@ -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 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\")}"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/petstore-multiple-required-properties-has-same-oneOf-object.yaml b/modules/openapi-generator/src/test/resources/3_0/petstore-multiple-required-properties-has-same-oneOf-object.yaml new file mode 100644 index 000000000000..ce6d4b5bce71 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/petstore-multiple-required-properties-has-same-oneOf-object.yaml @@ -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'