Skip to content

Commit ea3fabf

Browse files
[kotlin-spring] add a Spring type converter for enum values #21564
1 parent 473343f commit ea3fabf

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.samskivert.mustache.Mustache;
2121
import com.samskivert.mustache.Mustache.Lambda;
2222
import com.samskivert.mustache.Template;
23+
import io.swagger.v3.oas.models.Components;
2324
import io.swagger.v3.oas.models.OpenAPI;
2425
import io.swagger.v3.oas.models.Operation;
2526
import lombok.Getter;
@@ -768,6 +769,11 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
768769
public void preprocessOpenAPI(OpenAPI openAPI) {
769770
super.preprocessOpenAPI(openAPI);
770771

772+
if (SPRING_BOOT.equals(library) && containsEnums()) {
773+
supportingFiles.add(new SupportingFile("converter.mustache",
774+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "EnumConverterConfiguration.kt"));
775+
}
776+
771777
if (!additionalProperties.containsKey(TITLE)) {
772778
// The purpose of the title is for:
773779
// - README documentation
@@ -797,6 +803,20 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
797803
// TODO: Handle tags
798804
}
799805

806+
private boolean containsEnums() {
807+
if (openAPI == null) {
808+
return false;
809+
}
810+
811+
Components components = this.openAPI.getComponents();
812+
if (components == null || components.getSchemas() == null) {
813+
return false;
814+
}
815+
816+
return components.getSchemas().values().stream()
817+
.anyMatch(it -> it.getEnum() != null && !it.getEnum().isEmpty());
818+
}
819+
800820
@Override
801821
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
802822
super.postProcessModelProperty(model, property);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package {{configPackage}}
2+
3+
{{#models}}
4+
{{#model}}
5+
{{#isEnum}}
6+
import {{modelPackage}}.{{classname}}
7+
{{/isEnum}}
8+
{{/model}}
9+
{{/models}}
10+
11+
import org.springframework.context.annotation.Bean
12+
import org.springframework.context.annotation.Configuration
13+
import org.springframework.core.convert.converter.Converter
14+
15+
@Configuration(value = "{{configPackage}}.enumConverterConfiguration")
16+
class EnumConverterConfiguration {
17+
18+
{{#models}}
19+
{{#model}}
20+
{{#isEnum}}
21+
@Bean(name = ["{{configPackage}}.EnumConverterConfiguration.{{classVarName}}Converter"])
22+
fun {{classVarName}}Converter(): Converter<{{{dataType}}}, {{classname}}> {
23+
return object: Converter<{{{dataType}}}, {{classname}}> {
24+
override fun convert(source: {{{dataType}}}): {{classname}} = {{classname}}.forValue(source)
25+
}
26+
}
27+
{{/isEnum}}
28+
{{/model}}
29+
{{/models}}
30+
31+
}

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
import io.swagger.v3.oas.models.info.Info;
66
import io.swagger.v3.oas.models.servers.Server;
77
import io.swagger.v3.parser.core.models.ParseOptions;
8+
import java.util.HashMap;
9+
import java.util.function.Consumer;
810
import org.apache.commons.io.FileUtils;
911
import org.assertj.core.api.Assertions;
1012
import org.jetbrains.annotations.NotNull;
1113
import org.openapitools.codegen.ClientOptInput;
1214
import org.openapitools.codegen.CodegenConstants;
1315
import org.openapitools.codegen.DefaultGenerator;
1416
import org.openapitools.codegen.TestUtils;
17+
import org.openapitools.codegen.config.CodegenConfigurator;
18+
import org.openapitools.codegen.java.assertions.JavaFileAssert;
1519
import org.openapitools.codegen.kotlin.KotlinTestUtils;
1620
import org.openapitools.codegen.languages.KotlinSpringServerCodegen;
1721
import org.openapitools.codegen.languages.features.CXFServerFeatures;
@@ -31,8 +35,10 @@
3135
import java.util.function.Function;
3236
import java.util.stream.Collectors;
3337

38+
import static org.assertj.core.api.Assertions.assertThat;
3439
import static org.openapitools.codegen.TestUtils.assertFileContains;
3540
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
41+
import static org.openapitools.codegen.languages.SpringCodegen.SPRING_BOOT;
3642
import static org.openapitools.codegen.languages.features.DocumentationProviderFeatures.ANNOTATION_LIBRARY;
3743
import static org.openapitools.codegen.languages.features.DocumentationProviderFeatures.DOCUMENTATION_PROVIDER;
3844

@@ -748,6 +754,49 @@ public void useBeanValidationGenerateAnnotationsForRequestBody() throws IOExcept
748754
);
749755
}
750756

757+
@Test
758+
public void contractWithoutEnumDoesNotContainEnumConverter() throws IOException {
759+
Map<String, File> output = generateFromContract("src/test/resources/3_0/generic.yaml");
760+
761+
assertThat(output).doesNotContainKey("EnumConverterConfiguration.kt");
762+
}
763+
764+
@Test
765+
public void contractWithEnumContainsEnumConverter() throws IOException {
766+
Map<String, File> output = generateFromContract("src/test/resources/3_0/enum.yaml");
767+
768+
File enumConverterFile = output.get("EnumConverterConfiguration.kt");
769+
assertThat(enumConverterFile).isNotNull();
770+
assertFileContains(enumConverterFile.toPath(), "fun typeConverter(): Converter<kotlin.String, Type> {");
771+
assertFileContains(enumConverterFile.toPath(), "return object: Converter<kotlin.String, Type> {");
772+
assertFileContains(enumConverterFile.toPath(), "override fun convert(source: kotlin.String): Type = Type.forValue(source)");
773+
}
774+
775+
@Test
776+
public void contractWithResolvedInnerEnumContainsEnumConverter() throws IOException {
777+
File output = Files.createTempDirectory("test").toFile();
778+
output.deleteOnExit();
779+
780+
final CodegenConfigurator configurator = new CodegenConfigurator()
781+
.setGeneratorName("kotlin-spring")
782+
.setInputSpec("src/test/resources/3_0/inner_enum.yaml")
783+
.addInlineSchemaOption("RESOLVE_INLINE_ENUMS", "true")
784+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
785+
786+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
787+
DefaultGenerator generator = new DefaultGenerator();
788+
generator.setGenerateMetadata(false);
789+
790+
Map<String, File> files = generator.opts(clientOptInput).generate().stream()
791+
.collect(Collectors.toMap(File::getName, Function.identity()));
792+
793+
File enumConverterFile = files.get("EnumConverterConfiguration.kt");
794+
assertThat(enumConverterFile).isNotNull();
795+
assertFileContains(enumConverterFile.toPath(), "fun ponyTypeConverter(): Converter<kotlin.String, PonyType> {");
796+
assertFileContains(enumConverterFile.toPath(), "return object: Converter<kotlin.String, PonyType> {");
797+
assertFileContains(enumConverterFile.toPath(), "override fun convert(source: kotlin.String): PonyType = PonyType.forValue(source)");
798+
}
799+
751800
@Test
752801
public void givenMultipartFormArray_whenGenerateDelegateAndService_thenParameterIsCreatedAsListOfMultipartFile() throws IOException {
753802
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
@@ -1192,4 +1241,55 @@ public void testValidationsInQueryParams_issue21238_Api_Delegate() throws IOExce
11921241
"@NotNull", "@Valid", "@Pattern(regexp=\"^[a-zA-Z0-9]+[a-zA-Z0-9\\\\.\\\\-_]*[a-zA-Z0-9]+$\")");
11931242
}
11941243

1244+
1245+
private Map<String, File> generateFromContract(String url) throws IOException {
1246+
return generateFromContract(url, new HashMap<>(), new HashMap<>());
1247+
}
1248+
1249+
private Map<String, File> generateFromContract(String url, Map<String, Object> additionalProperties) throws IOException {
1250+
return generateFromContract(url, additionalProperties, new HashMap<>());
1251+
}
1252+
1253+
private Map<String, File> generateFromContract(
1254+
String url,
1255+
Map<String, Object> additionalProperties,
1256+
Map<String, String> generatorPropertyDefaults
1257+
) throws IOException {
1258+
return generateFromContract(url, additionalProperties, generatorPropertyDefaults, codegen -> {
1259+
});
1260+
}
1261+
1262+
/**
1263+
* Generate the contract with additional configuration.
1264+
* <p>
1265+
* use CodegenConfigurator instead of CodegenConfig for easier configuration like in JavaClientCodeGenTest
1266+
*/
1267+
private Map<String, File> generateFromContract(
1268+
String url,
1269+
Map<String, Object> additionalProperties,
1270+
Map<String, String> generatorPropertyDefaults,
1271+
Consumer<CodegenConfigurator> consumer
1272+
) throws IOException {
1273+
1274+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1275+
output.deleteOnExit();
1276+
1277+
final CodegenConfigurator configurator = new CodegenConfigurator()
1278+
.setGeneratorName("kotlin-spring")
1279+
.setAdditionalProperties(additionalProperties)
1280+
.setValidateSpec(false)
1281+
.setInputSpec(url)
1282+
.setLibrary(SPRING_BOOT)
1283+
.setOutputDir(output.getAbsolutePath());
1284+
1285+
consumer.accept(configurator);
1286+
1287+
ClientOptInput input = configurator.toClientOptInput();
1288+
DefaultGenerator generator = new DefaultGenerator();
1289+
generator.setGenerateMetadata(false);
1290+
generatorPropertyDefaults.forEach(generator::setGeneratorPropertyDefault);
1291+
1292+
return generator.opts(input).generate().stream()
1293+
.collect(Collectors.toMap(File::getName, Function.identity()));
1294+
}
11951295
}

0 commit comments

Comments
 (0)