Skip to content

Commit 43abab4

Browse files
rvlasveldclaude
andcommitted
[kotlin-spring] allOf discriminator children get default discriminator value
A child of a discriminator parent already knows its own discriminating value, but the generated property was an ordinary required constructor parameter, so every caller had to pass it and could pass a wrong one. The discriminator property now gets a default value taken from the discriminator mapping, matching what the oneOf path already did. The oneOf-specific loop is generalised to every discriminator parent, so allOf parents are covered by the same code path. The value is the mapping key, which is not always the schema name: a spec mapping DOG -> Dog makes "DOG" the discriminating value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBn7VWUxZL6mTUbg21xNjm
1 parent 33a7df6 commit 43abab4

8 files changed

Lines changed: 108 additions & 27 deletions

File tree

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

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,27 +1482,20 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
14821482

14831483
Map<String, CodegenModel> allModelsMap = getAllModels(objs);
14841484

1485-
// For each oneOf interface with a discriminator, mark the discriminator property
1486-
// as inherited in each subtype and set its default value from the discriminator mapping
1485+
// For each discriminator parent (oneOf interfaces and allOf parents alike), mark the
1486+
// discriminator property as inherited in each child and set its default value.
14871487
for (CodegenModel cm : allModelsMap.values()) {
1488-
if (Boolean.TRUE.equals(cm.vendorExtensions.get(CodegenConstants.X_IS_ONE_OF_INTERFACE))
1489-
&& cm.discriminator != null) {
1490-
String discrimBaseName = cm.discriminator.getPropertyBaseName();
1491-
String discrimType = cm.discriminator.getPropertyType();
1492-
boolean isEnumDiscriminator = cm.discriminator.getIsEnum();
1493-
1494-
// Build child name -> mapping name lookup from discriminator mappings
1495-
Map<String, String> childToMappingName = new HashMap<>();
1496-
for (CodegenDiscriminator.MappedModel mm : cm.discriminator.getMappedModels()) {
1497-
childToMappingName.put(mm.getModelName(), mm.getMappingName());
1498-
}
1499-
1500-
for (String childName : cm.oneOf) {
1501-
CodegenModel child = allModelsMap.get(childName);
1502-
if (child != null) {
1503-
String mappingName = childToMappingName.get(childName);
1504-
markPropertyAsInherited(child, discrimBaseName, discrimType, mappingName, isEnumDiscriminator);
1505-
}
1488+
if (cm.discriminator == null
1489+
|| cm.discriminator.getMappedModels() == null
1490+
|| cm.discriminator.getMappedModels().isEmpty()) continue;
1491+
String discrimBaseName = cm.discriminator.getPropertyBaseName();
1492+
String discrimType = cm.discriminator.getPropertyType();
1493+
boolean isEnumDiscriminator = cm.discriminator.getIsEnum();
1494+
for (CodegenDiscriminator.MappedModel mm : cm.discriminator.getMappedModels()) {
1495+
CodegenModel child = allModelsMap.get(mm.getModelName());
1496+
if (child != null && child != cm) {
1497+
markPropertyAsInherited(child, discrimBaseName, discrimType,
1498+
mm.getMappingName(), isEnumDiscriminator);
15061499
}
15071500
}
15081501
}

‎modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java‎

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1315,7 +1315,7 @@ public void generateSerializableModelWithSchemaImplements() throws Exception {
13151315
"@get:JsonProperty(\"likesFetch\", required = true) override val likesFetch: kotlin.Boolean,",
13161316
"@get:JsonProperty(\"name\", required = true) override val name: kotlin.String,",
13171317
"@get:JsonProperty(\"photoUrls\", required = true) override val photoUrls: kotlin.collections.List<kotlin.String>,",
1318-
"@get:JsonProperty(\"petType\", required = true) override val petType: kotlin.String,",
1318+
"@get:JsonProperty(\"petType\", required = true) override val petType: kotlin.String = \"Dog\",",
13191319
"@get:JsonProperty(\"id\") override val id: kotlin.Long? = null,",
13201320
"@get:JsonProperty(\"category\") override val category: Category? = null,",
13211321
"@get:JsonProperty(\"tags\") override val tags: kotlin.collections.List<Tag>? = null,",
@@ -7800,4 +7800,92 @@ public void extraImportsDedupAgainstGeneratedImports() throws IOException {
78007800
Assert.assertEquals(countOccurrences(widgets, "import org.openapitools.model.Widget"), 1L,
78017801
"Extra import duplicating a generated type import must be emitted only once");
78027802
}
7803+
7804+
// ==================== allOf discriminator default value tests ====================
7805+
7806+
@Test(description = "allOf discriminator children get a default value from the schema name when no explicit mapping")
7807+
public void testAllOfDiscriminatorChildrenGetDefaultValue() throws IOException {
7808+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
7809+
output.deleteOnExit();
7810+
7811+
new DefaultGenerator().opts(new ClientOptInput()
7812+
.openAPI(new OpenAPIParser().readLocation("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml", null, new ParseOptions()).getOpenAPI())
7813+
.config(new KotlinSpringServerCodegen() {{
7814+
setOutputDir(output.getAbsolutePath());
7815+
}}))
7816+
.generate();
7817+
7818+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model";
7819+
7820+
// Cat and Dog are allOf children of Pet; no explicit mapping → schema name is the discriminating value
7821+
assertFileContains(Paths.get(outputPath + "/Cat.kt"),
7822+
"data class Cat",
7823+
"override val petType: kotlin.String = \"Cat\""
7824+
);
7825+
assertFileContains(Paths.get(outputPath + "/Dog.kt"),
7826+
"data class Dog",
7827+
"override val petType: kotlin.String = \"Dog\""
7828+
);
7829+
// Pet parent is a plain interface when useSealedDiscriminatorInterfaces is at its default
7830+
assertFileContains(Paths.get(outputPath + "/Pet.kt"),
7831+
"interface Pet"
7832+
);
7833+
assertFileNotContains(Paths.get(outputPath + "/Cat.kt"),
7834+
"petType: kotlin.String?", "petType: kotlin.Any"
7835+
);
7836+
assertFileNotContains(Paths.get(outputPath + "/Dog.kt"),
7837+
"petType: kotlin.String?", "petType: kotlin.Any"
7838+
);
7839+
}
7840+
7841+
@Test(description = "allOf discriminator children get default value matching the explicit mapping key")
7842+
public void testAllOfDiscriminatorWithExplicitMappingDefaultValue() throws IOException {
7843+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
7844+
output.deleteOnExit();
7845+
7846+
new DefaultGenerator().opts(new ClientOptInput()
7847+
.openAPI(new OpenAPIParser().readLocation("src/test/resources/3_0/kotlin/petstore-with-x-kotlin-implements.yaml", null, new ParseOptions()).getOpenAPI())
7848+
.config(new KotlinSpringServerCodegen() {{
7849+
setOutputDir(output.getAbsolutePath());
7850+
}}))
7851+
.generate();
7852+
7853+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model";
7854+
7855+
// Pet has discriminator petType with explicit mapping: Dog → "Dog", Cat → "Cat"
7856+
assertFileContains(Paths.get(outputPath + "/Dog.kt"),
7857+
"data class Dog",
7858+
"override val petType: kotlin.String = \"Dog\""
7859+
);
7860+
assertFileContains(Paths.get(outputPath + "/Cat.kt"),
7861+
"data class Cat",
7862+
"override val petType: kotlin.String = \"Cat\""
7863+
);
7864+
}
7865+
7866+
@Test(description = "allOf discriminator children use the mapping key, not the schema name, as the default value")
7867+
public void testAllOfDiscriminatorMappingKeyDiffersFromSchemaName() throws IOException {
7868+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
7869+
output.deleteOnExit();
7870+
7871+
new DefaultGenerator().opts(new ClientOptInput()
7872+
.openAPI(new OpenAPIParser().readLocation("src/test/resources/3_0/kotlin/petstore-with-fake-endpoints-for-testing-with-cookie.yaml", null, new ParseOptions()).getOpenAPI())
7873+
.config(new KotlinSpringServerCodegen() {{
7874+
setOutputDir(output.getAbsolutePath());
7875+
}}))
7876+
.generate();
7877+
7878+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model";
7879+
7880+
// Animal maps DOG -> Dog and CAT -> Cat. The discriminating value is the mapping key,
7881+
// which differs from the schema name it points at, so it is the key that must be emitted.
7882+
assertFileContains(Paths.get(outputPath + "/Dog.kt"),
7883+
"override val className: kotlin.String = \"DOG\""
7884+
);
7885+
assertFileContains(Paths.get(outputPath + "/Cat.kt"),
7886+
"override val className: kotlin.String = \"CAT\""
7887+
);
7888+
assertFileNotContains(Paths.get(outputPath + "/Dog.kt"), "className: kotlin.String = \"Dog\"");
7889+
assertFileNotContains(Paths.get(outputPath + "/Cat.kt"), "className: kotlin.String = \"Cat\"");
7890+
}
78037891
}

‎samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Cat.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ data class Cat(
4949
@ApiModelProperty(required = true, value = "")
5050
@field:JsonInclude(JsonInclude.Include.ALWAYS)
5151
@param:JsonProperty("petType", required = true)
52-
@get:JsonProperty("petType", required = true) override val petType: kotlin.String,
52+
@get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Cat",
5353

5454
@ApiModelProperty(value = "")
5555
@field:JsonInclude(JsonInclude.Include.NON_NULL)

‎samples/server/petstore/kotlin-springboot-include-http-request-context-delegate/src/main/kotlin/org/openapitools/model/Dog.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ data class Dog(
6565
@ApiModelProperty(required = true, value = "")
6666
@field:JsonInclude(JsonInclude.Include.ALWAYS)
6767
@param:JsonProperty("petType", required = true)
68-
@get:JsonProperty("petType", required = true) override val petType: kotlin.String,
68+
@get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Dog",
6969

7070
@ApiModelProperty(value = "")
7171
@field:JsonInclude(JsonInclude.Include.NON_NULL)

‎samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Cat.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ data class Cat(
2828
@Schema(required = true, description = "")
2929
@field:JsonInclude(JsonInclude.Include.ALWAYS)
3030
@param:JsonProperty("className", required = true)
31-
@get:JsonProperty("className", required = true) override val className: kotlin.String,
31+
@get:JsonProperty("className", required = true) override val className: kotlin.String = "CAT",
3232

3333
@Schema(description = "")
3434
@field:JsonInclude(JsonInclude.Include.NON_NULL)

‎samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Dog.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ data class Dog(
2828
@Schema(required = true, description = "")
2929
@field:JsonInclude(JsonInclude.Include.ALWAYS)
3030
@param:JsonProperty("className", required = true)
31-
@get:JsonProperty("className", required = true) override val className: kotlin.String,
31+
@get:JsonProperty("className", required = true) override val className: kotlin.String = "DOG",
3232

3333
@Schema(description = "")
3434
@field:JsonInclude(JsonInclude.Include.NON_NULL)

‎samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Cat.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ data class Cat(
4949
@ApiModelProperty(required = true, value = "")
5050
@field:JsonInclude(JsonInclude.Include.ALWAYS)
5151
@param:JsonProperty("petType", required = true)
52-
@get:JsonProperty("petType", required = true) override val petType: kotlin.String,
52+
@get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Cat",
5353

5454
@ApiModelProperty(value = "")
5555
@field:JsonInclude(JsonInclude.Include.NON_NULL)

‎samples/server/petstore/kotlin-springboot-x-kotlin-implements/src/main/kotlin/org/openapitools/model/Dog.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ data class Dog(
6565
@ApiModelProperty(required = true, value = "")
6666
@field:JsonInclude(JsonInclude.Include.ALWAYS)
6767
@param:JsonProperty("petType", required = true)
68-
@get:JsonProperty("petType", required = true) override val petType: kotlin.String,
68+
@get:JsonProperty("petType", required = true) override val petType: kotlin.String = "Dog",
6969

7070
@ApiModelProperty(value = "")
7171
@field:JsonInclude(JsonInclude.Include.NON_NULL)

0 commit comments

Comments
 (0)