mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-10-14 00:13:50 +00:00
* [kotlin-spring] add a Spring type converter for enum values #21564 * [kotlin-spring] simplify unit test for inner enum converter * update samples * code review feedback; move containsEnums to ModelUtils * code review feedback; provide comment for generated EnumConverterConfiguration.kt * update samples --------- Co-authored-by: Chris Gual <cgual@omnidian.com>
This commit is contained in:
parent
31089c0e49
commit
bfb69388aa
@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Mustache.Lambda;
|
||||
import com.samskivert.mustache.Template;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import lombok.Getter;
|
||||
@ -33,6 +34,7 @@ import org.openapitools.codegen.model.ModelMap;
|
||||
import org.openapitools.codegen.model.ModelsMap;
|
||||
import org.openapitools.codegen.model.OperationMap;
|
||||
import org.openapitools.codegen.model.OperationsMap;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.openapitools.codegen.utils.URLPathUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -768,6 +770,11 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
super.preprocessOpenAPI(openAPI);
|
||||
|
||||
if (SPRING_BOOT.equals(library) && ModelUtils.containsEnums(this.openAPI)) {
|
||||
supportingFiles.add(new SupportingFile("converter.mustache",
|
||||
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "EnumConverterConfiguration.kt"));
|
||||
}
|
||||
|
||||
if (!additionalProperties.containsKey(TITLE)) {
|
||||
// The purpose of the title is for:
|
||||
// - README documentation
|
||||
|
@ -18,7 +18,6 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
@ -40,6 +39,7 @@ import org.openapitools.codegen.model.OperationsMap;
|
||||
import org.openapitools.codegen.templating.mustache.SplitStringLambda;
|
||||
import org.openapitools.codegen.templating.mustache.SpringHttpStatusLambda;
|
||||
import org.openapitools.codegen.templating.mustache.TrimWhitespaceLambda;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.openapitools.codegen.utils.ProcessUtils;
|
||||
import org.openapitools.codegen.utils.URLPathUtils;
|
||||
import org.slf4j.Logger;
|
||||
@ -648,20 +648,6 @@ public class SpringCodegen extends AbstractJavaCodegen
|
||||
supportsAdditionalPropertiesWithComposedSchema = true;
|
||||
}
|
||||
|
||||
private boolean containsEnums() {
|
||||
if (openAPI == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Components components = this.openAPI.getComponents();
|
||||
if (components == null || components.getSchemas() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return components.getSchemas().values().stream()
|
||||
.anyMatch(it -> it.getEnum() != null && !it.getEnum().isEmpty());
|
||||
}
|
||||
|
||||
private boolean supportLibraryUseTags() {
|
||||
return SPRING_BOOT.equals(library) || SPRING_CLOUD_LIBRARY.equals(library);
|
||||
}
|
||||
@ -696,7 +682,7 @@ public class SpringCodegen extends AbstractJavaCodegen
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
super.preprocessOpenAPI(openAPI);
|
||||
|
||||
if (SPRING_BOOT.equals(library) && containsEnums()) {
|
||||
if (SPRING_BOOT.equals(library) && ModelUtils.containsEnums(this.openAPI)) {
|
||||
supportingFiles.add(new SupportingFile("converter.mustache",
|
||||
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "EnumConverterConfiguration.java"));
|
||||
}
|
||||
|
@ -2435,6 +2435,19 @@ public class ModelUtils {
|
||||
schema.getContentSchema() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the OpenAPI specification contains any schemas which are enums.
|
||||
* @param openAPI OpenAPI specification
|
||||
* @return true if the OpenAPI specification contains any schemas which are enums.
|
||||
*/
|
||||
public static boolean containsEnums(OpenAPI openAPI) {
|
||||
Map<String, Schema> schemaMap = getSchemas(openAPI);
|
||||
if (schemaMap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return schemaMap.values().stream().anyMatch(ModelUtils::isEnumSchema);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface OpenAPISchemaVisitor {
|
||||
|
38
modules/openapi-generator/src/main/resources/kotlin-spring/converter.mustache
vendored
Normal file
38
modules/openapi-generator/src/main/resources/kotlin-spring/converter.mustache
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package {{configPackage}}
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#isEnum}}
|
||||
import {{modelPackage}}.{{classname}}
|
||||
{{/isEnum}}
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
|
||||
/**
|
||||
* This class provides Spring Converter beans for the enum models in the OpenAPI specification.
|
||||
*
|
||||
* By default, Spring only converts primitive types to enums using Enum::valueOf, which can prevent
|
||||
* correct conversion if the OpenAPI specification is using an `enumPropertyNaming` other than
|
||||
* `original` or the specification has an integer enum.
|
||||
*/
|
||||
@Configuration(value = "{{configPackage}}.enumConverterConfiguration")
|
||||
class EnumConverterConfiguration {
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#isEnum}}
|
||||
@Bean(name = ["{{configPackage}}.EnumConverterConfiguration.{{classVarName}}Converter"])
|
||||
fun {{classVarName}}Converter(): Converter<{{{dataType}}}, {{classname}}> {
|
||||
return object: Converter<{{{dataType}}}, {{classname}}> {
|
||||
override fun convert(source: {{{dataType}}}): {{classname}} = {{classname}}.forValue(source)
|
||||
}
|
||||
}
|
||||
{{/isEnum}}
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
|
||||
}
|
@ -5,6 +5,8 @@ import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.parser.core.models.ParseOptions;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -12,6 +14,8 @@ import org.openapitools.codegen.ClientOptInput;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.DefaultGenerator;
|
||||
import org.openapitools.codegen.TestUtils;
|
||||
import org.openapitools.codegen.config.CodegenConfigurator;
|
||||
import org.openapitools.codegen.java.assertions.JavaFileAssert;
|
||||
import org.openapitools.codegen.kotlin.KotlinTestUtils;
|
||||
import org.openapitools.codegen.languages.KotlinSpringServerCodegen;
|
||||
import org.openapitools.codegen.languages.features.CXFServerFeatures;
|
||||
@ -31,8 +35,10 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.openapitools.codegen.TestUtils.assertFileContains;
|
||||
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
|
||||
import static org.openapitools.codegen.languages.SpringCodegen.SPRING_BOOT;
|
||||
import static org.openapitools.codegen.languages.features.DocumentationProviderFeatures.ANNOTATION_LIBRARY;
|
||||
import static org.openapitools.codegen.languages.features.DocumentationProviderFeatures.DOCUMENTATION_PROVIDER;
|
||||
|
||||
@ -748,6 +754,40 @@ public class KotlinSpringServerCodegenTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contractWithoutEnumDoesNotContainEnumConverter() throws IOException {
|
||||
Map<String, File> output = generateFromContract("src/test/resources/3_0/generic.yaml");
|
||||
|
||||
assertThat(output).doesNotContainKey("EnumConverterConfiguration.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contractWithEnumContainsEnumConverter() throws IOException {
|
||||
Map<String, File> output = generateFromContract("src/test/resources/3_0/enum.yaml");
|
||||
|
||||
File enumConverterFile = output.get("EnumConverterConfiguration.kt");
|
||||
assertThat(enumConverterFile).isNotNull();
|
||||
assertFileContains(enumConverterFile.toPath(), "fun typeConverter(): Converter<kotlin.String, Type> {");
|
||||
assertFileContains(enumConverterFile.toPath(), "return object: Converter<kotlin.String, Type> {");
|
||||
assertFileContains(enumConverterFile.toPath(), "override fun convert(source: kotlin.String): Type = Type.forValue(source)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contractWithResolvedInnerEnumContainsEnumConverter() throws IOException {
|
||||
Map<String, File> files = generateFromContract(
|
||||
"src/test/resources/3_0/inner_enum.yaml",
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
configurator -> configurator.addInlineSchemaOption("RESOLVE_INLINE_ENUMS", "true")
|
||||
);
|
||||
|
||||
File enumConverterFile = files.get("EnumConverterConfiguration.kt");
|
||||
assertThat(enumConverterFile).isNotNull();
|
||||
assertFileContains(enumConverterFile.toPath(), "fun ponyTypeConverter(): Converter<kotlin.String, PonyType> {");
|
||||
assertFileContains(enumConverterFile.toPath(), "return object: Converter<kotlin.String, PonyType> {");
|
||||
assertFileContains(enumConverterFile.toPath(), "override fun convert(source: kotlin.String): PonyType = PonyType.forValue(source)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMultipartFormArray_whenGenerateDelegateAndService_thenParameterIsCreatedAsListOfMultipartFile() throws IOException {
|
||||
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
|
||||
@ -1192,4 +1232,54 @@ public class KotlinSpringServerCodegenTest {
|
||||
"@NotNull", "@Valid", "@Pattern(regexp=\"^[a-zA-Z0-9]+[a-zA-Z0-9\\\\.\\\\-_]*[a-zA-Z0-9]+$\")");
|
||||
}
|
||||
|
||||
private Map<String, File> generateFromContract(String url) throws IOException {
|
||||
return generateFromContract(url, new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
private Map<String, File> generateFromContract(String url, Map<String, Object> additionalProperties) throws IOException {
|
||||
return generateFromContract(url, additionalProperties, new HashMap<>());
|
||||
}
|
||||
|
||||
private Map<String, File> generateFromContract(
|
||||
String url,
|
||||
Map<String, Object> additionalProperties,
|
||||
Map<String, String> generatorPropertyDefaults
|
||||
) throws IOException {
|
||||
return generateFromContract(url, additionalProperties, generatorPropertyDefaults, codegen -> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the contract with additional configuration.
|
||||
* <p>
|
||||
* use CodegenConfigurator instead of CodegenConfig for easier configuration like in JavaClientCodeGenTest
|
||||
*/
|
||||
private Map<String, File> generateFromContract(
|
||||
String url,
|
||||
Map<String, Object> additionalProperties,
|
||||
Map<String, String> generatorPropertyDefaults,
|
||||
Consumer<CodegenConfigurator> consumer
|
||||
) throws IOException {
|
||||
|
||||
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
|
||||
output.deleteOnExit();
|
||||
|
||||
final CodegenConfigurator configurator = new CodegenConfigurator()
|
||||
.setGeneratorName("kotlin-spring")
|
||||
.setAdditionalProperties(additionalProperties)
|
||||
.setValidateSpec(false)
|
||||
.setInputSpec(url)
|
||||
.setLibrary(SPRING_BOOT)
|
||||
.setOutputDir(output.getAbsolutePath());
|
||||
|
||||
consumer.accept(configurator);
|
||||
|
||||
ClientOptInput input = configurator.toClientOptInput();
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
generator.setGenerateMetadata(false);
|
||||
generatorPropertyDefaults.forEach(generator::setGeneratorPropertyDefault);
|
||||
|
||||
return generator.opts(input).generate().stream()
|
||||
.collect(Collectors.toMap(File::getName, Function.identity()));
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ settings.gradle
|
||||
src/main/kotlin/org/openapitools/api/ApiUtil.kt
|
||||
src/main/kotlin/org/openapitools/api/DefaultApi.kt
|
||||
src/main/kotlin/org/openapitools/api/Exceptions.kt
|
||||
src/main/kotlin/org/openapitools/configuration/EnumConverterConfiguration.kt
|
||||
src/main/kotlin/org/openapitools/model/ApiError.kt
|
||||
src/main/kotlin/org/openapitools/model/ReasonCode.kt
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.openapitools.configuration
|
||||
|
||||
import org.openapitools.model.ReasonCode
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
|
||||
/**
|
||||
* This class provides Spring Converter beans for the enum models in the OpenAPI specification.
|
||||
*
|
||||
* By default, Spring only converts primitive types to enums using Enum::valueOf, which can prevent
|
||||
* correct conversion if the OpenAPI specification is using an `enumPropertyNaming` other than
|
||||
* `original` or the specification has an integer enum.
|
||||
*/
|
||||
@Configuration(value = "org.openapitools.configuration.enumConverterConfiguration")
|
||||
class EnumConverterConfiguration {
|
||||
|
||||
@Bean(name = ["org.openapitools.configuration.EnumConverterConfiguration.reasonCodeConverter"])
|
||||
fun reasonCodeConverter(): Converter<kotlin.Int, ReasonCode> {
|
||||
return object: Converter<kotlin.Int, ReasonCode> {
|
||||
override fun convert(source: kotlin.Int): ReasonCode = ReasonCode.forValue(source)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,7 @@ src/main/kotlin/org/openapitools/SpringDocConfiguration.kt
|
||||
src/main/kotlin/org/openapitools/api/ApiUtil.kt
|
||||
src/main/kotlin/org/openapitools/api/Exceptions.kt
|
||||
src/main/kotlin/org/openapitools/api/MultipartMixedApiController.kt
|
||||
src/main/kotlin/org/openapitools/configuration/EnumConverterConfiguration.kt
|
||||
src/main/kotlin/org/openapitools/model/MultipartMixedRequestMarker.kt
|
||||
src/main/kotlin/org/openapitools/model/MultipartMixedStatus.kt
|
||||
src/main/resources/application.yaml
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.openapitools.configuration
|
||||
|
||||
import org.openapitools.model.MultipartMixedStatus
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
|
||||
/**
|
||||
* This class provides Spring Converter beans for the enum models in the OpenAPI specification.
|
||||
*
|
||||
* By default, Spring only converts primitive types to enums using Enum::valueOf, which can prevent
|
||||
* correct conversion if the OpenAPI specification is using an `enumPropertyNaming` other than
|
||||
* `original` or the specification has an integer enum.
|
||||
*/
|
||||
@Configuration(value = "org.openapitools.configuration.enumConverterConfiguration")
|
||||
class EnumConverterConfiguration {
|
||||
|
||||
@Bean(name = ["org.openapitools.configuration.EnumConverterConfiguration.multipartMixedStatusConverter"])
|
||||
fun multipartMixedStatusConverter(): Converter<kotlin.String, MultipartMixedStatus> {
|
||||
return object: Converter<kotlin.String, MultipartMixedStatus> {
|
||||
override fun convert(source: kotlin.String): MultipartMixedStatus = MultipartMixedStatus.forValue(source)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user