Skip to content

Commit 2ab40b9

Browse files
Add support for x-kotlin-implements vendor extension to the kotlin client generator
1 parent 67b2433 commit 2ab40b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1830
-57
lines changed

.github/workflows/samples-kotlin-client.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
- samples/client/others/kotlin-jvm-okhttp-path-comments
7272
- samples/client/others/kotlin-integer-enum
7373
- samples/client/petstore/kotlin-allOf-discriminator-kotlinx-serialization
74+
- samples/client/others/kotlin-oneOf-discriminator
7475
steps:
7576
- uses: actions/checkout@v5
7677
- uses: actions/setup-java@v5
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
generatorName: kotlin
2+
library: jvm-spring-restclient
3+
outputDir: samples/client/others/kotlin-oneOf-discriminator
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/oneOf-with-discriminator-mapping.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
6+
additionalProperties:
7+
artifactId: kotlin-oneOf-discriminator
8+
serializableModel: "false"
9+
dateLibrary: java8
10+
useSpringBoot3: true
11+
serializationLibrary: jackson

docs/generators/kotlin.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5959

6060
| Extension name | Description | Applicable for | Default value |
6161
| -------------- | ----------- | -------------- | ------------- |
62+
|x-kotlin-implements|Ability to specify interfaces that model must implement|MODEL|empty array
63+
|x-kotlin-implements-fields|Specify attributes that are implemented by the interface(s) added via `x-kotlin-implements`|MODEL|empty array
6264
|x-class-extra-annotation|List of custom annotations to be added to model|MODEL|null
6365
|x-field-extra-annotation|List of custom annotations to be added to property|FIELD, OPERATION_PARAMETER|null
6466

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ public enum VendorExtension {
2626
X_OPERATION_EXTRA_ANNOTATION("x-operation-extra-annotation", ExtensionLevel.OPERATION, "List of custom annotations to be added to operation", null),
2727
X_VERSION_PARAM("x-version-param", ExtensionLevel.OPERATION_PARAMETER, "Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false", null),
2828
X_PATTERN_MESSAGE("x-pattern-message", Arrays.asList(ExtensionLevel.FIELD, ExtensionLevel.OPERATION_PARAMETER), "Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable", null),
29-
X_ZERO_BASED_ENUM("x-zero-based-enum", ExtensionLevel.MODEL, "When used on an enum, the index will not be generated and the default numbering will be used, zero-based", "false"),
30-
;
29+
X_ZERO_BASED_ENUM("x-zero-based-enum", ExtensionLevel.MODEL, "When used on an enum, the index will not be generated and the default numbering will be used, zero-based", "false");
3130

3231
private final String name;
3332
private final List<ExtensionLevel> levels;

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

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -828,23 +828,13 @@ protected boolean isReservedWord(String word) {
828828
protected boolean needToImport(String type) {
829829
// provides extra protection against improperly trying to import language primitives and java types
830830
return !type.startsWith("kotlin.") && !type.startsWith("java.") &&
831-
!defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type) &&
832-
!type.contains(".");
831+
!defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type) &&
832+
!type.contains(".");
833833
}
834834

835835
@Override
836836
public CodegenModel fromModel(String name, Schema schema) {
837837
CodegenModel m = super.fromModel(name, schema);
838-
List<String> implementedInterfacesClasses = (List<String>) m.getVendorExtensions().getOrDefault(VendorExtension.X_KOTLIN_IMPLEMENTS.getName(), List.of());
839-
List<String> implementedInterfacesFields = Optional.ofNullable((List<String>) m.getVendorExtensions().get(VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName()))
840-
.map(xKotlinImplementsFields -> {
841-
if (implementedInterfacesClasses.isEmpty() && !xKotlinImplementsFields.isEmpty()) {
842-
LOGGER.warn("Annotating {} with {} without {} is not supported. {} will be ignored.",
843-
name, VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName(), VendorExtension.X_KOTLIN_IMPLEMENTS.getName(),
844-
VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName());
845-
}
846-
return xKotlinImplementsFields;
847-
}).orElse(List.of());
848838
m.optionalVars = m.optionalVars.stream().distinct().collect(Collectors.toList());
849839
// Update allVars/requiredVars/optionalVars with isInherited
850840
// Each of these lists contains elements that are similar, but they are all cloned
@@ -860,11 +850,9 @@ public CodegenModel fromModel(String name, Schema schema) {
860850
// Update any other vars (requiredVars, optionalVars)
861851
Stream.of(m.requiredVars, m.optionalVars)
862852
.flatMap(List::stream)
863-
.filter(p -> allVarsMap.containsKey(p.baseName)
864-
|| implementedInterfacesFields.contains(p.baseName)
865-
)
853+
.filter(p -> allVarsMap.containsKey(p.baseName))
866854
.forEach(p -> p.isInherited = true);
867-
return m;
855+
return addIsInheritedBasedOnImplementsVendorExtension(name, m);
868856
}
869857

870858
@Override
@@ -1172,4 +1160,44 @@ protected void doDataTypeAssignment(final String returnType, DataTypeAssigner da
11721160
}
11731161
}
11741162
}
1163+
1164+
/**
1165+
* Uses the x-kotlin-implements and the x-kotlin-implements-fields vendor extensions to set the isInherited CodegenProperty field.
1166+
* Will log a warning if an invalid vendor extension combination is used.
1167+
* @param name The name
1168+
* @param codegenModel The codegenModel
1169+
* @return The modified CodegenModel where isInherited has been added to the var CodegenProperty
1170+
*/
1171+
private CodegenModel addIsInheritedBasedOnImplementsVendorExtension(String name, CodegenModel codegenModel) {
1172+
String warningMessage = "Annotating {} with {} without {} is not supported. {} will be ignored.";
1173+
String kotlinImplements = VendorExtension.X_KOTLIN_IMPLEMENTS.getName();
1174+
String kotlinImplementsFields = VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS.getName();
1175+
Map<String, Object> vendorExtensions = codegenModel.getVendorExtensions();
1176+
List<String> implementedInterfacesClasses = (List<String>) vendorExtensions.getOrDefault(kotlinImplements, List.of());
1177+
List<String> implementedInterfacesFields = Optional.ofNullable((List<String>) vendorExtensions.get(kotlinImplementsFields))
1178+
.map(xKotlinImplementsFields -> {
1179+
if (implementedInterfacesClasses.isEmpty() && !xKotlinImplementsFields.isEmpty()) {
1180+
LOGGER.warn(warningMessage, name,
1181+
kotlinImplementsFields,
1182+
kotlinImplements,
1183+
kotlinImplementsFields
1184+
);
1185+
}
1186+
return xKotlinImplementsFields;
1187+
})
1188+
.orElse(List.of());
1189+
codegenModel.optionalVars.stream()
1190+
.filter(p -> implementedInterfacesFields.contains(p.baseName))
1191+
.forEach(p -> p.isInherited = true);
1192+
codegenModel.requiredVars.stream()
1193+
.filter(p -> implementedInterfacesFields.contains(p.baseName))
1194+
.forEach(p -> p.isInherited = true);
1195+
codegenModel.allVars.stream()
1196+
.filter(p -> implementedInterfacesFields.contains(p.baseName))
1197+
.forEach(p -> p.isInherited = true);
1198+
codegenModel.vars.stream()
1199+
.filter(p -> implementedInterfacesFields.contains(p.baseName))
1200+
.forEach(p -> p.isInherited = true);
1201+
return codegenModel;
1202+
}
11751203
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,8 @@ public void postProcess() {
11281128
@Override
11291129
public List<VendorExtension> getSupportedVendorExtensions() {
11301130
var extensions = super.getSupportedVendorExtensions();
1131+
extensions.add(VendorExtension.X_KOTLIN_IMPLEMENTS);
1132+
extensions.add(VendorExtension.X_KOTLIN_IMPLEMENTS_FIELDS);
11311133
extensions.add(VendorExtension.X_CLASS_EXTRA_ANNOTATION);
11321134
extensions.add(VendorExtension.X_FIELD_EXTRA_ANNOTATION);
11331135
return extensions;

modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ import {{packageName}}.infrastructure.ITransformForStorage
108108
{{#required}}{{>data_class_req_var}}{{/required}}{{^required}}{{>data_class_opt_var}}{{/required}}{{^-last}},{{/-last}}
109109

110110
{{/allVars}}
111-
){{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#kotlinx_serialization}}(){{/kotlinx_serialization}}{{#multiplatform}}(){{/multiplatform}}{{#isArray}}(){{/isArray}}{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{^parcelizeModels}} : Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{^serializableModel}}{{#parcelizeModels}} : Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{#parcelizeModels}} : Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#generateRoomModels}}{{#parent}}, {{/parent}}{{^discriminator}}{{^parent}}:{{/parent}} ITransformForStorage<{{classname}}RoomModel>{{/discriminator}}{{/generateRoomModels}}{{#vendorExtensions.x-has-data-class-body}} {
111+
){{/discriminator}}{{#vendorExtensions.x-kotlin-implements}} : {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-kotlin-implements}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#kotlinx_serialization}}(){{/kotlinx_serialization}}{{#multiplatform}}(){{/multiplatform}}{{#isArray}}(){{/isArray}}{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{^parcelizeModels}} : Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{^serializableModel}}{{#parcelizeModels}} : Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{#parcelizeModels}} : Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#generateRoomModels}}{{#parent}}, {{/parent}}{{^discriminator}}{{^parent}}:{{/parent}} ITransformForStorage<{{classname}}RoomModel>{{/discriminator}}{{/generateRoomModels}}{{#vendorExtensions.x-has-data-class-body}} {
112112
{{/vendorExtensions.x-has-data-class-body}}
113113
{{#generateRoomModels}}
114114
companion object { }

0 commit comments

Comments
 (0)