Java importmapping 3589 (#4350)

* type aliasing issue

* Add example OpenAPI document from issue 3589

https://github.com/OpenAPITools/openapi-generator/issues/3589

* Add test to reproduce the issue

- type of TypeAlias changed from 'string' to 'object'
  (not sure if importMapping is supposed also for 'string' types...)
- there might be better ways to write the test, it's kind of a brute
  force test (generate a file and parse it with a regexp)

* Remove duplicate test file

* Add new method override handleMethodResponse

Fixes broken unit test after merge from master

Co-authored-by: bkoziak <bkoziak@gmail.com>
Co-authored-by: William Cheng <wing328hk@gmail.com>
This commit is contained in:
Peter Steiner 2020-02-13 15:05:53 +01:00 committed by GitHub
parent f5265853ba
commit eecd8c056a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 62 deletions

View File

@ -641,7 +641,7 @@ public class CodeGenMojo extends AbstractMojo {
configurator);
}
// Retained for backwards-compataibility with configOptions -> import-mappings
// Retained for backwards-compatibility with configOptions -> import-mappings
if (importMappings == null && configOptions.containsKey("import-mappings")) {
applyImportMappingsKvp(configOptions.get("import-mappings").toString(),
configurator);
@ -869,6 +869,7 @@ public class CodeGenMojo extends AbstractMojo {
}
}
}
/**
* This method enables conversion of true/false strings in
* config.additionalProperties (configuration/configOptions) to proper booleans.

View File

@ -1798,12 +1798,15 @@ public class DefaultCodegen implements CodegenConfig {
* @return the string representation of the schema type.
*/
private String getSingleSchemaType(Schema schema) {
Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema);
Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping);
if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema
// get the schema/model name from $ref
String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
if (StringUtils.isNotEmpty(schemaName)) {
if (importMapping.containsKey(schemaName)) {
return schemaName;
}
return getAlias(schemaName);
} else {
LOGGER.warn("Error obtaining the datatype from ref:" + unaliasSchema.get$ref() + ". Default to 'object'");
@ -1903,7 +1906,6 @@ public class DefaultCodegen implements CodegenConfig {
return (name.length() > 0) ? (Character.toLowerCase(name.charAt(0)) + name.substring(1)) : "";
}
/**
* Output the type declaration of a given name
*
@ -2021,7 +2023,7 @@ public class DefaultCodegen implements CodegenConfig {
}
// unalias schema
schema = ModelUtils.unaliasSchema(this.openAPI, schema);
schema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping);
if (schema == null) {
LOGGER.warn("Schema {} not found", name);
return null;
@ -2372,7 +2374,7 @@ public class DefaultCodegen implements CodegenConfig {
LOGGER.debug("debugging fromProperty for " + name + " : " + p);
// unalias schema
p = ModelUtils.unaliasSchema(this.openAPI, p);
p = ModelUtils.unaliasSchema(this.openAPI, p, importMapping);
CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY);
@ -2526,12 +2528,13 @@ public class DefaultCodegen implements CodegenConfig {
} else if (ModelUtils.isArraySchema(p)) {
// default to string if inner item is undefined
ArraySchema arraySchema = (ArraySchema) p;
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema));
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema), importMapping);
if (arraySchema.getItems() == null) {
arraySchema.setItems(innerSchema);
}
} else if (ModelUtils.isMapSchema(p)) {
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p));
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p),
importMapping);
if (innerSchema == null) {
LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName());
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
@ -2607,7 +2610,7 @@ public class DefaultCodegen implements CodegenConfig {
itemName = property.name;
}
ArraySchema arraySchema = (ArraySchema) p;
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema));
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema), importMapping);
if (arraySchema.getItems() == null) {
arraySchema.setItems(innerSchema);
}
@ -2622,7 +2625,8 @@ public class DefaultCodegen implements CodegenConfig {
property.maxItems = p.getMaxProperties();
// handle inner property
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p));
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p),
importMapping);
if (innerSchema == null) {
LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName());
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
@ -2841,7 +2845,24 @@ public class DefaultCodegen implements CodegenConfig {
Map<String, Schema> schemas,
CodegenOperation op,
ApiResponse methodResponse) {
Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse));
handleMethodResponse(operation, schemas, op, methodResponse, Collections.<String, String>emptyMap());
}
/**
* Set op's returnBaseType, returnType, examples etc.
*
* @param operation endpoint Operation
* @param schemas a map of the schemas in the openapi spec
* @param op endpoint CodegenOperation
* @param methodResponse the default ApiResponse for the endpoint
* @param importMappings mappings of external types to be omitted by unaliasing
*/
protected void handleMethodResponse(Operation operation,
Map<String, Schema> schemas,
CodegenOperation op,
ApiResponse methodResponse,
Map<String, String> importMappings) {
Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse), importMappings);
if (responseSchema != null) {
CodegenProperty cm = fromProperty("response", responseSchema);
@ -2996,7 +3017,7 @@ public class DefaultCodegen implements CodegenConfig {
op.responses.get(op.responses.size() - 1).hasMore = false;
if (methodResponse != null) {
handleMethodResponse(operation, schemas, op, methodResponse);
handleMethodResponse(operation, schemas, op, methodResponse, importMapping);
}
}
@ -3130,9 +3151,12 @@ public class DefaultCodegen implements CodegenConfig {
Collections.sort(allParams, new Comparator<CodegenParameter>() {
@Override
public int compare(CodegenParameter one, CodegenParameter another) {
if (one.required == another.required) return 0;
else if (one.required) return -1;
else return 1;
if (one.required == another.required)
return 0;
else if (one.required)
return -1;
else
return 1;
}
});
}
@ -3211,7 +3235,8 @@ public class DefaultCodegen implements CodegenConfig {
}
Schema responseSchema;
if (this.openAPI != null && this.openAPI.getComponents() != null) {
responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response));
responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response),
importMapping);
} else { // no model/alias defined
responseSchema = ModelUtils.getSchemaFromResponse(response);
}
@ -3443,7 +3468,7 @@ public class DefaultCodegen implements CodegenConfig {
}
if (s != null) {
Schema parameterSchema = ModelUtils.unaliasSchema(this.openAPI, s);
Schema parameterSchema = ModelUtils.unaliasSchema(this.openAPI, s, importMapping);
if (parameterSchema == null) {
LOGGER.warn("warning! Schema not found for parameter \"" + parameter.getName() + "\", using String");
parameterSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to missing type definition.");
@ -4047,7 +4072,7 @@ public class DefaultCodegen implements CodegenConfig {
private Map<String, Schema> unaliasPropertySchema(Map<String, Schema> properties) {
if (properties != null) {
for (String key : properties.keySet()) {
properties.put(key, ModelUtils.unaliasSchema(this.openAPI, properties.get(key)));
properties.put(key, ModelUtils.unaliasSchema(this.openAPI, properties.get(key), importMapping()));
}
}
@ -5422,7 +5447,6 @@ public class DefaultCodegen implements CodegenConfig {
codegenParameter.maxLength = codegenProperty.maxLength;
codegenParameter.pattern = codegenProperty.pattern;
if (codegenProperty.complexType != null) {
imports.add(codegenProperty.complexType);
}

View File

@ -261,7 +261,7 @@ public class CppRestSdkClientCodegen extends AbstractCppCodegen {
if (methodResponse != null) {
Schema response = ModelUtils.getSchemaFromResponse(methodResponse);
response = ModelUtils.unaliasSchema(this.openAPI, response);
response = ModelUtils.unaliasSchema(this.openAPI, response, importMapping);
if (response != null) {
CodegenProperty cm = fromProperty("response", response);
op.vendorExtensions.put("x-codegen-response", cm);

View File

@ -529,7 +529,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
// When we serialize/deserialize ModelSimple models, validations and enums will be checked.
Schema responseSchema;
if (this.openAPI != null && this.openAPI.getComponents() != null) {
responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response));
responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response), importMapping);
} else { // no model/alias defined
responseSchema = ModelUtils.getSchemaFromResponse(response);
}
@ -582,12 +582,30 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
Map<String, Schema> schemas,
CodegenOperation op,
ApiResponse methodResponse) {
handleMethodResponse(operation, schemas, op, methodResponse, Collections.<String, String>emptyMap());
}
/**
* Set op's returnBaseType, returnType, examples etc.
*
* @param operation endpoint Operation
* @param schemas a map of the schemas in the openapi spec
* @param op endpoint CodegenOperation
* @param methodResponse the default ApiResponse for the endpoint
* @param importMappings mappings of external types to be omitted by unaliasing
*/
@Override
protected void handleMethodResponse(Operation operation,
Map<String, Schema> schemas,
CodegenOperation op,
ApiResponse methodResponse,
Map<String, String> importMappings) {
// we have a custom version of this method to handle endpoints that return models where
// type != object the model has validations and/or enums
// we do this by invoking our custom fromResponse method to create defaultResponse
// which we then use to set op.returnType and op.returnBaseType
CodegenResponse defaultResponse = fromResponse("defaultResponse", methodResponse);
Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse));
Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse), importMappings);
if (responseSchema != null) {
op.returnBaseType = defaultResponse.baseType;

View File

@ -101,7 +101,16 @@ public class TypeScriptNodeClientCodegen extends AbstractTypeScriptClientCodegen
@Override
protected void handleMethodResponse(Operation operation, Map<String, Schema> schemas, CodegenOperation op,
ApiResponse methodResponse) {
super.handleMethodResponse(operation, schemas, op, methodResponse);
handleMethodResponse(operation, schemas, op, methodResponse, Collections.<String, String>emptyMap());
}
@Override
protected void handleMethodResponse(Operation operation,
Map<String, Schema> schemas,
CodegenOperation op,
ApiResponse methodResponse,
Map<String, String> importMappings) {
super.handleMethodResponse(operation, schemas, op, methodResponse, importMappings);
// see comment in getTypeDeclaration
if (op.isResponseFile) {

View File

@ -47,6 +47,7 @@ public class ModelUtils {
private static final String URI_FORMAT = "uri";
private static final String generateAliasAsModelKey = "generateAliasAsModel";
public static void setGenerateAliasAsModel(boolean value) {
GlobalSettings.setProperty(generateAliasAsModelKey, Boolean.toString(value));
}
@ -240,7 +241,7 @@ public class ModelUtils {
}
private static void visitParameters(OpenAPI openAPI, List<Parameter> parameters, OpenAPISchemaVisitor visitor,
List<String> visitedSchemas) {
List<String> visitedSchemas) {
if (parameters != null) {
for (Parameter p : parameters) {
Parameter parameter = getReferencedParameter(openAPI, p);
@ -870,7 +871,22 @@ public class ModelUtils {
* @param schema schema (alias or direct reference)
* @return actual schema
*/
public static Schema unaliasSchema(OpenAPI openAPI, Schema schema) {
public static Schema unaliasSchema(OpenAPI openAPI,
Schema schema) {
return unaliasSchema(openAPI, schema, Collections.<String, String>emptyMap());
}
/**
* Get the actual schema from aliases. If the provided schema is not an alias, the schema itself will be returned.
*
* @param openAPI specification being checked
* @param schema schema (alias or direct reference)
* @param importMappings mappings of external types to be omitted by unaliasing
* @return actual schema
*/
public static Schema unaliasSchema(OpenAPI openAPI,
Schema schema,
Map<String, String> importMappings) {
Map<String, Schema> allSchemas = getSchemas(openAPI);
if (allSchemas == null || allSchemas.isEmpty()) {
// skip the warning as the spec can have no model defined
@ -879,7 +895,12 @@ public class ModelUtils {
}
if (schema != null && StringUtils.isNotEmpty(schema.get$ref())) {
Schema ref = allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref()));
String simpleRef = ModelUtils.getSimpleRef(schema.get$ref());
if (importMappings.containsKey(simpleRef)) {
LOGGER.info("Schema unaliasing of {} omitted because aliased class is to be mapped to {}", simpleRef, importMappings.get(simpleRef));
return schema;
}
Schema ref = allSchemas.get(simpleRef);
if (ref == null) {
once(LOGGER).warn("{} is not defined", schema.get$ref());
return schema;
@ -890,7 +911,8 @@ public class ModelUtils {
if (isGenerateAliasAsModel()) {
return schema; // generate a model extending array
} else {
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())),
importMappings);
}
} else if (isComposedSchema(ref)) {
return schema;
@ -902,17 +924,19 @@ public class ModelUtils {
return schema; // generate a model extending map
} else {
// treat it as a typical map
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())),
importMappings);
}
}
} else if (isObjectSchema(ref)) { // model
if (ref.getProperties() != null && !ref.getProperties().isEmpty()) { // has at least one property
return schema;
} else { // free form object (type: object)
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())),
importMappings);
}
} else {
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())), importMappings);
}
}
return schema;

View File

@ -1073,6 +1073,21 @@ public class DefaultCodegenTest {
Assert.assertEquals(codegenModel.vars.size(), 1);
}
@Test
public void importMapping() {
DefaultCodegen codegen = new DefaultCodegen();
codegen.importMapping.put("TypeAlias", "foo.bar.TypeAlias");
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/type-alias.yaml", null, new ParseOptions()).getOpenAPI();
codegen.setOpenAPI(openAPI);
CodegenModel codegenModel = codegen.fromModel("ParentType", openAPI.getComponents().getSchemas().get("ParentType"));
Assert.assertEquals(codegenModel.vars.size(), 1);
Assert.assertEquals(codegenModel.vars.get(0).getBaseType(), "TypeAlias");
}
@Test
public void modelWithPrefixDoNotContainInheritedVars() {
DefaultCodegen codegen = new DefaultCodegen();

View File

@ -17,38 +17,16 @@
package org.openapitools.codegen.java;
import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.CodegenSecurity;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.MockDefaultGenerator;
import org.openapitools.codegen.*;
import org.openapitools.codegen.MockDefaultGenerator.WrittenTemplateBasedFile;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.AbstractJavaCodegen;
import org.openapitools.codegen.languages.JavaClientCodegen;
@ -56,16 +34,17 @@ import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class JavaClientCodegenTest {
@Test
@ -544,11 +523,60 @@ public class JavaClientCodegenTest {
Assert.assertEquals(cm.getClassname(), "OtherObj");
}
/**
* See https://github.com/OpenAPITools/openapi-generator/issues/3589
*/
@Test
public void testImportMapping() throws IOException {
Map<String, Object> properties = new HashMap<>();
properties.put(JavaClientCodegen.JAVA8_MODE, true);
properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
Map<String, String> importMappings = new HashMap<>();
importMappings.put("TypeAlias", "foo.bar.TypeAlias");
File output = Files.createTempDirectory("test").toFile();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("java")
.setLibrary(JavaClientCodegen.RESTEASY)
.setAdditionalProperties(properties)
.setImportMappings(importMappings)
.setInputSpec("src/test/resources/3_0/type-alias.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
final ClientOptInput clientOptInput = configurator.toClientOptInput();
Assert.assertEquals(clientOptInput.getConfig().importMapping().get("TypeAlias"), "foo.bar.TypeAlias");
MockDefaultGenerator generator = new MockDefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
generator.opts(clientOptInput).generate();
Map<String, String> generatedFiles = generator.getFiles();
Assert.assertEquals(generatedFiles.size(), 1);
TestUtils.ensureContainsFile(generatedFiles, output, "src/main/java/org/openapitools/client/model/ParentType.java");
final String parentTypeContents = generatedFiles.values().iterator().next();
final Pattern FIELD_PATTERN = Pattern.compile(".* private (.*?) typeAlias;.*", Pattern.DOTALL);
Matcher fieldMatcher = FIELD_PATTERN.matcher(parentTypeContents);
Assert.assertTrue(fieldMatcher.matches());
// this is the type of the field 'typeAlias'. With a working importMapping it should
// be 'foo.bar.TypeAlias' or just 'TypeAlias'
Assert.assertEquals(fieldMatcher.group(1), "foo.bar.TypeAlias");
}
@Test
public void testBearerAuth() {
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/pingBearerAuth.yaml");
JavaClientCodegen codegen = new JavaClientCodegen();
List<CodegenSecurity> security = codegen.fromSecurity(openAPI.getComponents().getSecuritySchemes());
Assert.assertEquals(security.size(), 1);
Assert.assertEquals(security.get(0).isBasic, Boolean.TRUE);

View File

@ -205,7 +205,20 @@ public class ModelUtilsTest {
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("SomeComposedSchema", composedSchema);
Assert.assertEquals(refToComposedSchema, ModelUtils.unaliasSchema(openAPI, refToComposedSchema));
Assert.assertEquals(refToComposedSchema, ModelUtils.unaliasSchema(openAPI, refToComposedSchema, new HashMap<>()));
}
@Test
public void testAliasedTypeIsNotUnaliasedIfUsedForImportMapping(){
Schema emailSchema = new Schema().$ref("#/components/schemas/Email").type("string");
StringSchema stringSchema = new StringSchema();
HashMap<String, String> importMappings = new HashMap<>();
importMappings.put("Email","foo.bar.Email");
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Email", stringSchema);
Assert.assertEquals(emailSchema, ModelUtils.unaliasSchema(openAPI, emailSchema, importMappings));
Assert.assertEquals(stringSchema, ModelUtils.unaliasSchema(openAPI, emailSchema, new HashMap<>()));
}
/**

View File

@ -0,0 +1,26 @@
openapi: 3.0.0
info:
title: API using a typeAlias mapped with importMapping
description: See https://github.com/OpenAPITools/openapi-generator/issues/3589
version: 1.0.0
paths:
/type-alias:
get:
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ParentType'
components:
schemas:
ParentType:
title: ParentType
type: object
properties:
typeAlias:
$ref: '#/components/schemas/TypeAlias'
TypeAlias:
type: string