Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -33,6 +34,7 @@
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;
Expand Down Expand Up @@ -768,6 +770,11 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,6 +39,7 @@
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;
Expand Down Expand Up @@ -648,20 +648,6 @@ public void processOpts() {
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);
}
Expand Down Expand Up @@ -696,7 +682,7 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
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"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2435,6 +2435,19 @@ public static boolean isMetadataOnlySchema(Schema schema) {
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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}}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
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;
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;
Expand All @@ -31,8 +35,10 @@
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;

Expand Down Expand Up @@ -748,6 +754,40 @@ public void useBeanValidationGenerateAnnotationsForRequestBody() throws IOExcept
);
}

@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();
Expand Down Expand Up @@ -1192,4 +1232,54 @@ public void testValidationsInQueryParams_issue21238_Api_Delegate() throws IOExce
"@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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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