Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ public void postProcessParameter(CodegenParameter parameter) {
@Override
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
if (useOneOfInterfaces) {
if (useOneOfInterfaces && openAPI.getComponents() != null) {
// we process the openapi schema here to find oneOf schemas and create interface models for them
Map<String, Schema> schemas = new HashMap<String, Schema>(openAPI.getComponents().getSchemas());
if (schemas == null) {
Expand Down Expand Up @@ -2418,16 +2418,35 @@ public CodegenModel fromModel(String name, Schema schema) {
m.classFilename = toModelFilename(name);
m.modelJson = Json.pretty(schema);
m.externalDocumentation = schema.getExternalDocs();
if (schema.getExtensions() != null && !schema.getExtensions().isEmpty()) {
m.getVendorExtensions().putAll(schema.getExtensions());
}
m.isAlias = (typeAliases.containsKey(name)
|| isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types
m.setDiscriminator(createDiscriminator(name, schema, this.openAPI));
if (!this.getLegacyDiscriminatorBehavior()) {
m.addDiscriminatorMappedModelsImports();
}

if (schema.getExtensions() != null && !schema.getExtensions().isEmpty()) {
m.getVendorExtensions().putAll(schema.getExtensions());
if (ModelUtils.isComposedSchema(schema)
&& m.discriminator == null
&& schema.getExtensions().containsKey("x-one-of-name")) {
boolean isDeductionCase = true;
List<String> deductionModelNames = new ArrayList<>();
for (Schema modelSchema : ((ComposedSchema) schema).getOneOf()) {
if (modelSchema.get$ref() == null) {
isDeductionCase = false;
break;
}
String modelName = ModelUtils.getSimpleRef(modelSchema.get$ref());
deductionModelNames.add(toModelName(modelName));
}
if (isDeductionCase) {
m.vendorExtensions.put("x-deduction", true);
m.vendorExtensions.put("x-deduction-model-names", deductionModelNames);
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI. Since this changes the default codegen, it will take a lot more time to review.

if (schema.getDeprecated() != null) {
m.isDeprecated = schema.getDeprecated();
}
Expand Down Expand Up @@ -3165,7 +3184,7 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
}
}
// if there are composed oneOf/anyOf schemas, add them to this discriminator
if (ModelUtils.isComposedSchema(schema) && !this.getLegacyDiscriminatorBehavior()) {
if (ModelUtils.isComposedSchema(schema)) {
List<MappedModel> otherDescendants = getOneOfAnyOfDescendants(schemaName, discPropName, (ComposedSchema) schema, openAPI);
for (MappedModel otherDescendant : otherDescendants) {
if (!uniqueDescendants.contains(otherDescendant)) {
Expand Down Expand Up @@ -4177,6 +4196,11 @@ public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
} else { // no model/alias defined
responseSchema = ModelUtils.getSchemaFromResponse(response);
}

if (!ModelUtils.isSchemaOneOfConsistsOfCustomTypes(this.openAPI, responseSchema)) {
responseSchema = new Schema();
}

r.schema = responseSchema;
if (responseSchema != null) {
ModelUtils.syncValidationProperties(responseSchema, r);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;

import io.swagger.v3.oas.models.responses.ApiResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -1363,8 +1364,23 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
for (Operation operation : path.readOperations()) {
LOGGER.info("Processing operation {}", operation.getOperationId());
if (hasBodyParameter(openAPI, operation) || hasFormParameter(openAPI, operation)) {
String defaultContentType = hasFormParameter(openAPI, operation) ? "application/x-www-form-urlencoded" : "application/json";
boolean hasBodyParameter = hasBodyParameter(openAPI, operation);
boolean hasFormParameter = hasFormParameter(openAPI, operation);

// OpenAPI parser do not add Inline One Of models in Operations to Components/Schemas
if (hasBodyParameter) {
Optional.ofNullable(operation.getRequestBody())
.map(RequestBody::getContent)
.ifPresent(this::repairInlineOneOf);
}
if (operation.getResponses() != null) {
operation.getResponses().values().stream().map(ApiResponse::getContent)
.filter(Objects::nonNull)
.forEach(this::repairInlineOneOf);
}

if (hasBodyParameter || hasFormParameter) {
String defaultContentType = hasFormParameter ? "application/x-www-form-urlencoded" : "application/json";
List<String> consumes = new ArrayList<>(getConsumesInfo(openAPI, operation));
String contentType = consumes == null || consumes.isEmpty() ? defaultContentType : consumes.get(0);
operation.addExtension("x-contentType", contentType);
Expand Down Expand Up @@ -1421,6 +1437,39 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
}

/**
* Add all OneOf schemas to #/components/schemas and replace them in the original content by ref schema
* Replace OneOf with unmodifiable types with an empty Schema
*
* OpenAPI Parser does not add inline OneOf schemas to models to generate
*
* @param content a 'content' section in the OAS specification.
*/
private void repairInlineOneOf(final Content content) {
content.values().forEach(mediaType -> {
final Schema replacingSchema = mediaType.getSchema();
if (isOneOfSchema(replacingSchema)) {
if (ModelUtils.isSchemaOneOfConsistsOfCustomTypes(openAPI, replacingSchema)) {
final String oneOfModelName = (String) replacingSchema.getExtensions().get("x-one-of-name");
final Schema newRefSchema = new Schema<>().$ref("#/components/schemas/" + oneOfModelName);
mediaType.setSchema(newRefSchema);
ModelUtils.getSchemas(openAPI).put(oneOfModelName, replacingSchema);
} else {
mediaType.setSchema(new Schema());
}
}
});
}

private static boolean isOneOfSchema(final Schema schema) {
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;
return Optional.ofNullable(composedSchema.getProperties()).map(Map::isEmpty).orElse(true)
&& Optional.ofNullable(schema.getExtensions()).map(m -> m.containsKey("x-one-of-name")).orElse(false);
}
return false;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI. Since these changes occur in the abstract java codegen, which is used by the java client codegen, we will need to test the Java client (jersey2, native) generator carefully which already supports oneOf implementation (not using interface though)

Have you tried the oneOf implementation in Java client (jersey2, native) generator? Does it meet your requirement?

private static String getAccept(OpenAPI openAPI, Operation operation) {
String accepts = null;
String defaultContentType = "application/json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public JavaClientCodegen() {
artifactId = "openapi-java-client";
apiPackage = "org.openapitools.client.api";
modelPackage = "org.openapitools.client.model";
useOneOfInterfaces = true;
addOneOfInterfaceImports = true;

// cliOptions default redefinition need to be updated
updateOption(CodegenConstants.INVOKER_PACKAGE, this.getInvokerPackage());
Expand Down Expand Up @@ -1027,11 +1029,13 @@ public String toApiVarName(String name) {

@Override
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
Map<String, String> oneImport = new HashMap<>();
oneImport.put("import", importMapping.get(i));
if (!imports.contains(oneImport)) {
imports.add(oneImport);
if (additionalProperties.containsKey(JACKSON)) {
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
Map<String, String> oneImport = new HashMap<>();
oneImport.put("import", importMapping.get(i));
if (!imports.contains(oneImport)) {
imports.add(oneImport);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public SpringCodegen() {
modelPackage = "org.openapitools.model";
invokerPackage = "org.openapitools.api";
artifactId = "openapi-spring";
useOneOfInterfaces = true;
addOneOfInterfaceImports = true;

// clioOptions default redefinition need to be updated
updateOption(CodegenConstants.INVOKER_PACKAGE, this.getInvokerPackage());
Expand Down Expand Up @@ -868,6 +870,19 @@ public Map<String, Object> postProcessModelsEnum(Map<String, Object> objs) {
return objs;
}

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

public void setUseBeanValidation(boolean useBeanValidation) {
this.useBeanValidation = useBeanValidation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1652,4 +1652,25 @@ public static SemVer getOpenApiVersion(OpenAPI openAPI, String location, List<Au

return new SemVer(version);
}

public static boolean isSchemaOneOfConsistsOfCustomTypes(OpenAPI openAPI, Schema schema) {
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;
if (composedSchema.getOneOf() == null || composedSchema.getOneOf().isEmpty()) {
return false;
}
for (Schema oneOfSchema : composedSchema.getOneOf()) {
if (oneOfSchema.get$ref() != null) {
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, schema);
}
if (!(oneOfSchema instanceof ComposedSchema
|| oneOfSchema instanceof MapSchema
|| oneOfSchema instanceof ArraySchema
|| oneOfSchema instanceof ObjectSchema)) {
return false;
}
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}}
public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#discriminator}}
{{^vendorExtensions.x-deduction}}{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}
{{/discriminator}}{{/vendorExtensions.x-deduction}}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{{#jackson}}

{{^vendorExtensions.x-deduction}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
{{/vendorExtensions.x-deduction}}{{#vendorExtensions.x-deduction}}
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
{{#vendorExtensions.x-deduction-model-names}}
@JsonSubTypes.Type(value = {{.}}.class, name = "{{.}}"),
{{/vendorExtensions.x-deduction-model-names}}
})
{{/vendorExtensions.x-deduction}}
{{#discriminator.mappedModels}}
{{#-first}}
@JsonSubTypes({
Expand All @@ -13,4 +22,4 @@
{{#isClassnameSanitized}}
@JsonTypeName("{{name}}")
{{/isClassnameSanitized}}
{{/jackson}}
{{/jackson}}
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}} {
{{^vendorExtensions.x-deduction}}{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}{{/vendorExtensions.x-deduction}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.springframework.hateoas.RepresentationModel;
{{>enumOuterClass}}
{{/isEnum}}
{{^isEnum}}
{{>pojo}}
{{#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}} {
{{^vendorExtensions.x-deduction}}{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}{{/vendorExtensions.x-deduction}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/{{#description}}
@ApiModel(description = "{{{.}}}"){{/description}}
{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}{{>additionalModelTypeAnnotations}}
public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#serializableModel}}implements Serializable{{/serializableModel}} {
public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
{{#serializableModel}}
private static final long serialVersionUID = 1L;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
{{#jackson}}

{{^vendorExtensions.x-deduction}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
{{/vendorExtensions.x-deduction}}{{#vendorExtensions.x-deduction}}
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION){{/vendorExtensions.x-deduction}}
@JsonSubTypes({
{{#discriminator.mappedModels}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"),
{{/discriminator.mappedModels}}
}){{/jackson}}
{{#vendorExtensions.x-deduction-model-names}}
@JsonSubTypes.Type(value = {{.}}.class, name = "{{.}}"),
{{/vendorExtensions.x-deduction-model-names}}
}){{/jackson}}
Original file line number Diff line number Diff line change
Expand Up @@ -1137,10 +1137,15 @@ public void testComposedSchemaAllOfDiscriminatorMapLegacy() {
Assert.assertNull(reptile.discriminator);

// the MyPets discriminator contains Cat and Lizard
List<String> myPetsModelNames = Arrays.asList("Cat", "Lizard");
CodegenDiscriminator myPetDisc = new CodegenDiscriminator();
myPetDisc.setPropertyName(propertyName);
myPetDisc.setPropertyBaseName(propertyBaseName);
hs.clear();
for (String myPetsModelName: myPetsModelNames) {
hs.add(new CodegenDiscriminator.MappedModel(myPetsModelName, codegen.toModelName(myPetsModelName)));
}
myPetDisc.setMappedModels(hs);
modelName = "MyPets";
sc = openAPI.getComponents().getSchemas().get(modelName);
CodegenModel myPets = codegen.fromModel(modelName, sc);
Expand Down
Loading