Skip to content

Commit 95953de

Browse files
committed
fix(kotlin-spring): add parentCtorCallNeeded model template property
The new parentCtorCallNeeded property can be used by the template to determine if there should be a constructor invocation on the parent type. Right now its value is computed in a way that could probably use some improvement because it is language specifically checking if the base type is a kotlin map or not. This might seem overly brittle, but was the only way I could differentiate between the two different edge cases that are present in the repro spec. This was the previous commit that changed the default behavior from one edge case to the other (both are wrong because we can only know for sure at generation time whether a constructor call will be necessary or not). 0bb08a7 Below is a yaml OpenAPI spec document that can be used to test/verify the currently known edge cases (which were also mentioned in the GH issue tracker's comments) ```yaml openapi: 3.0.3 info: version: "1.0.0" title: "" paths: /documents/v1: get: responses: 200: description: lorem content: application/json: schema: type: array items: $ref: "#/components/schemas/ParentSchema" components: schemas: JarFiles: type: array items: $ref: "#/components/schemas/JarFile" JarFile: type: object required: - filename - contentBase64 - hasDbMigrations additionalProperties: true properties: filename: type: string nullable: false minLength: 1 maxLength: 255 hasDbMigrations: description: Indicates whether the cordapp jar in question contains any embedded migrations that Cactus can/should execute between copying the jar into the cordapp directory and starting the node back up. type: boolean nullable: false contentBase64: type: string format: base64 nullable: false minLength: 1 maxLength: 1073741824 ParentSchema: type: object properties: id: type: string type: $ref: "#/components/schemas/DiscriminatingType" required: - id - type discriminator: propertyName: type mapping: subtypeA: "#/components/schemas/SubtypeA" subtypeB: "#/components/schemas/SubtypeB" subtypeC: "#/components/schemas/SubtypeC" SubtypeA: type: object allOf: - $ref: '#/components/schemas/ParentSchema' - type: object properties: subtypeAproperty: type: integer SubtypeB: type: object allOf: - $ref: '#/components/schemas/ParentSchema' - type: object properties: subtypeBproperty: type: string SubtypeC: type: object additionalProperties: true allOf: - $ref: '#/components/schemas/ParentSchema' - type: object properties: subtypeAproperty: type: integer DiscriminatingType: type: string enum: - subtypeA - subtypeB - subtypeC ``` Fixes #8366 Signed-off-by: Peter Somogyvari <peter.metz@unarin.com> Signed-off-by: Peter Metz <peter.metz@unarin.com>
1 parent 71a6901 commit 95953de

31 files changed

Lines changed: 1298 additions & 1 deletion

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
generatorName: kotlin-spring
2+
outputDir: samples/server/petstore/kotlin-spring-issue8366-inheritance-parent-ctor-call
3+
library: spring-boot
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/issue8366-inheritance-parent-ctor-call.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
6+
additionalProperties:
7+
documentationProvider: springdoc
8+
annotationLibrary: swagger2
9+
useSwaggerUI: "true"
10+
serviceImplementation: "true"
11+
reactive: "true"
12+
beanValidations: "false"

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
8181
// ref: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-map/
8282
protected Set<String> propertyAdditionalKeywords = new HashSet<>(Arrays.asList("entries", "keys", "size", "values"));
8383

84+
private final List<Pattern> PARENT_TYPES_NEEDING_CTOR_CALL = List.of(Pattern.compile("^kotlin\\.collections\\.HashMap.*"));
85+
8486
private final Map<String, String> schemaKeyToModelNameCache = new HashMap<>();
8587
@Getter @Setter
8688
protected List<String> additionalModelTypeAnnotations = new LinkedList<>();
@@ -420,10 +422,46 @@ public ModelsMap postProcessModels(ModelsMap objs) {
420422
break;
421423
}
422424
}
425+
426+
final boolean isParentCtorCallNeeded = isParentCtorCallNeeded(cm);
427+
cm.vendorExtensions.put("x-is-parent-ctor-call-needed", isParentCtorCallNeeded);
423428
}
424429
return postProcessModelsEnum(objs);
425430
}
426431

432+
/**
433+
* Determines if a constructor call is needed for the parent type of a model
434+
* and if yes, the invocation syntax `()` is added in the generated code.
435+
*
436+
* Important note: This is kotlin specific at the moment, but could be
437+
* extended with to support detection for other languages as well.
438+
*
439+
* The generator used to default to issuing a supertype
440+
* constructor call and then it was changed to default to not doing it.
441+
* Both are wrong since there are different cases and the decision has to be
442+
* made at runtime based on the parent type itself.
443+
*
444+
* For example if it is a HashMap (because you set
445+
* additionalProperties: true for example) then it MUST HAVE a parent
446+
* constructor call. If the parent type is an interface because you are
447+
* using allOf with a discriminator property then the generated code for the
448+
* model will have it's parent type as the interface which MUST NOT HAVE a
449+
* parent constructor call.
450+
*
451+
* @see https://github.com/OpenAPITools/openapi-generator/issues/8366
452+
*
453+
* @return {boolean} `true` if the () call syntax is needed, false otherwise.
454+
*/
455+
protected boolean isParentCtorCallNeeded(CodegenModel cm) {
456+
final String parent = cm.getParent();
457+
458+
if (parent != null) {
459+
return PARENT_TYPES_NEEDING_CTOR_CALL.stream()
460+
.anyMatch(pattern -> pattern.matcher(parent).matches());
461+
}
462+
return false;
463+
}
464+
427465
@Override
428466
public void processOpts() {
429467
super.processOpts();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>dataClassOptVar}}{{^-last}},
1919
{{/-last}}{{/optionalVars}}
2020
){{/discriminator}}{{! no newline
21-
}}{{#parent}} : {{{.}}}{{! no newline
21+
}}{{#parent}} : {{{.}}}{{#vendorExtensions.x-is-parent-ctor-call-needed}}(){{/vendorExtensions.x-is-parent-ctor-call-needed}}{{! no newline
2222
}}{{#serializableModel}}{{! no newline
2323
}}{{^vendorExtensions.x-kotlin-implements}}, Serializable{{/vendorExtensions.x-kotlin-implements}}{{! no newline
2424
}}{{#vendorExtensions.x-kotlin-implements}}, Serializable, {{! no newline
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
openapi: 3.0.3
2+
info:
3+
version: "1.0.0"
4+
title: ""
5+
paths:
6+
/documents/v1:
7+
get:
8+
responses:
9+
200:
10+
description: lorem
11+
content:
12+
application/json:
13+
schema:
14+
type: array
15+
items:
16+
$ref: "#/components/schemas/ParentSchema"
17+
components:
18+
schemas:
19+
JarFiles:
20+
type: array
21+
items:
22+
$ref: "#/components/schemas/JarFile"
23+
JarFile:
24+
type: object
25+
required:
26+
- filename
27+
- contentBase64
28+
- hasDbMigrations
29+
additionalProperties: true
30+
properties:
31+
filename:
32+
type: string
33+
nullable: false
34+
minLength: 1
35+
maxLength: 255
36+
hasDbMigrations:
37+
description: Indicates whether the cordapp jar in question contains any
38+
embedded migrations that Cactus can/should execute between copying the
39+
jar into the cordapp directory and starting the node back up.
40+
type: boolean
41+
nullable: false
42+
contentBase64:
43+
type: string
44+
format: base64
45+
nullable: false
46+
minLength: 1
47+
maxLength: 1073741824
48+
49+
ParentSchema:
50+
type: object
51+
properties:
52+
id:
53+
type: string
54+
type:
55+
$ref: "#/components/schemas/DiscriminatingType"
56+
discriminator:
57+
propertyName: type
58+
mapping:
59+
subtypeA: "#/components/schemas/SubtypeA"
60+
subtypeB: "#/components/schemas/SubtypeB"
61+
subtypeC: "#/components/schemas/SubtypeC"
62+
SubtypeA:
63+
type: object
64+
allOf:
65+
- $ref: '#/components/schemas/ParentSchema'
66+
- type: object
67+
properties:
68+
subtypeAproperty:
69+
type: integer
70+
SubtypeB:
71+
type: object
72+
allOf:
73+
- $ref: '#/components/schemas/ParentSchema'
74+
- type: object
75+
properties:
76+
subtypeBproperty:
77+
type: string
78+
SubtypeC:
79+
type: object
80+
additionalProperties: true
81+
allOf:
82+
- $ref: '#/components/schemas/ParentSchema'
83+
- type: object
84+
properties:
85+
subtypeAproperty:
86+
type: integer
87+
88+
DiscriminatingType:
89+
type: string
90+
enum:
91+
- subtypeA
92+
- subtypeB
93+
- subtypeC
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
README.md
2+
build.gradle.kts
3+
gradle/wrapper/gradle-wrapper.jar
4+
gradle/wrapper/gradle-wrapper.properties
5+
gradlew
6+
gradlew.bat
7+
pom.xml
8+
settings.gradle
9+
src/main/kotlin/org/openapitools/Application.kt
10+
src/main/kotlin/org/openapitools/HomeController.kt
11+
src/main/kotlin/org/openapitools/api/ApiUtil.kt
12+
src/main/kotlin/org/openapitools/api/DocumentsApiController.kt
13+
src/main/kotlin/org/openapitools/api/DocumentsApiService.kt
14+
src/main/kotlin/org/openapitools/api/DocumentsApiServiceImpl.kt
15+
src/main/kotlin/org/openapitools/configuration/EnumConverterConfiguration.kt
16+
src/main/kotlin/org/openapitools/model/DiscriminatingType.kt
17+
src/main/kotlin/org/openapitools/model/JarFile.kt
18+
src/main/kotlin/org/openapitools/model/ParentSchema.kt
19+
src/main/kotlin/org/openapitools/model/SubtypeA.kt
20+
src/main/kotlin/org/openapitools/model/SubtypeB.kt
21+
src/main/kotlin/org/openapitools/model/SubtypeC.kt
22+
src/main/resources/application.yaml
23+
src/main/resources/openapi.yaml
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.16.0-SNAPSHOT
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#
2+
3+
This Kotlin based [Spring Boot](https://spring.io/projects/spring-boot) application has been generated using the [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator).
4+
5+
## Getting Started
6+
7+
This document assumes you have either maven or gradle available, either via the wrapper or otherwise. This does not come with a gradle / maven wrapper checked in.
8+
9+
By default a [`pom.xml`](pom.xml) file will be generated. If you specified `gradleBuildFile=true` when generating this project, a `build.gradle.kts` will also be generated. Note this uses [Gradle Kotlin DSL](https://github.com/gradle/kotlin-dsl).
10+
11+
To build the project using maven, run:
12+
```bash
13+
mvn package && java -jar target/openapi-spring-1.0.0.jar
14+
```
15+
16+
To build the project using gradle, run:
17+
```bash
18+
gradle build && java -jar build/libs/openapi-spring-1.0.0.jar
19+
```
20+
21+
If all builds successfully, the server should run on [http://localhost:8080/](http://localhost:8080/)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
3+
buildscript {
4+
repositories {
5+
mavenCentral()
6+
}
7+
dependencies {
8+
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.6.7")
9+
}
10+
}
11+
12+
group = "org.openapitools"
13+
version = "1.0.0"
14+
15+
repositories {
16+
mavenCentral()
17+
}
18+
19+
tasks.withType<KotlinCompile> {
20+
kotlinOptions.jvmTarget = "1.8"
21+
}
22+
23+
plugins {
24+
val kotlinVersion = "1.9.25"
25+
id("org.jetbrains.kotlin.jvm") version kotlinVersion
26+
id("org.jetbrains.kotlin.plugin.jpa") version kotlinVersion
27+
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
28+
id("org.springframework.boot") version "2.6.7"
29+
id("io.spring.dependency-management") version "1.0.11.RELEASE"
30+
}
31+
32+
dependencies {
33+
val kotlinxCoroutinesVersion = "1.6.1"
34+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
35+
implementation("org.jetbrains.kotlin:kotlin-reflect")
36+
implementation("org.springframework.boot:spring-boot-starter-webflux")
37+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
38+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinxCoroutinesVersion")
39+
implementation("org.springdoc:springdoc-openapi-webflux-ui:1.6.8")
40+
41+
implementation("com.google.code.findbugs:jsr305:3.0.2")
42+
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
43+
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
44+
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
45+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
46+
implementation("javax.validation:validation-api")
47+
implementation("javax.annotation:javax.annotation-api:1.3.2")
48+
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
49+
testImplementation("org.springframework.boot:spring-boot-starter-test") {
50+
exclude(module = "junit")
51+
}
52+
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion")
53+
}

0 commit comments

Comments
 (0)