Skip to content

Commit 0f7eeb2

Browse files
authored
Merge pull request #1259 from swagger-api/OsztosA-feature/java-interface-discriminator
java interface discriminator mapping
2 parents 3fd9941 + 3029c17 commit 0f7eeb2

File tree

9 files changed

+318
-36
lines changed

9 files changed

+318
-36
lines changed

src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java

+22-14
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,8 @@
9999
import java.util.regex.Pattern;
100100
import java.util.stream.Collectors;
101101

102-
import static io.swagger.codegen.v3.CodegenConstants.HAS_ONLY_READ_ONLY_EXT_NAME;
103-
import static io.swagger.codegen.v3.CodegenConstants.HAS_OPTIONAL_EXT_NAME;
104-
import static io.swagger.codegen.v3.CodegenConstants.HAS_REQUIRED_EXT_NAME;
105-
import static io.swagger.codegen.v3.CodegenConstants.IS_ARRAY_MODEL_EXT_NAME;
106-
import static io.swagger.codegen.v3.CodegenConstants.IS_CONTAINER_EXT_NAME;
107-
import static io.swagger.codegen.v3.CodegenConstants.IS_ENUM_EXT_NAME;
108-
import static io.swagger.codegen.v3.generators.CodegenHelper.getDefaultIncludes;
109-
import static io.swagger.codegen.v3.generators.CodegenHelper.getImportMappings;
110-
import static io.swagger.codegen.v3.generators.CodegenHelper.getTypeMappings;
111-
import static io.swagger.codegen.v3.generators.CodegenHelper.initalizeSpecialCharacterMapping;
102+
import static io.swagger.codegen.v3.CodegenConstants.*;
103+
import static io.swagger.codegen.v3.generators.CodegenHelper.*;
112104
import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue;
113105

114106
public abstract class DefaultCodegenConfig implements CodegenConfig {
@@ -1344,9 +1336,6 @@ public CodegenModel fromModel(String name, Schema schema, Map<String, Schema> al
13441336
codegenModel.getVendorExtensions().put(CodegenConstants.IS_ALIAS_EXT_NAME, typeAliases.containsKey(name));
13451337

13461338
codegenModel.discriminator = schema.getDiscriminator();
1347-
if (codegenModel.discriminator != null && codegenModel.discriminator.getPropertyName() != null) {
1348-
codegenModel.discriminator.setPropertyName(toVarName(codegenModel.discriminator.getPropertyName()));
1349-
}
13501339

13511340
if (schema.getXml() != null) {
13521341
codegenModel.xmlPrefix = schema.getXml().getPrefix();
@@ -1397,6 +1386,25 @@ else if (schema instanceof ComposedSchema) {
13971386
}
13981387
}
13991388
}
1389+
if (codegenModel.discriminator != null && codegenModel.discriminator.getPropertyName() != null) {
1390+
codegenModel.discriminator.setPropertyName(toVarName(codegenModel.discriminator.getPropertyName()));
1391+
Map<String, String> classnameKeys = new HashMap<>();
1392+
1393+
if (composed.getOneOf()!=null) {
1394+
composed.getOneOf().forEach( s -> {
1395+
codegenModel.discriminator.getMapping().keySet().stream().filter( key -> codegenModel.discriminator.getMapping().get(key).equals(s.get$ref()))
1396+
.forEach(key -> {
1397+
String mappingValue = codegenModel.discriminator.getMapping().get(key);
1398+
if (classnameKeys.containsKey(codegenModel.classname)) {
1399+
throw new IllegalArgumentException("Duplicate shema name in discriminator mapping");
1400+
}
1401+
classnameKeys.put(toModelName(mappingValue.replace("#/components/schemas/", "")),key);
1402+
});
1403+
});
1404+
codegenModel.discriminator.getMapping().putAll(classnameKeys);
1405+
}
1406+
}
1407+
14001408
} else {
14011409
allProperties = null;
14021410
allRequired = null;
@@ -4415,7 +4423,7 @@ protected void setParameterJson(CodegenParameter codegenParameter, Schema parame
44154423
codegenParameter.isJson = true;
44164424
}
44174425
}
4418-
4426+
44194427
protected boolean isFileTypeSchema(Schema schema) {
44204428
final Schema fileTypeSchema;
44214429
if (StringUtils.isNotBlank(schema.get$ref())) {

src/main/java/io/swagger/codegen/v3/generators/SchemaHandler.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,7 @@ protected void addInterfaces(List<Schema> schemas, CodegenModel codegenModel, Ma
199199
codegenModel.addSubType(model);
200200
}
201201

202-
if (codegenModel.getVendorExtensions() == null || codegenModel.getVendorExtensions().containsKey("x-discriminator-type")) {
203-
continue;
204-
}
202+
205203
if (codegenModel.getDiscriminator() != null && StringUtils.isNotBlank(codegenModel.getDiscriminator().getPropertyName())) {
206204
Optional<CodegenProperty> optionalProperty = model.vars.stream()
207205
.filter(codegenProperty -> codegenProperty.baseName.equals(codegenModel.getDiscriminator().getPropertyName())).findFirst();

src/main/resources/handlebars/Java/interface.mustache

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
99
@JsonTypeInfo(
1010
use = JsonTypeInfo.Id.NAME,
1111
include = JsonTypeInfo.As.PROPERTY,
12-
property = "type")
12+
property = "{{discriminator.propertyName}}")
1313
@JsonSubTypes({
1414
{{#subTypes}}
15-
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{classname}}"){{^@last}},{{/@last}}
15+
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{subtypeName}}"){{^@last}},{{/@last}}
1616
{{/subTypes}}
1717
})
1818
{{/jackson}}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
{{#jackson}}
22
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{discriminator.propertyName}}", visible = true )
33
@JsonSubTypes({
4-
{{#if discriminator.mapping}}
5-
{{#each discriminator.mapping}}
6-
@JsonSubTypes.Type(value = {{this}}.class, name = "{{@key}}"),
7-
{{/each}}
8-
{{else}}
94
{{#children}}
10-
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{name}}"),
5+
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{subtypeName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"),
116
{{/children}}
12-
{{/if}}
137
})
14-
{{/jackson}}
8+
{{/jackson}}

src/test/java/io/swagger/codegen/v3/generators/GeneratorRunner.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.io.InputStream;
1414
import java.nio.file.Files;
1515
import java.util.List;
16+
import java.util.function.Consumer;
17+
import java.util.function.Supplier;
1618

1719
/**
1820
*
@@ -28,14 +30,22 @@ public static List<File> runGenerator(
2830
boolean v2Spec,
2931
boolean yaml,
3032
boolean flattenInlineComposedSchema,
31-
String outFolder
33+
String outFolder,
34+
Consumer<Options> optionsCustomizer
3235
) throws Exception {
3336

3437
String path = outFolder;
3538
if (StringUtils.isBlank(path)) {
3639
path = getTmpFolder().getAbsolutePath();
3740
}
3841
GenerationRequest request = new GenerationRequest();
42+
43+
Options option = new Options()
44+
.flattenInlineComposedSchema(flattenInlineComposedSchema)
45+
.outputDir(path);
46+
47+
optionsCustomizer.accept(option);
48+
3949
request
4050
.codegenVersion(codegenVersion) // use V2 to target Swagger/OpenAPI 2.x Codegen version
4151
.type(GenerationRequest.Type.CLIENT)
@@ -44,9 +54,7 @@ public static List<File> runGenerator(
4454
yaml, // YAML file, use false for JSON
4555
v2Spec)) // OpenAPI 3.x - use true for Swagger/OpenAPI 2.x definitions
4656
.options(
47-
new Options()
48-
.flattenInlineComposedSchema(flattenInlineComposedSchema)
49-
.outputDir(path)
57+
option
5058
);
5159

5260
List<File> files = new GeneratorService().generationRequest(request).generate();

src/test/java/io/swagger/codegen/v3/generators/java/GeneratorResultTestJava.java

+105-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package io.swagger.codegen.v3.generators.java;
22

3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
34
import io.swagger.codegen.v3.generators.GeneratorRunner;
45
import io.swagger.codegen.v3.service.GenerationRequest;
6+
import org.apache.commons.io.FileUtils;
57
import org.testng.Assert;
68
import org.testng.annotations.Test;
79

810
import java.io.File;
11+
import java.nio.file.Files;
12+
import java.nio.file.Paths;
913
import java.util.List;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
1016

1117
public class GeneratorResultTestJava {
1218

@@ -29,11 +35,109 @@ public void testJavaGenerator_OneOf() throws Exception {
2935
v2Spec,
3036
yaml,
3137
flattenInlineComposedSchema,
32-
outFolder);
38+
outFolder, options -> {});
3339

3440
Assert.assertFalse(files.isEmpty());
3541
for (File f: files) {
3642
// TODO test stuff
3743
}
3844
}
45+
46+
@Test
47+
public void interfaceWithCustomDiscriminator() throws Exception {
48+
49+
String name = "java";
50+
String specPath = "3_0_0/sample_interface_with_discriminator.json";
51+
GenerationRequest.CodegenVersion codegenVersion = GenerationRequest.CodegenVersion.V3;
52+
boolean v2Spec = false; // 3.0 spec
53+
boolean yaml = false;
54+
boolean flattenInlineComposedSchema = true;
55+
String outFolder = null; // temporary folder
56+
57+
File tmpFolder = GeneratorRunner.getTmpFolder();
58+
Assert.assertNotNull(tmpFolder);
59+
60+
List<File> files = GeneratorRunner.runGenerator(
61+
name,
62+
specPath,
63+
codegenVersion,
64+
v2Spec,
65+
yaml,
66+
flattenInlineComposedSchema,
67+
tmpFolder.getAbsolutePath(),
68+
options -> options.setLibrary("resttemplate"));
69+
70+
71+
File interfaceFile = files.stream().filter(f -> f.getName().equals("Item.java")).findAny().orElseThrow(() -> new RuntimeException("No interface generated"));
72+
73+
String interfaceContent = new String(Files.readAllBytes(Paths.get(interfaceFile.toURI())));
74+
75+
Pattern typeInfoPattern = Pattern.compile("(.*)(@JsonTypeInfo\\()(.*)(}\\))(.*)", Pattern.DOTALL);
76+
77+
Matcher matcher = typeInfoPattern.matcher(interfaceContent);
78+
79+
Assert.assertTrue(matcher.matches(),
80+
"No JsonTypeInfo generated into the interface file");
81+
82+
String generatedTypeInfoLines = matcher.group(2)+matcher.group(3)+matcher.group(4);
83+
84+
Assert.assertEquals( generatedTypeInfoLines, "@JsonTypeInfo(" + System.lineSeparator() +
85+
" use = JsonTypeInfo.Id.NAME," + System.lineSeparator() +
86+
" include = JsonTypeInfo.As.PROPERTY," + System.lineSeparator() +
87+
" property = \"aCustomProperty\")" + System.lineSeparator() +
88+
"@JsonSubTypes({" + System.lineSeparator() +
89+
" @JsonSubTypes.Type(value = ClassA.class, name = \"typeA\")," + System.lineSeparator() +
90+
" @JsonSubTypes.Type(value = ClassB.class, name = \"typeB\")," + System.lineSeparator() +
91+
" @JsonSubTypes.Type(value = ClassC.class, name = \"typeC\")" + System.lineSeparator() +
92+
"})", "Wrong json subtypes generated");
93+
94+
FileUtils.deleteDirectory(new File(tmpFolder.getAbsolutePath()));
95+
}
96+
97+
@Test
98+
public void javaCustomDiscriminator() throws Exception {
99+
100+
String name = "java";
101+
String specPath = "3_0_0/javaDiscriminatorExample.yaml";
102+
GenerationRequest.CodegenVersion codegenVersion = GenerationRequest.CodegenVersion.V3;
103+
boolean v2Spec = false; // 3.0 spec
104+
boolean yaml = true;
105+
boolean flattenInlineComposedSchema = true;
106+
String outFolder = null; // temporary folder
107+
108+
File tmpFolder = GeneratorRunner.getTmpFolder();
109+
Assert.assertNotNull(tmpFolder);
110+
111+
List<File> files = GeneratorRunner.runGenerator(
112+
name,
113+
specPath,
114+
codegenVersion,
115+
v2Spec,
116+
yaml,
117+
flattenInlineComposedSchema,
118+
tmpFolder.getAbsolutePath(),
119+
options -> options.setLibrary("resttemplate"));
120+
121+
122+
File interfaceFile = files.stream().filter(f -> f.getName().equals("ResultForSubTypeDTO.java")).findAny().orElseThrow(() -> new RuntimeException("No class generated"));
123+
124+
String interfaceContent = new String(Files.readAllBytes(Paths.get(interfaceFile.toURI())));
125+
126+
Pattern typeInfoPattern = Pattern.compile("(.*)(@JsonTypeInfo\\()(.*)(}\\))(.*)", Pattern.DOTALL);
127+
128+
Matcher matcher = typeInfoPattern.matcher(interfaceContent);
129+
130+
Assert.assertTrue(matcher.matches(),
131+
"No JsonTypeInfo generated into the interface file");
132+
133+
String generatedTypeInfoLines = matcher.group(2)+matcher.group(3)+matcher.group(4);
134+
135+
Assert.assertEquals( generatedTypeInfoLines, "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", visible = true )" + System.lineSeparator() +
136+
"@JsonSubTypes({" + System.lineSeparator() +
137+
" @JsonSubTypes.Type(value = SubTypeBResultDTO.class, name = \"WeirdlyNamedSubTypeB\")," + System.lineSeparator() +
138+
" @JsonSubTypes.Type(value = SubTypeAResultDTO.class, name = \"SubTypeA\")," + System.lineSeparator() +
139+
"})");
140+
141+
FileUtils.deleteDirectory(new File(tmpFolder.getAbsolutePath()));
142+
}
39143
}

src/test/java/io/swagger/codegen/v3/generators/java/JavaPolymorphicAnnotationCodegenTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ public void testParameterOrders() throws Exception {
3131
final String content = FileUtils.readFileToString(petControllerFile);
3232

3333
Assert.assertTrue(content.contains(
34-
"@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", visible = true )\n" +
35-
"@JsonSubTypes({\n" +
36-
" @JsonSubTypes.Type(value = Error.class, name = \"Error\"),\n" +
37-
" @JsonSubTypes.Type(value = Success.class, name = \"Success\"),\n" +
34+
"@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", visible = true )" + System.lineSeparator() +
35+
"@JsonSubTypes({" + System.lineSeparator() +
36+
" @JsonSubTypes.Type(value = Error.class, name = \"Error\")," + System.lineSeparator() +
37+
" @JsonSubTypes.Type(value = Success.class, name = \"Success\")," + System.lineSeparator() +
3838
"})"));
3939

4040
this.folder.delete();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
openapi: '3.0.3'
2+
info:
3+
title: 'Discriminator Problem API'
4+
version: '1.0.0'
5+
6+
components:
7+
schemas:
8+
SomeTypeDTO:
9+
type: string
10+
enum:
11+
- SubTypeA
12+
- WeirdlyNamedSubTypeB
13+
14+
ResultForSubTypeDTO:
15+
type: object
16+
properties:
17+
type:
18+
type: string
19+
oneOf:
20+
- $ref: '#/components/schemas/SubTypeAResultDTO'
21+
- $ref: '#/components/schemas/SubTypeBResultDTO'
22+
required:
23+
- type
24+
discriminator:
25+
propertyName: type
26+
mapping:
27+
SubTypeA: '#/components/schemas/SubTypeAResultDTO'
28+
WeirdlyNamedSubTypeB: '#/components/schemas/SubTypeBResultDTO'
29+
30+
SubTypeAResultDTO:
31+
allOf:
32+
- $ref: '#/components/schemas/ResultForSubTypeDTO'
33+
type: object
34+
properties:
35+
some_attribute:
36+
type: string
37+
38+
SubTypeBResultDTO:
39+
allOf:
40+
- $ref: '#/components/schemas/ResultForSubTypeDTO'
41+
type: object
42+
properties:
43+
another_attribute:
44+
type: string
45+
46+
paths:
47+
/repro:
48+
get:
49+
operationId: 'getRepo'
50+
responses:
51+
204:
52+
description: OK

0 commit comments

Comments
 (0)