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

Fix oneOf interfaces for Java projects #1

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion modules/openapi-generator-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-project</artifactId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
2 changes: 1 addition & 1 deletion modules/openapi-generator-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<artifactId>openapi-generator-project</artifactId>
<groupId>org.openapitools</groupId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
2 changes: 1 addition & 1 deletion modules/openapi-generator-gradle-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-project</artifactId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
2 changes: 1 addition & 1 deletion modules/openapi-generator-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-project</artifactId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
2 changes: 1 addition & 1 deletion modules/openapi-generator-online/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-project</artifactId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
2 changes: 1 addition & 1 deletion modules/openapi-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-project</artifactId>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<relativePath>../..</relativePath>
</parent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,26 +804,18 @@ public void preprocessOpenAPI(OpenAPI openAPI) {

// go through all gathered schemas and add them as interfaces to be created
for (Map.Entry<String, Schema> e : schemas.entrySet()) {
String n = toModelName(e.getKey());
Schema s = e.getValue();
String nOneOf = toModelName(n + "OneOf");
if (ModelUtils.isComposedSchema(s)) {
if (e.getKey().contains("/")) {
// if this is property schema, we also need to generate the oneOf interface model
addOneOfNameExtension((ComposedSchema) s, nOneOf);
addOneOfInterfaceModel((ComposedSchema) s, nOneOf, openAPI);
} else {
// else this is a component schema, so we will just use that as the oneOf interface model
addOneOfNameExtension((ComposedSchema) s, n);
}
} else if (ModelUtils.isArraySchema(s)) {
Schema items = ((ArraySchema) s).getItems();
String modelName = toModelName(e.getKey());
Schema schema = e.getValue();
String nOneOf = toModelName(modelName + "OneOf");
if (ModelUtils.isComposedSchema(schema)) {
addOneOfForComposedSchema(e, modelName, (ComposedSchema) schema, nOneOf, openAPI);
} else if (ModelUtils.isArraySchema(schema)) {
Schema items = ((ArraySchema) schema).getItems();
if (ModelUtils.isComposedSchema(items)) {
addOneOfNameExtension((ComposedSchema) items, nOneOf);
addOneOfInterfaceModel((ComposedSchema) items, nOneOf, openAPI);
addOneOfForComposedSchemaArray(nOneOf, modelName, (ComposedSchema) items, openAPI);
}
} else if (ModelUtils.isMapSchema(s)) {
Schema addProps = getAdditionalProperties(s);
} else if (ModelUtils.isMapSchema(schema)) {
Schema addProps = getAdditionalProperties(schema);
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
addOneOfNameExtension((ComposedSchema) addProps, nOneOf);
addOneOfInterfaceModel((ComposedSchema) addProps, nOneOf, openAPI);
Expand All @@ -833,6 +825,23 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
}

protected void addOneOfForComposedSchemaArray(String nOneOf, String modelName, ComposedSchema items, OpenAPI openAPI) {
addOneOfNameExtension(items, nOneOf);
addOneOfInterfaceModel(items, nOneOf, openAPI);
}

protected void addOneOfForComposedSchema(Entry<String, Schema> stringSchemaEntry, String modelName, ComposedSchema composedSchema,
String nOneOf, OpenAPI openAPI) {
if (stringSchemaEntry.getKey().contains("/")) {
// if this is property schema, we also need to generate the oneOf interface model
addOneOfNameExtension(composedSchema, nOneOf);
addOneOfInterfaceModel(composedSchema, nOneOf, openAPI);
} else {
// else this is a component schema, so we will just use that as the oneOf interface model
addOneOfNameExtension(composedSchema, modelName);
}
}

// override with any special handling of the entire OpenAPI spec document
@SuppressWarnings("unused")
public void processOpenAPI(OpenAPI openAPI) {
Expand Down Expand Up @@ -4610,13 +4619,13 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
* of the 'additionalProperties' keyword. Some language generator use class inheritance
* to implement additional properties. For example, in Java the generated model class
* has 'extends HashMap' to represent the additional properties.
*
*
* TODO: it's not a good idea to use single class inheritance to implement
* additionalProperties. That may work for non-composed schemas, but that does not
* work for composed 'allOf' schemas. For example, in Java, if additionalProperties
* is set to true (which it should be by default, per OAS spec), then the generated
* code has extends HashMap. That wouldn't work for composed 'allOf' schemas.
*
*
* @param model the codegen representation of the OAS schema.
* @param name the name of the model.
* @param schema the input OAS schema.
Expand Down Expand Up @@ -5847,7 +5856,7 @@ private void addBodyModelSchema(CodegenParameter codegenParameter, String name,
"'application/x-www-form-urlencoded' or 'multipart/?'");
LOGGER.warn("schema: " + schema);
LOGGER.warn("codegenModel is null. Default to UNKNOWN_BASE_TYPE");
codegenModelName = "UNKNOWN_BASE_TYPE";
codegenModelName = getCodegenModelName(codegenProperty);
codegenModelDescription = "UNKNOWN_DESCRIPTION";
}

Expand Down Expand Up @@ -6062,6 +6071,10 @@ private void addJsonSchemaForBodyRequestInCaseItsNotPresent(CodegenParameter cod
codegenParameter.jsonSchema = Json.pretty(body);
}

protected String getCodegenModelName(CodegenProperty codegenProperty) {
return "UNKNOWN_BASE_TYPE";
}

protected void addOption(String key, String description, String defaultValue) {
CliOption option = new CliOption(key, description);
if (defaultValue != null)
Expand Down Expand Up @@ -6471,15 +6484,15 @@ protected boolean isFreeFormObject(Schema schema) {

/**
* Returns the additionalProperties Schema for the specified input schema.
*
*
* The additionalProperties keyword is used to control the handling of additional, undeclared
* properties, that is, properties whose names are not listed in the properties keyword.
* The additionalProperties keyword may be either a boolean or an object.
* If additionalProperties is a boolean and set to false, no additional properties are allowed.
* By default when the additionalProperties keyword is not specified in the input schema,
* any additional properties are allowed. This is equivalent to setting additionalProperties
* to the boolean value True or setting additionalProperties: {}
*
*
* @param schema the input schema that may or may not have the additionalProperties keyword.
* @return the Schema of the additionalProperties. The null value is returned if no additional
* properties are allowed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
public static final String BOOLEAN_GETTER_PREFIX = "booleanGetterPrefix";
public static final String ADDITIONAL_MODEL_TYPE_ANNOTATIONS = "additionalModelTypeAnnotations";
public static final String DISCRIMINATOR_CASE_SENSITIVE = "discriminatorCaseSensitive";
public static final String USE_ONEOF_INTERFACES = "useOneOfInterfaces";

protected String dateLibrary = "threetenbp";
protected boolean supportAsync = false;
Expand Down Expand Up @@ -197,6 +198,7 @@ public AbstractJavaCodegen() {
cliOptions.add(CliOption.newBoolean(DISCRIMINATOR_CASE_SENSITIVE, "Whether the discriminator value lookup should be case-sensitive or not. This option only works for Java API client", discriminatorCaseSensitive));
cliOptions.add(CliOption.newBoolean(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC, this.isHideGenerationTimestamp()));
cliOptions.add(CliOption.newBoolean(WITH_XML, "whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)"));
cliOptions.add(CliOption.newBoolean(USE_ONEOF_INTERFACES, "Generate interfaces for OneOf types"));

CliOption dateLibrary = new CliOption(DATE_LIBRARY, "Option. Date library to use").defaultValue(this.getDateLibrary());
Map<String, String> dateOptions = new HashMap<>();
Expand Down Expand Up @@ -540,6 +542,11 @@ public void processOpts() {
} else if (dateLibrary.equals("legacy")) {
additionalProperties.put("legacyDates", "true");
}

if (additionalProperties.containsKey(USE_ONEOF_INTERFACES)) {
useOneOfInterfaces = Boolean.parseBoolean(additionalProperties.get(USE_ONEOF_INTERFACES).toString());
addOneOfInterfaceImports = useOneOfInterfaces;
}
}

@Override
Expand Down Expand Up @@ -1651,7 +1658,7 @@ public void setAdditionalModelTypeAnnotations(final List<String> additionalModel
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
if (!supportsAdditionalPropertiesWithComposedSchema) {
// The additional (undeclared) propertiees are modeled in Java as a HashMap.
//
//
// 1. supportsAdditionalPropertiesWithComposedSchema is set to false:
// The generated model class extends from the HashMap. That does not work
// with composed schemas that also use a discriminator because the model class
Expand All @@ -1669,4 +1676,36 @@ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Sc
addImport(codegenModel, codegenModel.additionalPropertiesType);
}
}

@Override
protected String getCodegenModelName(CodegenProperty codegenProperty) {
return codegenProperty.getComplexType();
}

@Override
protected void addOneOfForComposedSchema(Map.Entry<String, Schema> stringSchemaEntry, String modelName,
ComposedSchema composedSchema, String nOneOf, OpenAPI openAPI) {
// if this is property schema, we also need to generate the oneOf interface model
addOneOfNameExtension(composedSchema, modelName);
addOneOfInterfaceModel(composedSchema, modelName, openAPI);
}

@Override
protected void addOneOfForComposedSchemaArray(String nOneOf, String modelName, ComposedSchema items, OpenAPI openAPI) {
addOneOfNameExtension(items, modelName);
addOneOfInterfaceModel(items, modelName, openAPI);
}

@Override
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
Map<String, String> oneImport = new HashMap<String, String>() {{
put("import", importMapping.get(i));
}};
if (!imports.contains(oneImport)) {
imports.add(oneImport);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.tuple.Pair;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ import org.springframework.hateoas.RepresentationModel;

{{#models}}
{{#model}}
{{#isEnum}}
{{>enumOuterClass}}
{{/isEnum}}
{{^isEnum}}
{{>pojo}}
{{/isEnum}}
{{#isEnum}}{{>enumOuterClass}}{{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}{{/isEnum}}
{{/model}}
{{/models}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}}
public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/{{#description}}
@ApiModel(description = "{{{description}}}"){{/description}}
{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}{{>additionalModelTypeAnnotations}}
public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#serializableModel}}implements Serializable{{/serializableModel}} {
public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}>{{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{{^vendorExtensions.x-implements}}{{#serializableModel}} implements Serializable{{/serializableModel}}{{/vendorExtensions.x-implements}} {
{{#serializableModel}}
private static final long serialVersionUID = 1L;

Expand All @@ -14,7 +14,7 @@ public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}
{{>enumClass}}
{{/isContainer}}
{{#isContainer}}
{{#mostInnerItems}}
{{#mostInnerItems}}
{{>enumClass}}
{{/mostInnerItems}}
{{/isContainer}}
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<packaging>pom</packaging>
<name>openapi-generator-project</name>
<!-- RELEASE_VERSION -->
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0-SYM</version>
<!-- /RELEASE_VERSION -->
<url>https://github.com/openapitools/openapi-generator</url>
<scm>
Expand Down