diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index e9579cd82223..c56317eeea84 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -267,6 +267,8 @@ apiTemplateFiles are for API outputs only (controllers/handlers). // Support legacy logic for evaluating discriminators protected boolean legacyDiscriminatorBehavior = true; + private boolean uniqueModelMappings = false; + // Specify what to do if the 'additionalProperties' keyword is not present in a schema. // See CodegenConstants.java for more details. protected boolean disallowAdditionalPropertiesIfNotPresent = true; @@ -1365,6 +1367,14 @@ public void setLegacyDiscriminatorBehavior(boolean val) { this.legacyDiscriminatorBehavior = val; } + public boolean isUniqueModelMappings() { + return uniqueModelMappings; + } + + public void setUniqueModelMappings(boolean uniqueModelMappings) { + this.uniqueModelMappings = uniqueModelMappings; + } + public Boolean getDisallowAdditionalPropertiesIfNotPresent() { return disallowAdditionalPropertiesIfNotPresent; } @@ -3361,13 +3371,19 @@ protected List getAllOfDescendants(String thisSchemaName, OpenAPI o break; } currentSchemaName = queue.remove(0); - MappedModel mm = new MappedModel(currentSchemaName, toModelName(currentSchemaName)); - descendentSchemas.add(mm); Schema cs = schemas.get(currentSchemaName); Map vendorExtensions = cs.getExtensions(); - if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) { + boolean hasDiscriminatorValue = + vendorExtensions != null && !vendorExtensions.isEmpty() + && vendorExtensions.containsKey("x-discriminator-value"); + + if (!uniqueModelMappings || !hasDiscriminatorValue) { + MappedModel mm = new MappedModel(currentSchemaName, toModelName(currentSchemaName)); + descendentSchemas.add(mm); + } + if (hasDiscriminatorValue) { String xDiscriminatorValue = (String) vendorExtensions.get("x-discriminator-value"); - mm = new MappedModel(xDiscriminatorValue, toModelName(currentSchemaName)); + MappedModel mm = new MappedModel(xDiscriminatorValue, toModelName(currentSchemaName)); descendentSchemas.add(mm); } } @@ -3422,7 +3438,8 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch // add only if the mapping names are not the same boolean matched = false; for (MappedModel uniqueDescendant : uniqueDescendants) { - if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())) { + if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName()) + || (uniqueModelMappings && uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) { matched = true; break; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java index cfdc60aa0aa5..202ff8eb3d92 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java @@ -951,6 +951,26 @@ public void testDiscriminatorWithCustomMapping() { Assert.assertEquals(personModel.getHasDiscriminatorWithNonEmptyMapping(), true); } + @Test + public void testUniqueMappingDiscriminatorWithCustomMapping() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf.yaml"); + DefaultCodegen codegen = new DefaultCodegen(); + codegen.setLegacyDiscriminatorBehavior(false); + codegen.setOpenAPI(openAPI); + codegen.setUniqueModelMappings(true); + + String path = "/person/display/{personId}"; + Operation operation = openAPI.getPaths().get(path).getGet(); + CodegenOperation codegenOperation = codegen.fromOperation(path, "GET", operation, null); + verifyPersonDiscriminatorWithUniqueMappings(codegenOperation.discriminator); + + Schema person = openAPI.getComponents().getSchemas().get("Person"); + codegen.setOpenAPI(openAPI); + CodegenModel personModel = codegen.fromModel("Person", person); + verifyPersonDiscriminatorWithUniqueMappings(personModel.discriminator); + Assert.assertEquals(personModel.getHasDiscriminatorWithNonEmptyMapping(), true); + } + @Test public void testParentName() { final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf.yaml"); @@ -1592,6 +1612,54 @@ public void verifyXDiscriminatorValue() { assertEquals(cm.discriminator, discriminator); } + @Test + public void verifyXDiscriminatorValueWithUniqueMappingNames() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/x-discriminator-value.yaml"); + final DefaultCodegen config = new DefaultCodegen(); + config.setOpenAPI(openAPI); + config.setUniqueModelMappings(true); + + String modelName; + CodegenDiscriminator discriminator; + CodegenModel cm; + + Boolean dryRun = Boolean.TRUE; + final DefaultGenerator generator = new DefaultGenerator(dryRun); + generator.openAPI = openAPI; + generator.config = config; + generator.configureGeneratorProperties(); + + // for us to check a model's children we need to run generator.generateModels + // because children are assigned in config.updateAllModels which is invoked in generator.generateModels + List files = new ArrayList<>(); + List filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI); + List allModels = new ArrayList<>(); + generator.generateModels(files, allModels, filteredSchemas); + + // check that the model's children contain the x-discriminator-values + modelName = "BaseObj"; + cm = getModel(allModels, modelName); + Assert.assertNotNull(cm); + Assert.assertNotNull(cm.children); + List expectedDiscriminatorValues = new ArrayList<>(Arrays.asList("daily", "sub-obj")); + ArrayList xDiscriminatorValues = new ArrayList<>(); + for (CodegenModel child : cm.children) { + xDiscriminatorValues.add((String) child.vendorExtensions.get("x-discriminator-value")); + } + assertEquals(xDiscriminatorValues, expectedDiscriminatorValues); + + // check that the discriminator's MappedModels also contains the x-discriminator-values + discriminator = new CodegenDiscriminator(); + String prop = "object_type"; + discriminator.setPropertyName(config.toVarName(prop)); + discriminator.setPropertyBaseName(prop); + discriminator.setMapping(null); + discriminator.setMappedModels(new HashSet() {{ + add(new CodegenDiscriminator.MappedModel("daily", "DailySubObj")); + add(new CodegenDiscriminator.MappedModel("sub-obj", "SubObj")); + }}); + assertEquals(cm.discriminator, discriminator); + } @Test public void testAllOfSingleRefNoOwnProps() { @@ -1996,6 +2064,18 @@ private void verifyPersonDiscriminator(CodegenDiscriminator discriminator) { Assert.assertEquals(discriminator, test); } + private void verifyPersonDiscriminatorWithUniqueMappings(CodegenDiscriminator discriminator) { + CodegenDiscriminator test = new CodegenDiscriminator(); + test.setPropertyName("DollarUnderscoretype"); + test.setPropertyBaseName("$_type"); + test.setMapping(new HashMap<>()); + test.getMapping().put("a", "#/components/schemas/Adult"); + test.getMapping().put("c", "Child"); + test.getMappedModels().add(new CodegenDiscriminator.MappedModel("a", "Adult")); + test.getMappedModels().add(new CodegenDiscriminator.MappedModel("c", "Child")); + Assert.assertEquals(discriminator, test); + } + private CodegenProperty codegenPropertyWithArrayOfIntegerValues() { CodegenProperty array = new CodegenProperty(); final CodegenProperty items = new CodegenProperty(); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index ac1f12a3eb0f..7dd4a29d2a5d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -1079,6 +1079,45 @@ public void testOneOfAndAllOf() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); } + @Test + public void testMappingSubtypesIssue13150() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/bugs/issue_13150.yaml", null, new ParseOptions()).getOpenAPI(); + + SpringCodegen codegen = new SpringCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + codegen.setUseOneOfInterfaces(true); + codegen.setUniqueModelMappings(true); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + codegen.setHateoas(true); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); + + codegen.setUseOneOfInterfaces(true); + codegen.setLegacyDiscriminatorBehavior(false); + + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + + generator.opts(input).generate(); + + String jsonSubType = "@JsonSubTypes({\n" + + " @JsonSubTypes.Type(value = Foo.class, name = \"foo\")\n" + + "})"; + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Parent.java"), jsonSubType); + } + @Test public void testTypeMappings() { final SpringCodegen codegen = new SpringCodegen(); diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_13150.yaml b/modules/openapi-generator/src/test/resources/bugs/issue_13150.yaml new file mode 100644 index 000000000000..07ee407b37e7 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_13150.yaml @@ -0,0 +1,60 @@ +openapi: '3.0.0' +info: + version: '1.0.0' + title: 'FooService' +paths: + /parent: + put: + summary: put parent + operationId: putParent + parameters: + - name: name + in: path + required: true + description: Name of the account being updated. + schema: + type: string + requestBody: + description: The updated account definition to save. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Parent' + responses: + '200': + $ref: '#/components/responses/Parent' +components: + schemas: + Parent: + type: object + description: Defines an account by name. + properties: + name: + type: string + description: The account name. + type: + type: string + description: The account type discriminator. + required: + - name + - type + discriminator: + propertyName: type + mapping: + foo: '#/components/schemas/Foo' + + Foo: + allOf: + - $ref: "#/components/schemas/Parent" + - type: object + properties: + fooType: + type: string + responses: + Parent: + description: The saved account definition. + content: + application/json: + schema: + $ref: '#/components/schemas/Parent' \ No newline at end of file