Skip to content

Commit fa59009

Browse files
authored
[KOTLIN-SPRING] Feature - add support for 'useDeductionForOneOfInterfaces' (#23677)
* feat(kotlin-spring): add support for deduction in oneOf interfaces with Jackson annotations * update samples * Revert "update samples" This reverts commit 71cdb09. * Reapply "update samples" This reverts commit dc0bac6. * Revert "Reapply "update samples"" This reverts commit e45f0e6. * Reapply "Reapply "update samples"" This reverts commit 050092c. * white space change to trigger tests * Revert "white space change to trigger tests" This reverts commit ddde44c.
1 parent f4bf9ae commit fa59009

5 files changed

Lines changed: 49 additions & 1 deletion

File tree

docs/generators/kotlin-spring.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
6161
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' pagination-metadata property) and replace their generated references with PagedModel<T>. By default this uses a generated type in the config package (default 'org.openapitools.configuration'), but `importMappings.PagedModel` can override it to a custom/FQCN-mapped type. The detected page schemas and the pagination metadata schema are suppressed from code generation.| |false|
6262
|title|server title name or client service name| |OpenAPI Kotlin Spring|
6363
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
64+
|useDeductionForOneOfInterfaces|Annotate discriminator-free oneOf interfaces with Jackson's @JsonTypeInfo(use = Id.DEDUCTION) and @JsonSubTypes so the concrete subtype is resolved from the JSON field set rather than a type-tag property. Has no effect when a discriminator is present (name-based resolution is used instead). Requires subtypes to have structurally distinct sets of properties.| |false|
6465
|useFeignClientUrl|Whether to generate Feign client with url parameter.| |true|
6566
|useFlowForArrayReturnType|Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.| |true|
6667
|useJackson3|Use Jackson 3 dependencies (tools.jackson package). Only available with `useSpringBoot4`. Defaults to true when `useSpringBoot4` is enabled. Incompatible with `openApiNullable`.| |false|

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ public String getDescription() {
181181
@Setter private boolean substituteGenericPagedModel = false;
182182
@Setter private boolean useSealedResponseInterfaces = false;
183183
@Setter private boolean companionObject = false;
184+
@Getter @Setter
185+
protected boolean useDeductionForOneOfInterfaces = false;
184186

185187
@Getter @Setter
186188
protected boolean useSpringBoot3 = false;
@@ -311,6 +313,7 @@ public KotlinSpringServerCodegen() {
311313
+ "schema are suppressed from code generation.",
312314
substituteGenericPagedModel);
313315
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
316+
cliOptions.add(CliOption.newBoolean(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES_DESC, useDeductionForOneOfInterfaces));
314317
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
315318
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
316319
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -572,6 +575,8 @@ public void processOpts() {
572575
additionalProperties.put(COMPANION_OBJECT, companionObject);
573576
}
574577

578+
convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces);
579+
575580
additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());
576581

577582
// Set basePackage from invokerPackage

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ public void processOpts() {
556556
}
557557
convertPropertyToBooleanAndWriteBack(OPTIONAL_ACCEPT_NULLABLE, this::setOptionalAcceptNullable);
558558
convertPropertyToBooleanAndWriteBack(USE_SPRING_BUILT_IN_VALIDATION, this::setUseSpringBuiltInValidation);
559+
convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces);
559560

560561
additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());
561562

modules/openapi-generator/src/main/resources/kotlin-spring/oneof_interface.mustache

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
{{#discriminator}}
55
{{>typeInfoAnnotation}}
66
{{/discriminator}}
7-
{{#additionalModelTypeAnnotations}}
7+
{{^discriminator}}{{#useDeductionForOneOfInterfaces}}{{#jackson}}
8+
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
9+
@JsonSubTypes(
10+
{{#interfaceModels}}
11+
JsonSubTypes.Type(value = {{classname}}::class){{^-last}},{{/-last}}
12+
{{/interfaceModels}}
13+
)
14+
{{/jackson}}{{/useDeductionForOneOfInterfaces}}{{/discriminator}}{{#additionalModelTypeAnnotations}}
815
{{{.}}}
916
{{/additionalModelTypeAnnotations}}
1017
{{#vendorExtensions.x-class-extra-annotation}}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6050,6 +6050,40 @@ public void testOneOfRefEnumDiscriminatorResolvesType() throws IOException {
60506050
);
60516051
}
60526052

6053+
@Test(description = "oneOf without discriminator with useDeductionForOneOfInterfaces generates @JsonTypeInfo(DEDUCTION) annotation")
6054+
public void testOneOfDeductionWithoutDiscriminatorGeneratesDeductionAnnotation() throws IOException {
6055+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
6056+
output.deleteOnExit();
6057+
6058+
new DefaultGenerator().opts(new ClientOptInput()
6059+
.openAPI(new OpenAPIParser().readLocation("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", null, new ParseOptions()).getOpenAPI())
6060+
.config(new KotlinSpringServerCodegen() {{
6061+
setOutputDir(output.getAbsolutePath());
6062+
additionalProperties().put(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "true");
6063+
}}))
6064+
.generate();
6065+
6066+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/model";
6067+
6068+
// Animal has oneOf [Dog, Cat] with NO discriminator → deduction should be applied
6069+
assertFileContains(Paths.get(outputPath + "/Animal.kt"),
6070+
"sealed interface Animal",
6071+
"@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)",
6072+
"@JsonSubTypes(",
6073+
"JsonSubTypes.Type(value = Dog::class)",
6074+
"JsonSubTypes.Type(value = Cat::class)"
6075+
);
6076+
6077+
// Fruit has oneOf [Apple, Banana] WITH a discriminator → must NOT use deduction
6078+
assertFileNotContains(Paths.get(outputPath + "/Fruit.kt"),
6079+
"JsonTypeInfo.Id.DEDUCTION"
6080+
);
6081+
assertFileContains(Paths.get(outputPath + "/Fruit.kt"),
6082+
"sealed interface Fruit",
6083+
"@JsonTypeInfo(use = JsonTypeInfo.Id.NAME"
6084+
);
6085+
}
6086+
60536087
@Test
60546088
public void testSealedResponseInterfacesWithDeclarativeHttpInterface() throws IOException {
60556089
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();

0 commit comments

Comments
 (0)