Skip to content

Commit 039606d

Browse files
committed
add schemaImplements and schemaImplementsFields support to kotlin-spring
1 parent d45c293 commit 039606d

10 files changed

Lines changed: 151 additions & 22 deletions

File tree

bin/configs/kotlin-spring-boot-x-kotlin-implements.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@ additionalProperties:
1313
serializableModel: true
1414
beanValidations: true
1515
includeHttpRequestContext: true
16+
schemaImplements:
17+
Pet: com.some.pack.WithId
18+
Category: [ com.some.pack.CategoryInterface ]
19+
Dog: [ com.some.pack.Canine ]
20+
schemaImplementsFields:
21+
Pet: id
22+
Category: [ name, id ]
23+
Dog: [ bark, breed ]

bin/configs/spring-boot-skip-x-implements.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ additionalProperties:
1212
xImplementsOverrides:
1313
com.custompackage.InterfaceToSubstitute: com.custompackage.SubstitutedInterface
1414
com.custompackage.InterfaceToSkip: skip
15+
schemaImplements:
16+
Foo: [ com.custompackage.FooInterface, com.custompackage.FooIntergace ]
17+
Animal: [ com.custompackage.AnimalInterfaceImplleeements ]

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

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
5656

5757
public static final String JAVAX_PACKAGE = "javaxPackage";
5858
public static final String USE_JAKARTA_EE = "useJakartaEe";
59+
public static final String SCHEMA_IMPLEMENTS = "schemaImplements";
60+
public static final String SCHEMA_IMPLEMENTS_FIELDS = "schemaImplementsFields";
5961

6062
private final Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class);
6163

@@ -88,6 +90,12 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
8890
private final Map<String, String> schemaKeyToModelNameCache = new HashMap<>();
8991
@Getter @Setter
9092
protected List<String> additionalModelTypeAnnotations = new LinkedList<>();
93+
@Getter
94+
@Setter
95+
protected Map<String, List<String>> schemaImplements = new HashMap<>();
96+
@Getter
97+
@Setter
98+
protected Map<String, List<String>> schemaImplementsFields = new HashMap<>();
9199

92100
public AbstractKotlinCodegen() {
93101
super();
@@ -515,6 +523,12 @@ public void processOpts() {
515523
String additionalAnnotationsList = additionalProperties.get(ADDITIONAL_MODEL_TYPE_ANNOTATIONS).toString();
516524
this.setAdditionalModelTypeAnnotations(Arrays.asList(SPLIT_ON_SEMICOLON_OR_NEWLINE_REGEX.split(additionalAnnotationsList.trim())));
517525
}
526+
if (additionalProperties.containsKey(SCHEMA_IMPLEMENTS)) {
527+
this.setSchemaImplements(getPropertyAsStringListMap(SCHEMA_IMPLEMENTS));
528+
}
529+
if (additionalProperties.containsKey(SCHEMA_IMPLEMENTS_FIELDS)) {
530+
this.setSchemaImplementsFields(getPropertyAsStringListMap(SCHEMA_IMPLEMENTS_FIELDS));
531+
}
518532

519533
additionalProperties.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, getSortParamsByRequiredFlag());
520534
additionalProperties.put(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG, getSortModelPropertiesByRequiredFlag());
@@ -840,23 +854,30 @@ protected boolean needToImport(String type) {
840854
public CodegenModel fromModel(String name, Schema schema) {
841855
CodegenModel m = super.fromModel(name, schema);
842856
m.getVendorExtensions().putIfAbsent(VendorExtension.X_KOTLIN_IMPLEMENTS.getName(), List.of());
843-
List<String> implementedInterfacesClasses = (List<String>) m.getVendorExtensions().getOrDefault(VendorExtension.X_KOTLIN_IMPLEMENTS.getName(), List.of());
844-
List<String> implementedInterfacesFields = Optional.ofNullable((List<String>) m.getVendorExtensions().get(VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName()))
857+
List<String> schemaImplementedInterfacesClasses = this.getSchemaImplements().getOrDefault(m.getSchemaName(), List.of());
858+
List<String> schemaImplementedInterfacesFields = this.getSchemaImplementsFields().getOrDefault(m.getSchemaName(), List.of());
859+
List<String> vendorExtensionImplementedInterfacesClasses = (List<String>) m.getVendorExtensions().get(VendorExtension.X_KOTLIN_IMPLEMENTS.getName());
860+
List<String> vendorExtensionImplementedInterfacesFields = Optional.ofNullable((List<String>) m.getVendorExtensions().get(VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName()))
845861
.map(xKotlinImplementsFields -> {
846-
if (implementedInterfacesClasses.isEmpty() && !xKotlinImplementsFields.isEmpty()) {
862+
if (vendorExtensionImplementedInterfacesClasses.isEmpty() && !xKotlinImplementsFields.isEmpty()) {
847863
LOGGER.warn("Annotating {} with {} without {} is not supported. {} will be ignored.",
848864
name, VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName(), VendorExtension.X_KOTLIN_IMPLEMENTS.getName(),
849865
VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName());
850866
}
851867
return xKotlinImplementsFields;
852868
}).orElse(List.of());
853-
if (serializableModel) {
854-
if (!implementedInterfacesClasses.contains("java.io.Serializable")) {
855-
var implementedInterfacesClassesWithSerializable = new ArrayList<>(implementedInterfacesClasses);
856-
implementedInterfacesClassesWithSerializable.add("java.io.Serializable");
857-
m.getVendorExtensions().put(VendorExtension.X_KOTLIN_IMPLEMENTS.getName(), implementedInterfacesClassesWithSerializable);
858-
}
859-
}
869+
List<String> combinedImplementedInterfacesClasses = Stream.concat(vendorExtensionImplementedInterfacesClasses.stream(), schemaImplementedInterfacesClasses.stream())
870+
.distinct()
871+
.sorted()
872+
.collect(Collectors.toList());
873+
List<String> combinedImplementedInterfacesFields = Stream.concat(vendorExtensionImplementedInterfacesFields.stream(), schemaImplementedInterfacesFields.stream())
874+
.distinct()
875+
.collect(Collectors.toList());
876+
if (serializableModel && !combinedImplementedInterfacesClasses.contains("java.io.Serializable")) {
877+
combinedImplementedInterfacesClasses.add("java.io.Serializable");
878+
}
879+
m.getVendorExtensions().replace(VendorExtension.X_KOTLIN_IMPLEMENTS.getName(), combinedImplementedInterfacesClasses);
880+
LOGGER.info("Model {} implements interfaces: {}", name, combinedImplementedInterfacesClasses);
860881
m.optionalVars = m.optionalVars.stream().distinct().collect(Collectors.toList());
861882
// Update allVars/requiredVars/optionalVars with isInherited
862883
// Each of these lists contains elements that are similar, but they are all cloned
@@ -873,7 +894,7 @@ public CodegenModel fromModel(String name, Schema schema) {
873894
Stream.of(m.requiredVars, m.optionalVars)
874895
.flatMap(List::stream)
875896
.filter(p -> allVarsMap.containsKey(p.baseName)
876-
|| implementedInterfacesFields.contains(p.baseName)
897+
|| combinedImplementedInterfacesFields.contains(p.baseName)
877898
)
878899
.forEach(p -> p.isInherited = true);
879900
return m;

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

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,80 @@ public void generateSerializableModelWithXimplements() throws Exception {
10401040
);
10411041
}
10421042

1043+
@Test
1044+
public void generateSerializableModelWithSchemaImplements() throws Exception {
1045+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1046+
output.deleteOnExit();
1047+
String outputPath = output.getAbsolutePath().replace('\\', '/');
1048+
1049+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
1050+
codegen.setOutputDir(output.getAbsolutePath());
1051+
codegen.additionalProperties().put(CodegenConstants.SERIALIZABLE_MODEL, true);
1052+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SCHEMA_IMPLEMENTS, Map.of(
1053+
"Pet", "com.some.pack.WithId",
1054+
"Category", List.of("com.some.pack.CategoryInterface"),
1055+
"Dog", List.of("com.some.pack.Canine")
1056+
));
1057+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SCHEMA_IMPLEMENTS_FIELDS, Map.of(
1058+
"Pet", List.of("id"),
1059+
"Category", List.of("name", "id"),
1060+
"Dog", List.of("bark", "breed")
1061+
));
1062+
1063+
ClientOptInput input = new ClientOptInput()
1064+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/petstore-with-x-kotlin-implements.yaml"))
1065+
.config(codegen);
1066+
DefaultGenerator generator = new DefaultGenerator();
1067+
1068+
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
1069+
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
1070+
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
1071+
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
1072+
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
1073+
1074+
generator.opts(input).generate();
1075+
1076+
Path dog = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/model/Dog.kt");
1077+
assertFileContains(
1078+
dog,
1079+
"@get:JsonProperty(\"bark\", required = true) override val bark: kotlin.Boolean,",
1080+
"@get:JsonProperty(\"breed\", required = true) override val breed: Dog.Breed,",
1081+
"@get:JsonProperty(\"likesFetch\", required = true) override val likesFetch: kotlin.Boolean,",
1082+
"@get:JsonProperty(\"name\", required = true) override val name: kotlin.String,",
1083+
"@get:JsonProperty(\"photoUrls\", required = true) override val photoUrls: kotlin.collections.List<kotlin.String>,",
1084+
"@get:JsonProperty(\"petType\", required = true) override val petType: kotlin.String,",
1085+
"@get:JsonProperty(\"id\") override val id: kotlin.Long? = null,",
1086+
"@get:JsonProperty(\"category\") override val category: Category? = null,",
1087+
"@get:JsonProperty(\"tags\") override val tags: kotlin.collections.List<Tag>? = null,",
1088+
"@get:JsonProperty(\"color\") override val color: Color? = null",
1089+
") : Pet, com.some.pack.Canine, com.some.pack.Fetchable, java.io.Serializable {",
1090+
"private const val serialVersionUID: kotlin.Long = 1"
1091+
);
1092+
1093+
Path pet = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/model/Pet.kt");
1094+
assertFileContains(
1095+
pet,
1096+
"interface Pet : com.some.pack.Named, com.some.pack.WithCategory, com.some.pack.WithDefaultMethods, com.some.pack.WithId, java.io.Serializable {",
1097+
"override val name: kotlin.String",
1098+
"val photoUrls: kotlin.collections.List<kotlin.String>",
1099+
"val petType: kotlin.String",
1100+
"override val id: kotlin.Long?",
1101+
"override val category: Category?",
1102+
"val tags: kotlin.collections.List<Tag>?",
1103+
"val color: Color?",
1104+
"private const val serialVersionUID: kotlin.Long = 1"
1105+
);
1106+
1107+
Path category = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/model/Category.kt");
1108+
assertFileContains(
1109+
category,
1110+
"@get:JsonProperty(\"id\") override val id: kotlin.Long? = null,",
1111+
"@get:JsonProperty(\"name\") override val name: kotlin.String? = null",
1112+
") : com.some.pack.CategoryInterface, java.io.Serializable {",
1113+
"private const val serialVersionUID: kotlin.Long = 1"
1114+
);
1115+
}
1116+
10431117
@Test
10441118
public void generateHttpInterfaceReactiveWithReactorResponseEntity() throws Exception {
10451119
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
@@ -1363,8 +1437,9 @@ public void generateNonSerializableModelWithXimplements() throws Exception {
13631437
assertFileNotContains(
13641438
path,
13651439
"import java.io.Serializable",
1366-
") : Pet, Serializable, com.some.pack.Fetchable {",
1367-
") : Pet, Serializable {",
1440+
"Serializable",
1441+
") : Pet, java.io.Serializable, com.some.pack.Fetchable {",
1442+
") : Pet, java.io.Serializable {",
13681443
"private const val serialVersionUID: kotlin.Long = 1"
13691444
);
13701445
}
@@ -2792,7 +2867,7 @@ public void nonReactiveWithResponseEntity() throws Exception {
27922867
+ " @PathVariable(\"petId\") petId: kotlin.Long"
27932868
+ " ): ResponseEntity<Pet>"),
27942869
root.resolve("src/main/kotlin/org/openapitools/api/UserApi.kt"), List.of(
2795-
"fun logoutUser(): ResponseEntity<Unit>"
2870+
"fun logoutUser(): ResponseEntity<Unit>"
27962871
),
27972872
root.resolve("src/main/kotlin/org/openapitools/api/StoreApi.kt"), List.of(
27982873
"fun getInventory(): ResponseEntity<Map<String, kotlin.Int>>")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.some.pack
2+
3+
import org.openapitools.model.Dog
4+
5+
interface Canine {
6+
7+
val breed: Dog.Breed
8+
val bark: kotlin.Boolean
9+
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.some.pack
2+
3+
interface CategoryInterface {
4+
val id: kotlin.Long?
5+
val name: kotlin.String?
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.some.pack
2+
3+
interface WithId {
4+
5+
val id: Long?
6+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ import io.swagger.annotations.ApiModelProperty
2121
data class Category(
2222

2323
@ApiModelProperty(example = "null", value = "")
24-
@get:JsonProperty("id") val id: kotlin.Long? = null,
24+
@get:JsonProperty("id") override val id: kotlin.Long? = null,
2525

2626
@ApiModelProperty(example = "null", value = "")
27-
@get:JsonProperty("name") val name: kotlin.String? = null
28-
) : java.io.Serializable {
27+
@get:JsonProperty("name") override val name: kotlin.String? = null
28+
) : com.some.pack.CategoryInterface, java.io.Serializable {
2929

3030
companion object {
3131
private const val serialVersionUID: kotlin.Long = 1

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import io.swagger.annotations.ApiModelProperty
2828
data class Dog(
2929

3030
@ApiModelProperty(example = "null", required = true, value = "")
31-
@get:JsonProperty("bark", required = true) val bark: kotlin.Boolean,
31+
@get:JsonProperty("bark", required = true) override val bark: kotlin.Boolean,
3232

3333
@ApiModelProperty(example = "null", required = true, value = "")
34-
@get:JsonProperty("breed", required = true) val breed: Dog.Breed,
34+
@get:JsonProperty("breed", required = true) override val breed: Dog.Breed,
3535

3636
@ApiModelProperty(example = "null", required = true, value = "Whether the dog enjoys fetching")
3737
@get:JsonProperty("likesFetch", required = true) override val likesFetch: kotlin.Boolean,
@@ -59,7 +59,7 @@ data class Dog(
5959
@field:Valid
6060
@ApiModelProperty(example = "null", value = "")
6161
@get:JsonProperty("color") override val color: Color? = null
62-
) : Pet, com.some.pack.Fetchable, java.io.Serializable {
62+
) : Pet, com.some.pack.Canine, com.some.pack.Fetchable, java.io.Serializable {
6363

6464
/**
6565
*

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import io.swagger.annotations.ApiModelProperty
4141
JsonSubTypes.Type(value = Dog::class, name = "Dog")
4242
)
4343

44-
interface Pet : com.some.pack.Named, com.some.pack.WithCategory, com.some.pack.WithDefaultMethods, java.io.Serializable {
44+
interface Pet : com.some.pack.Named, com.some.pack.WithCategory, com.some.pack.WithDefaultMethods, com.some.pack.WithId, java.io.Serializable {
4545

4646
@get:ApiModelProperty(example = "null", required = true, value = "")
4747
override val name: kotlin.String
@@ -56,7 +56,7 @@ interface Pet : com.some.pack.Named, com.some.pack.WithCategory, com.some.pack.W
5656

5757

5858
@get:ApiModelProperty(example = "null", value = "")
59-
val id: kotlin.Long?
59+
override val id: kotlin.Long?
6060

6161

6262
@get:ApiModelProperty(example = "null", value = "")

0 commit comments

Comments
 (0)