diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index 12047a88e12f..00216f903ddd 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -1482,27 +1482,20 @@ public Map postProcessAllModels(Map objs) Map allModelsMap = getAllModels(objs); - // For each oneOf interface with a discriminator, mark the discriminator property - // as inherited in each subtype and set its default value from the discriminator mapping + // For each discriminator parent (oneOf interfaces and allOf parents alike), mark the + // discriminator property as inherited in each child and set its default value. for (CodegenModel cm : allModelsMap.values()) { - if (Boolean.TRUE.equals(cm.vendorExtensions.get(CodegenConstants.X_IS_ONE_OF_INTERFACE)) - && cm.discriminator != null) { - String discrimBaseName = cm.discriminator.getPropertyBaseName(); - String discrimType = cm.discriminator.getPropertyType(); - boolean isEnumDiscriminator = cm.discriminator.getIsEnum(); - - // Build child name -> mapping name lookup from discriminator mappings - Map childToMappingName = new HashMap<>(); - for (CodegenDiscriminator.MappedModel mm : cm.discriminator.getMappedModels()) { - childToMappingName.put(mm.getModelName(), mm.getMappingName()); - } - - for (String childName : cm.oneOf) { - CodegenModel child = allModelsMap.get(childName); - if (child != null) { - String mappingName = childToMappingName.get(childName); - markPropertyAsInherited(child, discrimBaseName, discrimType, mappingName, isEnumDiscriminator); - } + if (cm.discriminator == null + || cm.discriminator.getMappedModels() == null + || cm.discriminator.getMappedModels().isEmpty()) continue; + String discrimBaseName = cm.discriminator.getPropertyBaseName(); + String discrimType = cm.discriminator.getPropertyType(); + boolean isEnumDiscriminator = cm.discriminator.getIsEnum(); + for (CodegenDiscriminator.MappedModel mm : cm.discriminator.getMappedModels()) { + CodegenModel child = allModelsMap.get(mm.getModelName()); + if (child != null && child != cm) { + markPropertyAsInherited(child, discrimBaseName, discrimType, + mm.getMappingName(), isEnumDiscriminator); } } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java index 7671d2683121..d4786a1aa545 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java @@ -1315,7 +1315,7 @@ public void generateSerializableModelWithSchemaImplements() throws Exception { "@get:JsonProperty(\"likesFetch\", required = true) override val likesFetch: kotlin.Boolean,", "@get:JsonProperty(\"name\", required = true) override val name: kotlin.String,", "@get:JsonProperty(\"photoUrls\", required = true) override val photoUrls: kotlin.collections.List,", - "@get:JsonProperty(\"petType\", required = true) override val petType: kotlin.String,", + "@get:JsonProperty(\"petType\", required = true) override val petType: kotlin.String = \"Dog\",", "@get:JsonProperty(\"id\") override val id: kotlin.Long? = null,", "@get:JsonProperty(\"category\") override val category: Category? = null,", "@get:JsonProperty(\"tags\") override val tags: kotlin.collections.List? = null,", @@ -7800,4 +7800,92 @@ public void extraImportsDedupAgainstGeneratedImports() throws IOException { Assert.assertEquals(countOccurrences(widgets, "import org.openapitools.model.Widget"), 1L, "Extra import duplicating a generated type import must be emitted only once"); } + + // ==================== allOf discriminator default value tests ==================== + + @Test(description = "allOf discriminator children get a default value from the schema name when no explicit mapping") + public void testAllOfDiscriminatorChildrenGetDefaultValue() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + new DefaultGenerator().opts(new ClientOptInput() + .openAPI(new OpenAPIParser().readLocation("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml", null, new ParseOptions()).getOpenAPI()) + .config(new KotlinSpringServerCodegen() {{ + setOutputDir(output.getAbsolutePath()); + }})) + .generate(); + + String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model"; + + // Cat and Dog are allOf children of Pet; no explicit mapping → schema name is the discriminating value + assertFileContains(Paths.get(outputPath + "/Cat.kt"), + "data class Cat", + "override val petType: kotlin.String = \"Cat\"" + ); + assertFileContains(Paths.get(outputPath + "/Dog.kt"), + "data class Dog", + "override val petType: kotlin.String = \"Dog\"" + ); + // Pet parent is a plain interface when useSealedDiscriminatorInterfaces is at its default + assertFileContains(Paths.get(outputPath + "/Pet.kt"), + "interface Pet" + ); + assertFileNotContains(Paths.get(outputPath + "/Cat.kt"), + "petType: kotlin.String?", "petType: kotlin.Any" + ); + assertFileNotContains(Paths.get(outputPath + "/Dog.kt"), + "petType: kotlin.String?", "petType: kotlin.Any" + ); + } + + @Test(description = "allOf discriminator children get default value matching the explicit mapping key") + public void testAllOfDiscriminatorWithExplicitMappingDefaultValue() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + new DefaultGenerator().opts(new ClientOptInput() + .openAPI(new OpenAPIParser().readLocation("src/test/resources/3_0/kotlin/petstore-with-x-kotlin-implements.yaml", null, new ParseOptions()).getOpenAPI()) + .config(new KotlinSpringServerCodegen() {{ + setOutputDir(output.getAbsolutePath()); + }})) + .generate(); + + String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model"; + + // Pet has discriminator petType with explicit mapping: Dog → "Dog", Cat → "Cat" + assertFileContains(Paths.get(outputPath + "/Dog.kt"), + "data class Dog", + "override val petType: kotlin.String = \"Dog\"" + ); + assertFileContains(Paths.get(outputPath + "/Cat.kt"), + "data class Cat", + "override val petType: kotlin.String = \"Cat\"" + ); + } + + @Test(description = "allOf discriminator children use the mapping key, not the schema name, as the default value") + public void testAllOfDiscriminatorMappingKeyDiffersFromSchemaName() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + new DefaultGenerator().opts(new ClientOptInput() + .openAPI(new OpenAPIParser().readLocation("src/test/resources/3_0/kotlin/petstore-with-fake-endpoints-for-testing-with-cookie.yaml", null, new ParseOptions()).getOpenAPI()) + .config(new KotlinSpringServerCodegen() {{ + setOutputDir(output.getAbsolutePath()); + }})) + .generate(); + + String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model"; + + // Animal maps DOG -> Dog and CAT -> Cat. The discriminating value is the mapping key, + // which differs from the schema name it points at, so it is the key that must be emitted. + assertFileContains(Paths.get(outputPath + "/Dog.kt"), + "override val className: kotlin.String = \"DOG\"" + ); + assertFileContains(Paths.get(outputPath + "/Cat.kt"), + "override val className: kotlin.String = \"CAT\"" + ); + assertFileNotContains(Paths.get(outputPath + "/Dog.kt"), "className: kotlin.String = \"Dog\""); + assertFileNotContains(Paths.get(outputPath + "/Cat.kt"), "className: kotlin.String = \"Cat\""); + } } diff --git a/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Cat.kt b/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Cat.kt index 496f2959ceaa..4322ee050263 100644 --- a/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Cat.kt +++ b/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Cat.kt @@ -49,7 +49,7 @@ data class Cat( @ApiModelProperty(required = true, value = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("petType", required = true) - @get:JsonProperty("petType", required = true) override val petType: kotlin.String, + @get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Cat", @ApiModelProperty(value = "") @field:JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Dog.kt b/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Dog.kt index c39b81755815..8b1d2ab986cd 100644 --- a/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Dog.kt +++ b/samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Dog.kt @@ -65,7 +65,7 @@ data class Dog( @ApiModelProperty(required = true, value = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("petType", required = true) - @get:JsonProperty("petType", required = true) override val petType: kotlin.String, + @get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Dog", @ApiModelProperty(value = "") @field:JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Cat.kt b/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Cat.kt index d8b85c78838d..4928bcef5d7a 100644 --- a/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Cat.kt +++ b/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Cat.kt @@ -28,7 +28,7 @@ data class Cat( @Schema(required = true, description = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("className", required = true) - @get:JsonProperty("className", required = true) override val className: kotlin.String, + @get:JsonProperty("className", required = true) override val className: kotlin.String = "CAT", @Schema(description = "") @field:JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Dog.kt b/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Dog.kt index bd54ef3d3a7b..14e81ca10b76 100644 --- a/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Dog.kt +++ b/samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Dog.kt @@ -28,7 +28,7 @@ data class Dog( @Schema(required = true, description = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("className", required = true) - @get:JsonProperty("className", required = true) override val className: kotlin.String, + @get:JsonProperty("className", required = true) override val className: kotlin.String = "DOG", @Schema(description = "") @field:JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Cat.kt b/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Cat.kt index 496f2959ceaa..4322ee050263 100644 --- a/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Cat.kt +++ b/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Cat.kt @@ -49,7 +49,7 @@ data class Cat( @ApiModelProperty(required = true, value = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("petType", required = true) - @get:JsonProperty("petType", required = true) override val petType: kotlin.String, + @get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Cat", @ApiModelProperty(value = "") @field:JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Dog.kt b/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Dog.kt index b8729705bec2..bf6d116d867d 100644 --- a/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Dog.kt +++ b/samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Dog.kt @@ -65,7 +65,7 @@ data class Dog( @ApiModelProperty(required = true, value = "") @field:JsonInclude(JsonInclude.Include.ALWAYS) @param:JsonProperty("petType", required = true) - @get:JsonProperty("petType", required = true) override val petType: kotlin.String, + @get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Dog", @ApiModelProperty(value = "") @field:JsonInclude(JsonInclude.Include.NON_NULL)