Skip to content

Commit

Permalink
Merge pull request #1259 from swagger-api/OsztosA-feature/java-interf…
Browse files Browse the repository at this point in the history
…ace-discriminator

java interface discriminator mapping
  • Loading branch information
HugoMario authored Feb 15, 2024
2 parents 3fd9941 + 3029c17 commit 0f7eeb2
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,8 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.swagger.codegen.v3.CodegenConstants.HAS_ONLY_READ_ONLY_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.HAS_OPTIONAL_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.HAS_REQUIRED_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.IS_ARRAY_MODEL_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.IS_CONTAINER_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.IS_ENUM_EXT_NAME;
import static io.swagger.codegen.v3.generators.CodegenHelper.getDefaultIncludes;
import static io.swagger.codegen.v3.generators.CodegenHelper.getImportMappings;
import static io.swagger.codegen.v3.generators.CodegenHelper.getTypeMappings;
import static io.swagger.codegen.v3.generators.CodegenHelper.initalizeSpecialCharacterMapping;
import static io.swagger.codegen.v3.CodegenConstants.*;
import static io.swagger.codegen.v3.generators.CodegenHelper.*;
import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue;

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

codegenModel.discriminator = schema.getDiscriminator();
if (codegenModel.discriminator != null && codegenModel.discriminator.getPropertyName() != null) {
codegenModel.discriminator.setPropertyName(toVarName(codegenModel.discriminator.getPropertyName()));
}

if (schema.getXml() != null) {
codegenModel.xmlPrefix = schema.getXml().getPrefix();
Expand Down Expand Up @@ -1397,6 +1386,25 @@ else if (schema instanceof ComposedSchema) {
}
}
}
if (codegenModel.discriminator != null && codegenModel.discriminator.getPropertyName() != null) {
codegenModel.discriminator.setPropertyName(toVarName(codegenModel.discriminator.getPropertyName()));
Map<String, String> classnameKeys = new HashMap<>();

if (composed.getOneOf()!=null) {
composed.getOneOf().forEach( s -> {
codegenModel.discriminator.getMapping().keySet().stream().filter( key -> codegenModel.discriminator.getMapping().get(key).equals(s.get$ref()))
.forEach(key -> {
String mappingValue = codegenModel.discriminator.getMapping().get(key);
if (classnameKeys.containsKey(codegenModel.classname)) {
throw new IllegalArgumentException("Duplicate shema name in discriminator mapping");
}
classnameKeys.put(toModelName(mappingValue.replace("#/components/schemas/", "")),key);
});
});
codegenModel.discriminator.getMapping().putAll(classnameKeys);
}
}

} else {
allProperties = null;
allRequired = null;
Expand Down Expand Up @@ -4415,7 +4423,7 @@ protected void setParameterJson(CodegenParameter codegenParameter, Schema parame
codegenParameter.isJson = true;
}
}

protected boolean isFileTypeSchema(Schema schema) {
final Schema fileTypeSchema;
if (StringUtils.isNotBlank(schema.get$ref())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@ protected void addInterfaces(List<Schema> schemas, CodegenModel codegenModel, Ma
codegenModel.addSubType(model);
}

if (codegenModel.getVendorExtensions() == null || codegenModel.getVendorExtensions().containsKey("x-discriminator-type")) {
continue;
}

if (codegenModel.getDiscriminator() != null && StringUtils.isNotBlank(codegenModel.getDiscriminator().getPropertyName())) {
Optional<CodegenProperty> optionalProperty = model.vars.stream()
.filter(codegenProperty -> codegenProperty.baseName.equals(codegenModel.getDiscriminator().getPropertyName())).findFirst();
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/handlebars/Java/interface.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
property = "{{discriminator.propertyName}}")
@JsonSubTypes({
{{#subTypes}}
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{classname}}"){{^@last}},{{/@last}}
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{subtypeName}}"){{^@last}},{{/@last}}
{{/subTypes}}
})
{{/jackson}}
Expand Down
10 changes: 2 additions & 8 deletions src/main/resources/handlebars/Java/typeInfoAnnotation.mustache
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
{{#jackson}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{discriminator.propertyName}}", visible = true )
@JsonSubTypes({
{{#if discriminator.mapping}}
{{#each discriminator.mapping}}
@JsonSubTypes.Type(value = {{this}}.class, name = "{{@key}}"),
{{/each}}
{{else}}
{{#children}}
@JsonSubTypes.Type(value = {{classname}}.class, name = "{{name}}"),
@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}}"),
{{/children}}
{{/if}}
})
{{/jackson}}
{{/jackson}}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
*
Expand All @@ -28,14 +30,22 @@ public static List<File> runGenerator(
boolean v2Spec,
boolean yaml,
boolean flattenInlineComposedSchema,
String outFolder
String outFolder,
Consumer<Options> optionsCustomizer
) throws Exception {

String path = outFolder;
if (StringUtils.isBlank(path)) {
path = getTmpFolder().getAbsolutePath();
}
GenerationRequest request = new GenerationRequest();

Options option = new Options()
.flattenInlineComposedSchema(flattenInlineComposedSchema)
.outputDir(path);

optionsCustomizer.accept(option);

request
.codegenVersion(codegenVersion) // use V2 to target Swagger/OpenAPI 2.x Codegen version
.type(GenerationRequest.Type.CLIENT)
Expand All @@ -44,9 +54,7 @@ public static List<File> runGenerator(
yaml, // YAML file, use false for JSON
v2Spec)) // OpenAPI 3.x - use true for Swagger/OpenAPI 2.x definitions
.options(
new Options()
.flattenInlineComposedSchema(flattenInlineComposedSchema)
.outputDir(path)
option
);

List<File> files = new GeneratorService().generationRequest(request).generate();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package io.swagger.codegen.v3.generators.java;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import io.swagger.codegen.v3.generators.GeneratorRunner;
import io.swagger.codegen.v3.service.GenerationRequest;
import org.apache.commons.io.FileUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GeneratorResultTestJava {

Expand All @@ -29,11 +35,109 @@ public void testJavaGenerator_OneOf() throws Exception {
v2Spec,
yaml,
flattenInlineComposedSchema,
outFolder);
outFolder, options -> {});

Assert.assertFalse(files.isEmpty());
for (File f: files) {
// TODO test stuff
}
}

@Test
public void interfaceWithCustomDiscriminator() throws Exception {

String name = "java";
String specPath = "3_0_0/sample_interface_with_discriminator.json";
GenerationRequest.CodegenVersion codegenVersion = GenerationRequest.CodegenVersion.V3;
boolean v2Spec = false; // 3.0 spec
boolean yaml = false;
boolean flattenInlineComposedSchema = true;
String outFolder = null; // temporary folder

File tmpFolder = GeneratorRunner.getTmpFolder();
Assert.assertNotNull(tmpFolder);

List<File> files = GeneratorRunner.runGenerator(
name,
specPath,
codegenVersion,
v2Spec,
yaml,
flattenInlineComposedSchema,
tmpFolder.getAbsolutePath(),
options -> options.setLibrary("resttemplate"));


File interfaceFile = files.stream().filter(f -> f.getName().equals("Item.java")).findAny().orElseThrow(() -> new RuntimeException("No interface generated"));

String interfaceContent = new String(Files.readAllBytes(Paths.get(interfaceFile.toURI())));

Pattern typeInfoPattern = Pattern.compile("(.*)(@JsonTypeInfo\\()(.*)(}\\))(.*)", Pattern.DOTALL);

Matcher matcher = typeInfoPattern.matcher(interfaceContent);

Assert.assertTrue(matcher.matches(),
"No JsonTypeInfo generated into the interface file");

String generatedTypeInfoLines = matcher.group(2)+matcher.group(3)+matcher.group(4);

Assert.assertEquals( generatedTypeInfoLines, "@JsonTypeInfo(" + System.lineSeparator() +
" use = JsonTypeInfo.Id.NAME," + System.lineSeparator() +
" include = JsonTypeInfo.As.PROPERTY," + System.lineSeparator() +
" property = \"aCustomProperty\")" + System.lineSeparator() +
"@JsonSubTypes({" + System.lineSeparator() +
" @JsonSubTypes.Type(value = ClassA.class, name = \"typeA\")," + System.lineSeparator() +
" @JsonSubTypes.Type(value = ClassB.class, name = \"typeB\")," + System.lineSeparator() +
" @JsonSubTypes.Type(value = ClassC.class, name = \"typeC\")" + System.lineSeparator() +
"})", "Wrong json subtypes generated");

FileUtils.deleteDirectory(new File(tmpFolder.getAbsolutePath()));
}

@Test
public void javaCustomDiscriminator() throws Exception {

String name = "java";
String specPath = "3_0_0/javaDiscriminatorExample.yaml";
GenerationRequest.CodegenVersion codegenVersion = GenerationRequest.CodegenVersion.V3;
boolean v2Spec = false; // 3.0 spec
boolean yaml = true;
boolean flattenInlineComposedSchema = true;
String outFolder = null; // temporary folder

File tmpFolder = GeneratorRunner.getTmpFolder();
Assert.assertNotNull(tmpFolder);

List<File> files = GeneratorRunner.runGenerator(
name,
specPath,
codegenVersion,
v2Spec,
yaml,
flattenInlineComposedSchema,
tmpFolder.getAbsolutePath(),
options -> options.setLibrary("resttemplate"));


File interfaceFile = files.stream().filter(f -> f.getName().equals("ResultForSubTypeDTO.java")).findAny().orElseThrow(() -> new RuntimeException("No class generated"));

String interfaceContent = new String(Files.readAllBytes(Paths.get(interfaceFile.toURI())));

Pattern typeInfoPattern = Pattern.compile("(.*)(@JsonTypeInfo\\()(.*)(}\\))(.*)", Pattern.DOTALL);

Matcher matcher = typeInfoPattern.matcher(interfaceContent);

Assert.assertTrue(matcher.matches(),
"No JsonTypeInfo generated into the interface file");

String generatedTypeInfoLines = matcher.group(2)+matcher.group(3)+matcher.group(4);

Assert.assertEquals( generatedTypeInfoLines, "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", visible = true )" + System.lineSeparator() +
"@JsonSubTypes({" + System.lineSeparator() +
" @JsonSubTypes.Type(value = SubTypeBResultDTO.class, name = \"WeirdlyNamedSubTypeB\")," + System.lineSeparator() +
" @JsonSubTypes.Type(value = SubTypeAResultDTO.class, name = \"SubTypeA\")," + System.lineSeparator() +
"})");

FileUtils.deleteDirectory(new File(tmpFolder.getAbsolutePath()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public void testParameterOrders() throws Exception {
final String content = FileUtils.readFileToString(petControllerFile);

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

this.folder.delete();
Expand Down
52 changes: 52 additions & 0 deletions src/test/resources/3_0_0/javaDiscriminatorExample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
openapi: '3.0.3'
info:
title: 'Discriminator Problem API'
version: '1.0.0'

components:
schemas:
SomeTypeDTO:
type: string
enum:
- SubTypeA
- WeirdlyNamedSubTypeB

ResultForSubTypeDTO:
type: object
properties:
type:
type: string
oneOf:
- $ref: '#/components/schemas/SubTypeAResultDTO'
- $ref: '#/components/schemas/SubTypeBResultDTO'
required:
- type
discriminator:
propertyName: type
mapping:
SubTypeA: '#/components/schemas/SubTypeAResultDTO'
WeirdlyNamedSubTypeB: '#/components/schemas/SubTypeBResultDTO'

SubTypeAResultDTO:
allOf:
- $ref: '#/components/schemas/ResultForSubTypeDTO'
type: object
properties:
some_attribute:
type: string

SubTypeBResultDTO:
allOf:
- $ref: '#/components/schemas/ResultForSubTypeDTO'
type: object
properties:
another_attribute:
type: string

paths:
/repro:
get:
operationId: 'getRepo'
responses:
204:
description: OK
Loading

0 comments on commit 0f7eeb2

Please sign in to comment.