Skip to content

Commit 458ea56

Browse files
authored
[SPRING] Add converters for enums (#13349)
* [SPRING] Add converters for enums * Review * review * fix merge * review
1 parent 574a70c commit 458ea56

40 files changed

Lines changed: 756 additions & 127 deletions

File tree

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import static org.openapitools.codegen.utils.StringUtils.camelize;
2323

2424
import com.samskivert.mustache.Mustache;
25+
import io.swagger.v3.oas.models.Components;
2526
import io.swagger.v3.oas.models.OpenAPI;
2627
import io.swagger.v3.oas.models.Operation;
2728
import io.swagger.v3.oas.models.PathItem;
2829
import io.swagger.v3.oas.models.media.Schema;
2930
import io.swagger.v3.oas.models.servers.Server;
31+
3032
import java.io.File;
3133
import java.net.URL;
3234
import java.util.ArrayList;
@@ -41,6 +43,8 @@
4143
import java.util.Set;
4244
import java.util.regex.Matcher;
4345
import java.util.stream.Collectors;
46+
import java.util.stream.Stream;
47+
4448
import org.apache.commons.lang3.tuple.Pair;
4549
import org.openapitools.codegen.CliOption;
4650
import org.openapitools.codegen.CodegenConstants;
@@ -498,6 +502,10 @@ public void processOpts() {
498502
this.setUseFeignClient(true);
499503
} else {
500504
apiTemplateFiles.put("apiController.mustache", "Controller.java");
505+
if (containsEnums()) {
506+
supportingFiles.add(new SupportingFile("converter.mustache",
507+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "EnumConverterConfiguration.java"));
508+
}
501509
supportingFiles.add(new SupportingFile("application.mustache",
502510
("src.main.resources").replace(".", java.io.File.separator), "application.properties"));
503511
supportingFiles.add(new SupportingFile("homeController.mustache",
@@ -595,6 +603,20 @@ public void processOpts() {
595603
}
596604
}
597605

606+
private boolean containsEnums() {
607+
if (openAPI == null) {
608+
return false;
609+
}
610+
611+
Components components = this.openAPI.getComponents();
612+
if (components == null || components.getSchemas() == null) {
613+
return false;
614+
}
615+
616+
return components.getSchemas().values().stream()
617+
.anyMatch(it -> it.getEnum() != null && !it.getEnum().isEmpty());
618+
}
619+
598620
@Override
599621
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co,
600622
Map<String, List<CodegenOperation>> operations) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package {{configPackage}};
2+
3+
{{#models}}
4+
{{#model}}
5+
{{#isEnum}}
6+
import {{modelPackage}}.{{name}};
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
16+
public class EnumConverterConfiguration {
17+
18+
{{#models}}
19+
{{#model}}
20+
{{#isEnum}}
21+
@Bean
22+
Converter<{{{dataType}}}, {{name}}> {{classVarName}}Converter() {
23+
return new Converter<{{{dataType}}}, {{name}}>() {
24+
@Override
25+
public {{name}} convert({{{dataType}}} source) {
26+
return {{name}}.fromValue(source);
27+
}
28+
};
29+
}
30+
{{/isEnum}}
31+
{{/model}}
32+
{{/models}}
33+
34+
}

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

Lines changed: 74 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.openapitools.codegen.java.spring;
1919

2020
import static java.util.stream.Collectors.groupingBy;
21+
import static org.assertj.core.api.Assertions.assertThat;
2122
import static org.openapitools.codegen.TestUtils.assertFileContains;
2223
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
2324
import static org.openapitools.codegen.languages.SpringCodegen.RESPONSE_WRAPPER;
@@ -1214,7 +1215,7 @@ public void shouldPurAdditionalModelTypesOverAllModels() throws IOException {
12141215
generator.opts(input).generate();
12151216

12161217
File[] generatedModels = new File(outputPath + "/src/main/java/org/openapitools/model").listFiles();
1217-
Assertions.assertThat(generatedModels).isNotEmpty();
1218+
assertThat(generatedModels).isNotEmpty();
12181219

12191220
for (File modelPath : generatedModels) {
12201221
JavaFileAssert.assertThat(modelPath)
@@ -1227,22 +1228,9 @@ public void shouldPurAdditionalModelTypesOverAllModels() throws IOException {
12271228

12281229
@Test
12291230
public void testHandleDefaultValue_issue8535() throws Exception {
1230-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1231-
output.deleteOnExit();
1232-
1233-
OpenAPI openAPI = new OpenAPIParser()
1234-
.readLocation("src/test/resources/3_0/issue_8535.yaml", null, new ParseOptions()).getOpenAPI();
1235-
SpringCodegen codegen = new SpringCodegen();
1236-
codegen.setOutputDir(output.getAbsolutePath());
1237-
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
1238-
1239-
ClientOptInput input = new ClientOptInput()
1240-
.openAPI(openAPI)
1241-
.config(codegen);
1242-
1243-
DefaultGenerator generator = new DefaultGenerator();
1244-
Map<String, File> files = generator.opts(input).generate().stream()
1245-
.collect(Collectors.toMap(File::getName, Function.identity()));
1231+
Map<String, Object> additionalProperties = new HashMap<>();
1232+
additionalProperties.put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
1233+
Map<String, File> files = generateFromContract("src/test/resources/3_0/issue_8535.yaml", SPRING_BOOT, additionalProperties);
12461234

12471235
JavaFileAssert.assertThat(files.get("TestHeadersApi.java"))
12481236
.assertMethod("headersTest")
@@ -1329,29 +1317,15 @@ public void testExtraAnnotations() throws IOException {
13291317

13301318
@Test
13311319
public void testResponseWithArray_issue11897() throws Exception {
1332-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1333-
output.deleteOnExit();
1334-
1335-
OpenAPI openAPI = new OpenAPIParser()
1336-
.readLocation("src/test/resources/bugs/issue_11897.yaml", null, new ParseOptions()).getOpenAPI();
1337-
SpringCodegen codegen = new SpringCodegen();
1338-
codegen.setLibrary(SPRING_BOOT);
1339-
codegen.setOutputDir(output.getAbsolutePath());
1340-
codegen.additionalProperties().put(AbstractJavaCodegen.FULL_JAVA_UTIL, "true");
1341-
codegen.additionalProperties().put(SpringCodegen.USE_TAGS, "true");
1342-
codegen.additionalProperties().put(SpringCodegen.INTERFACE_ONLY, "true");
1343-
codegen.additionalProperties().put(SpringCodegen.SKIP_DEFAULT_INTERFACE, "true");
1344-
codegen.additionalProperties().put(SpringCodegen.PERFORM_BEANVALIDATION, "true");
1345-
codegen.additionalProperties().put(SpringCodegen.SPRING_CONTROLLER, "true");
1346-
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1347-
1348-
ClientOptInput input = new ClientOptInput()
1349-
.openAPI(openAPI)
1350-
.config(codegen);
1351-
1352-
DefaultGenerator generator = new DefaultGenerator();
1353-
Map<String, File> files = generator.opts(input).generate().stream()
1354-
.collect(Collectors.toMap(File::getName, Function.identity()));
1320+
Map<String, Object> additionalProperties = new HashMap<>();
1321+
additionalProperties.put(AbstractJavaCodegen.FULL_JAVA_UTIL, "true");
1322+
additionalProperties.put(SpringCodegen.USE_TAGS, "true");
1323+
additionalProperties.put(SpringCodegen.INTERFACE_ONLY, "true");
1324+
additionalProperties.put(SpringCodegen.SKIP_DEFAULT_INTERFACE, "true");
1325+
additionalProperties.put(SpringCodegen.PERFORM_BEANVALIDATION, "true");
1326+
additionalProperties.put(SpringCodegen.SPRING_CONTROLLER, "true");
1327+
additionalProperties.put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1328+
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_11897.yaml", SPRING_BOOT, additionalProperties);
13551329

13561330
JavaFileAssert.assertThat(files.get("MetadataApi.java"))
13571331
.assertMethod("getWithArrayOfObjects").hasReturnType("ResponseEntity<List<TestResponse>>")
@@ -1369,29 +1343,16 @@ public void testResponseWithArray_issue11897() throws Exception {
13691343

13701344
@Test
13711345
public void shouldSetDefaultValueForMultipleArrayItems() throws IOException {
1372-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1373-
output.deleteOnExit();
1346+
Map<String, Object> additionalProperties = new HashMap<>();
1347+
additionalProperties.put(AbstractJavaCodegen.FULL_JAVA_UTIL, "true");
1348+
additionalProperties.put(SpringCodegen.USE_TAGS, "true");
1349+
additionalProperties.put(SpringCodegen.INTERFACE_ONLY, "true");
1350+
additionalProperties.put(SpringCodegen.SKIP_DEFAULT_INTERFACE, "true");
1351+
additionalProperties.put(SpringCodegen.PERFORM_BEANVALIDATION, "true");
1352+
additionalProperties.put(SpringCodegen.SPRING_CONTROLLER, "true");
1353+
additionalProperties.put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
13741354

1375-
OpenAPI openAPI = new OpenAPIParser()
1376-
.readLocation("src/test/resources/bugs/issue_11957.yaml", null, new ParseOptions()).getOpenAPI();
1377-
SpringCodegen codegen = new SpringCodegen();
1378-
codegen.setLibrary(SPRING_BOOT);
1379-
codegen.setOutputDir(output.getAbsolutePath());
1380-
codegen.additionalProperties().put(AbstractJavaCodegen.FULL_JAVA_UTIL, "true");
1381-
codegen.additionalProperties().put(SpringCodegen.USE_TAGS, "true");
1382-
codegen.additionalProperties().put(SpringCodegen.INTERFACE_ONLY, "true");
1383-
codegen.additionalProperties().put(SpringCodegen.SKIP_DEFAULT_INTERFACE, "true");
1384-
codegen.additionalProperties().put(SpringCodegen.PERFORM_BEANVALIDATION, "true");
1385-
codegen.additionalProperties().put(SpringCodegen.SPRING_CONTROLLER, "true");
1386-
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1387-
1388-
ClientOptInput input = new ClientOptInput()
1389-
.openAPI(openAPI)
1390-
.config(codegen);
1391-
1392-
DefaultGenerator generator = new DefaultGenerator();
1393-
Map<String, File> files = generator.opts(input).generate().stream()
1394-
.collect(Collectors.toMap(File::getName, Function.identity()));
1355+
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_11957.yaml", SPRING_BOOT, additionalProperties);
13951356

13961357
JavaFileAssert.assertThat(files.get("SearchApi.java"))
13971358
.assertMethod("defaultList")
@@ -1417,21 +1378,7 @@ public void shouldSetDefaultValueForMultipleArrayItems() throws IOException {
14171378

14181379
@Test
14191380
public void testPutItemsMethodContainsKeyInSuperClassMethodCall_issue12494() throws IOException {
1420-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1421-
output.deleteOnExit();
1422-
1423-
OpenAPI openAPI = new OpenAPIParser()
1424-
.readLocation("src/test/resources/bugs/issue_12494.yaml", null, new ParseOptions()).getOpenAPI();
1425-
SpringCodegen codegen = new SpringCodegen();
1426-
codegen.setOutputDir(output.getAbsolutePath());
1427-
1428-
ClientOptInput input = new ClientOptInput()
1429-
.openAPI(openAPI)
1430-
.config(codegen);
1431-
1432-
DefaultGenerator generator = new DefaultGenerator();
1433-
Map<String, File> files = generator.opts(input).generate().stream()
1434-
.collect(Collectors.toMap(File::getName, Function.identity()));
1381+
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_12494.yaml", null);
14351382

14361383
JavaFileAssert.assertThat(files.get("ChildClass.java"))
14371384
.assertMethod("putSomeMapItem")
@@ -1440,22 +1387,7 @@ public void testPutItemsMethodContainsKeyInSuperClassMethodCall_issue12494() thr
14401387

14411388
@Test
14421389
public void shouldHandleCustomResponseType_issue11731() throws IOException {
1443-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1444-
output.deleteOnExit();
1445-
1446-
OpenAPI openAPI = new OpenAPIParser()
1447-
.readLocation("src/test/resources/bugs/issue_11731.yaml", null, new ParseOptions()).getOpenAPI();
1448-
SpringCodegen codegen = new SpringCodegen();
1449-
codegen.setLibrary(SPRING_BOOT);
1450-
codegen.setOutputDir(output.getAbsolutePath());
1451-
1452-
ClientOptInput input = new ClientOptInput()
1453-
.openAPI(openAPI)
1454-
.config(codegen);
1455-
1456-
DefaultGenerator generator = new DefaultGenerator();
1457-
Map<String, File> files = generator.opts(input).generate().stream()
1458-
.collect(Collectors.toMap(File::getName, Function.identity()));
1390+
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_11731.yaml", SPRING_BOOT);
14591391

14601392
JavaFileAssert.assertThat(files.get("CustomersApi.java"))
14611393
.assertMethod("getAllUsingGET1")
@@ -1464,23 +1396,9 @@ public void shouldHandleCustomResponseType_issue11731() throws IOException {
14641396

14651397
@Test
14661398
public void shouldHandleContentTypeWithSecondWildcardSubtype_issue12457() throws IOException {
1467-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1468-
output.deleteOnExit();
1469-
1470-
OpenAPI openAPI = new OpenAPIParser()
1471-
.readLocation("src/test/resources/bugs/issue_12457.yaml", null, new ParseOptions()).getOpenAPI();
1472-
SpringCodegen codegen = new SpringCodegen();
1473-
codegen.setLibrary(SPRING_BOOT);
1474-
codegen.setOutputDir(output.getAbsolutePath());
1475-
codegen.additionalProperties().put(SpringCodegen.USE_TAGS, "true");
1476-
1477-
ClientOptInput input = new ClientOptInput()
1478-
.openAPI(openAPI)
1479-
.config(codegen);
1480-
1481-
DefaultGenerator generator = new DefaultGenerator();
1482-
Map<String, File> files = generator.opts(input).generate().stream()
1483-
.collect(Collectors.toMap(File::getName, Function.identity()));
1399+
Map<String, Object> additionalProperties = new HashMap<>();
1400+
additionalProperties.put(SpringCodegen.USE_TAGS, "true");
1401+
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_12457.yaml", SPRING_BOOT, additionalProperties);
14841402

14851403
JavaFileAssert.assertThat(files.get("UsersApi.java"))
14861404
.assertMethod("wildcardSubTypeForContentType")
@@ -1493,28 +1411,15 @@ public void shouldHandleContentTypeWithSecondWildcardSubtype_issue12457() throws
14931411

14941412
@Test
14951413
public void shouldGenerateDiscriminatorFromAllOfWhenUsingLegacyDiscriminatorBehaviour_issue12692() throws IOException {
1496-
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1497-
output.deleteOnExit();
1498-
1499-
OpenAPI openAPI = new OpenAPIParser()
1500-
.readLocation("src/test/resources/bugs/issue_12692.yml", null, new ParseOptions()).getOpenAPI();
1501-
SpringCodegen codegen = new SpringCodegen();
1502-
codegen.setLibrary(SPRING_BOOT);
1503-
codegen.setOutputDir(output.getAbsolutePath());
1504-
codegen.additionalProperties().put(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "true");
1505-
1506-
ClientOptInput input = new ClientOptInput()
1507-
.openAPI(openAPI)
1508-
.config(codegen);
1509-
1510-
DefaultGenerator generator = new DefaultGenerator();
1511-
generator.opts(input).generate();
1414+
Map<String, Object> additionalProperties = new HashMap<>();
1415+
additionalProperties.put(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "true");
1416+
Map<String, File> output = generateFromContract("src/test/resources/bugs/issue_12692.yml", SPRING_BOOT, additionalProperties);
15121417

15131418
String jsonTypeInfo = "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", visible = true)";
15141419
String jsonSubType = "@JsonSubTypes({\n" +
15151420
" @JsonSubTypes.Type(value = Cat.class, name = \"cat\")" +
15161421
"})";
1517-
assertFileContains(Paths.get(output.getAbsolutePath() + "/src/main/java/org/openapitools/model/Pet.java"), jsonTypeInfo, jsonSubType);
1422+
assertFileContains(output.get("Pet.java").toPath(), jsonTypeInfo, jsonSubType);
15181423
}
15191424

15201425
@Test
@@ -1696,4 +1601,46 @@ public void shouldNotUseEqualsNullableForArrayWhenNotSetInConfig_issue13385() th
16961601
.assertMethod("equals")
16971602
.bodyContainsLines("return Arrays.equals(this.picture, testObject.picture);");
16981603
}
1604+
1605+
@Test
1606+
public void contractWithoutEnumDoesNotContainsEnumConverter() throws IOException {
1607+
Map<String, File> output = generateFromContract("src/test/resources/3_0/generic.yaml", SPRING_BOOT);
1608+
1609+
assertThat(output).doesNotContainKey("EnumConverterConfiguration.java");
1610+
}
1611+
1612+
@Test
1613+
public void contractWithEnumContainsEnumConverter() throws IOException {
1614+
Map<String, File> output = generateFromContract("src/test/resources/3_0/enum.yaml", SPRING_BOOT);
1615+
1616+
JavaFileAssert.assertThat(output.get("EnumConverterConfiguration.java"))
1617+
.assertMethod("typeConverter");
1618+
}
1619+
1620+
private Map<String, File> generateFromContract(String url, String library) throws IOException {
1621+
return generateFromContract(url, library, new HashMap<>());
1622+
}
1623+
private Map<String, File> generateFromContract(String url, String library, Map<String, Object> additionalProperties) throws IOException {
1624+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1625+
output.deleteOnExit();
1626+
1627+
OpenAPI openAPI = new OpenAPIParser()
1628+
.readLocation(url, null, new ParseOptions()).getOpenAPI();
1629+
1630+
SpringCodegen codegen = new SpringCodegen();
1631+
if (null != library) {
1632+
codegen.setLibrary(library);
1633+
}
1634+
codegen.setOutputDir(output.getAbsolutePath());
1635+
codegen.additionalProperties().putAll(additionalProperties);
1636+
1637+
ClientOptInput input = new ClientOptInput()
1638+
.openAPI(openAPI)
1639+
.config(codegen);
1640+
1641+
DefaultGenerator generator = new DefaultGenerator();
1642+
1643+
return generator.opts(input).generate().stream()
1644+
.collect(Collectors.toMap(File::getName, Function.identity()));
1645+
}
16991646
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Sample API
4+
description: API description in Markdown.
5+
version: 1.0.0
6+
paths:
7+
/ponies:
8+
get:
9+
summary: Returns all animals.
10+
description: Optional extended description in Markdown.
11+
responses:
12+
200:
13+
description: OK
14+
content:
15+
application/json:
16+
schema:
17+
type: array
18+
items:
19+
$ref: '#/components/schemas/Pony'
20+
components:
21+
schemas:
22+
Pony:
23+
type: object
24+
properties:
25+
type:
26+
$ref: '#/components/schemas/Type'
27+
Type:
28+
type: string
29+
enum:
30+
- Earth
31+
- Pegasi
32+
- Unicorn

0 commit comments

Comments
 (0)