Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java interface discriminator mapping #1259

Merged
merged 8 commits into from
Feb 15, 2024
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 @@ -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
Loading