Refactor InlineModelResolver (#1788)

* Extract a method "flattenPaths" to reduce the scope of method

* Tweak

* Rename parameter name

* Extract a method "flattenModels" to reduce the scope of method

* Rename parameter name

* Rename: models -> components

* Delete comment

* Extract a method "flattenRequestBody" to reduce the scope of method

* Extract a method "flattenParameters" to reduce the scope of method

* Extract a method "flattenResponses" to reduce the scope of method

* Tweak types

* Reduce indentation
This commit is contained in:
Akihito Nakano 2019-01-02 18:14:34 +09:00 committed by William Cheng
parent 9334dd391a
commit af6757ccde

View File

@ -17,18 +17,16 @@
package org.openapitools.codegen; package org.openapitools.codegen;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.*; import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody; import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.openapitools.codegen.utils.ModelUtils; import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -56,260 +54,264 @@ public class InlineModelResolver {
if (openapi.getComponents().getSchemas() == null) { if (openapi.getComponents().getSchemas() == null) {
openapi.getComponents().setSchemas(new HashMap<String, Schema>()); openapi.getComponents().setSchemas(new HashMap<String, Schema>());
} }
// operations
Map<String, PathItem> paths = openapi.getPaths();
Map<String, Schema> models = openapi.getComponents().getSchemas();
if (paths != null) {
for (String pathname : paths.keySet()) {
PathItem path = paths.get(pathname);
for (Operation operation : path.readOperations()) {
RequestBody requestBody = operation.getRequestBody();
if (requestBody != null) {
Schema model = ModelUtils.getSchemaFromRequestBody(requestBody);
if (model instanceof ObjectSchema) {
Schema obj = (Schema) model;
if (obj.getType() == null || "object".equals(obj.getType())) {
if (obj.getProperties() != null && obj.getProperties().size() > 0) {
flattenProperties(obj.getProperties(), pathname);
// for model name, use "title" if defined, otherwise default to 'inline_object'
String modelName = resolveModelName(obj.getTitle(), "inline_object");
addGenerated(modelName, model);
openapi.getComponents().addSchemas(modelName, model);
// create request body flattenPaths(openapi);
RequestBody rb = new RequestBody(); flattenComponents(openapi);
Content content = new Content(); }
MediaType mt = new MediaType();
Schema schema = new Schema();
schema.set$ref(modelName);
mt.setSchema(schema);
// get "consumes", e.g. application/xml, application/json /**
Set<String> consumes; * Flatten inline models in Paths
if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) { *
consumes = new HashSet<>(); * @param openAPI target spec
consumes.add("application/json"); // default to application/json */
LOGGER.info("Default to application/json for inline body schema"); private void flattenPaths(OpenAPI openAPI) {
} else { Paths paths = openAPI.getPaths();
consumes = requestBody.getContent().keySet(); if (paths == null) {
} return;
}
for (String consume : consumes) { for (String pathname : paths.keySet()) {
content.addMediaType(consume, mt); PathItem path = paths.get(pathname);
} for (Operation operation : path.readOperations()) {
flattenRequestBody(openAPI, pathname, operation);
flattenParameters(openAPI, pathname, operation);
flattenResponses(openAPI, pathname, operation);
}
}
}
rb.setContent(content); /**
* Flatten inline models in RequestBody
*
* @param openAPI target spec
* @param pathname target pathname
* @param operation target operation
*/
private void flattenRequestBody(OpenAPI openAPI, String pathname, Operation operation) {
RequestBody requestBody = operation.getRequestBody();
if (requestBody == null) {
return;
}
// add to openapi "components" Schema model = ModelUtils.getSchemaFromRequestBody(requestBody);
if (openapi.getComponents().getRequestBodies() == null) { if (model instanceof ObjectSchema) {
Map<String, RequestBody> requestBodies = new HashMap<String, RequestBody>(); Schema obj = (Schema) model;
requestBodies.put(modelName, rb); if (obj.getType() == null || "object".equals(obj.getType())) {
openapi.getComponents().setRequestBodies(requestBodies); if (obj.getProperties() != null && obj.getProperties().size() > 0) {
} else { flattenProperties(obj.getProperties(), pathname);
openapi.getComponents().getRequestBodies().put(modelName, rb); // for model name, use "title" if defined, otherwise default to 'inline_object'
} String modelName = resolveModelName(obj.getTitle(), "inline_object");
addGenerated(modelName, model);
openAPI.getComponents().addSchemas(modelName, model);
// update requestBody to use $ref instead of inline def // create request body
requestBody.set$ref(modelName); RequestBody rb = new RequestBody();
Content content = new Content();
MediaType mt = new MediaType();
Schema schema = new Schema();
schema.set$ref(modelName);
mt.setSchema(schema);
} // get "consumes", e.g. application/xml, application/json
} Set<String> consumes;
} else if (model instanceof ArraySchema) { if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) {
ArraySchema am = (ArraySchema) model; consumes = new HashSet<>();
Schema inner = am.getItems(); consumes.add("application/json"); // default to application/json
if (inner instanceof ObjectSchema) { LOGGER.info("Default to application/json for inline body schema");
ObjectSchema op = (ObjectSchema) inner; } else {
if (op.getProperties() != null && op.getProperties().size() > 0) { consumes = requestBody.getContent().keySet();
flattenProperties(op.getProperties(), pathname);
String modelName = resolveModelName(op.getTitle(), null);
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
am.setItems(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
am.setItems(schema);
addGenerated(modelName, innerModel);
openapi.getComponents().addSchemas(modelName, innerModel);
}
}
}
}
} }
List<Parameter> parameters = operation.getParameters(); for (String consume : consumes) {
if (parameters != null) { content.addMediaType(consume, mt);
for (Parameter parameter : parameters) {
if (parameter.getSchema() != null) {
Schema model = parameter.getSchema();
if (model instanceof ObjectSchema) {
Schema obj = (Schema) model;
if (obj.getType() == null || "object".equals(obj.getType())) {
if (obj.getProperties() != null && obj.getProperties().size() > 0) {
flattenProperties(obj.getProperties(), pathname);
String modelName = resolveModelName(obj.getTitle(), parameter.getName());
parameter.$ref(modelName);
addGenerated(modelName, model);
openapi.getComponents().addSchemas(modelName, model);
}
}
} else if (model instanceof ArraySchema) {
ArraySchema am = (ArraySchema) model;
Schema inner = am.getItems();
if (inner instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) {
flattenProperties(op.getProperties(), pathname);
String modelName = resolveModelName(op.getTitle(), parameter.getName());
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
am.setItems(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
am.setItems(schema);
addGenerated(modelName, innerModel);
openapi.getComponents().addSchemas(modelName, innerModel);
}
}
}
}
}
}
} }
Map<String, ApiResponse> responses = operation.getResponses();
if (responses != null) { rb.setContent(content);
for (String key : responses.keySet()) {
ApiResponse response = responses.get(key); // add to openapi "components"
if (ModelUtils.getSchemaFromResponse(response) != null) { if (openAPI.getComponents().getRequestBodies() == null) {
Schema property = ModelUtils.getSchemaFromResponse(response); Map<String, RequestBody> requestBodies = new HashMap<String, RequestBody>();
if (property instanceof ObjectSchema) { requestBodies.put(modelName, rb);
ObjectSchema op = (ObjectSchema) property; openAPI.getComponents().setRequestBodies(requestBodies);
if (op.getProperties() != null && op.getProperties().size() > 0) { } else {
String modelName = resolveModelName(op.getTitle(), "inline_response_" + key); openAPI.getComponents().getRequestBodies().put(modelName, rb);
Schema model = modelFromProperty(op, modelName); }
String existing = matchGenerated(model);
Content content = response.getContent(); // update requestBody to use $ref instead of inline def
for (MediaType mediaType : content.values()) { requestBody.set$ref(modelName);
if (existing != null) {
Schema schema = this.makeSchema(existing, property); }
schema.setRequired(op.getRequired()); }
mediaType.setSchema(schema); } else if (model instanceof ArraySchema) {
} else { ArraySchema am = (ArraySchema) model;
Schema schema = this.makeSchema(modelName, property); Schema inner = am.getItems();
schema.setRequired(op.getRequired()); if (inner instanceof ObjectSchema) {
mediaType.setSchema(schema); ObjectSchema op = (ObjectSchema) inner;
addGenerated(modelName, model); if (op.getProperties() != null && op.getProperties().size() > 0) {
openapi.getComponents().addSchemas(modelName, model); flattenProperties(op.getProperties(), pathname);
} String modelName = resolveModelName(op.getTitle(), null);
} Schema innerModel = modelFromProperty(op, modelName);
} String existing = matchGenerated(innerModel);
} else if (property instanceof ArraySchema) { if (existing != null) {
ArraySchema ap = (ArraySchema) property; Schema schema = new Schema().$ref(existing);
Schema inner = ap.getItems(); schema.setRequired(op.getRequired());
if (inner instanceof ObjectSchema) { am.setItems(schema);
ObjectSchema op = (ObjectSchema) inner; } else {
if (op.getProperties() != null && op.getProperties().size() > 0) { Schema schema = new Schema().$ref(modelName);
flattenProperties(op.getProperties(), pathname); schema.setRequired(op.getRequired());
String modelName = resolveModelName(op.getTitle(), am.setItems(schema);
"inline_response_" + key); addGenerated(modelName, innerModel);
Schema innerModel = modelFromProperty(op, modelName); openAPI.getComponents().addSchemas(modelName, innerModel);
String existing = matchGenerated(innerModel); }
if (existing != null) { }
Schema schema = this.makeSchema(existing, op); }
schema.setRequired(op.getRequired()); }
ap.setItems(schema); }
} else {
Schema schema = this.makeSchema(modelName, op); /**
schema.setRequired(op.getRequired()); * Flatten inline models in parameters
ap.setItems(schema); *
addGenerated(modelName, innerModel); * @param openAPI target spec
openapi.getComponents().addSchemas(modelName, innerModel); * @param pathname target pathname
} * @param operation target operation
} */
} private void flattenParameters(OpenAPI openAPI, String pathname, Operation operation) {
} else if (property instanceof MapSchema) { List<Parameter> parameters = operation.getParameters();
MapSchema mp = (MapSchema) property; if (parameters == null) {
Schema innerProperty = ModelUtils.getAdditionalProperties(mp); return;
if (innerProperty instanceof ObjectSchema) { }
ObjectSchema op = (ObjectSchema) innerProperty;
if (op.getProperties() != null && op.getProperties().size() > 0) { for (Parameter parameter : parameters) {
flattenProperties(op.getProperties(), pathname); if (parameter.getSchema() == null) {
String modelName = resolveModelName(op.getTitle(), continue;
"inline_response_" + key); }
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel); Schema model = parameter.getSchema();
if (existing != null) { if (model instanceof ObjectSchema) {
Schema schema = new Schema().$ref(existing); Schema obj = (Schema) model;
schema.setRequired(op.getRequired()); if (obj.getType() == null || "object".equals(obj.getType())) {
mp.setAdditionalProperties(schema); if (obj.getProperties() != null && obj.getProperties().size() > 0) {
} else { flattenProperties(obj.getProperties(), pathname);
Schema schema = new Schema().$ref(modelName); String modelName = resolveModelName(obj.getTitle(), parameter.getName());
schema.setRequired(op.getRequired());
mp.setAdditionalProperties(schema); parameter.$ref(modelName);
addGenerated(modelName, innerModel); addGenerated(modelName, model);
openapi.getComponents().addSchemas(modelName, innerModel); openAPI.getComponents().addSchemas(modelName, model);
} }
} }
} } else if (model instanceof ArraySchema) {
} ArraySchema am = (ArraySchema) model;
} Schema inner = am.getItems();
if (inner instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) {
flattenProperties(op.getProperties(), pathname);
String modelName = resolveModelName(op.getTitle(), parameter.getName());
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
am.setItems(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
am.setItems(schema);
addGenerated(modelName, innerModel);
openAPI.getComponents().addSchemas(modelName, innerModel);
} }
} }
} }
} }
} }
// definitions }
if (models != null) {
List<String> modelNames = new ArrayList<String>(models.keySet()); /**
for (String modelName : modelNames) { * Flatten inline models in ApiResponses
Schema model = models.get(modelName); *
if (model instanceof Schema) { * @param openAPI target spec
Schema m = (Schema) model; * @param pathname target pathname
Map<String, Schema> properties = m.getProperties(); * @param operation target operation
flattenProperties(properties, modelName); */
fixStringModel(m); private void flattenResponses(OpenAPI openAPI, String pathname, Operation operation) {
} else if (ModelUtils.isArraySchema(model)) { ApiResponses responses = operation.getResponses();
ArraySchema m = (ArraySchema) model; if (responses == null) {
Schema inner = m.getItems(); return;
if (inner instanceof ObjectSchema) { }
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) { for (String key : responses.keySet()) {
String innerModelName = resolveModelName(op.getTitle(), modelName + "_inner"); ApiResponse response = responses.get(key);
Schema innerModel = modelFromProperty(op, innerModelName); if (ModelUtils.getSchemaFromResponse(response) == null) {
String existing = matchGenerated(innerModel); continue;
if (existing == null) { }
openapi.getComponents().addSchemas(innerModelName, innerModel);
addGenerated(innerModelName, innerModel); Schema property = ModelUtils.getSchemaFromResponse(response);
Schema schema = new Schema().$ref(innerModelName); if (property instanceof ObjectSchema) {
schema.setRequired(op.getRequired()); ObjectSchema op = (ObjectSchema) property;
m.setItems(schema); if (op.getProperties() != null && op.getProperties().size() > 0) {
} else { String modelName = resolveModelName(op.getTitle(), "inline_response_" + key);
Schema schema = new Schema().$ref(existing); Schema model = modelFromProperty(op, modelName);
schema.setRequired(op.getRequired()); String existing = matchGenerated(model);
m.setItems(schema); Content content = response.getContent();
} for (MediaType mediaType : content.values()) {
if (existing != null) {
Schema schema = this.makeSchema(existing, property);
schema.setRequired(op.getRequired());
mediaType.setSchema(schema);
} else {
Schema schema = this.makeSchema(modelName, property);
schema.setRequired(op.getRequired());
mediaType.setSchema(schema);
addGenerated(modelName, model);
openAPI.getComponents().addSchemas(modelName, model);
} }
} }
} else if (ModelUtils.isComposedSchema(model)) { }
ComposedSchema m = (ComposedSchema) model; } else if (property instanceof ArraySchema) {
if (m.getAllOf() != null && !m.getAllOf().isEmpty()) { ArraySchema ap = (ArraySchema) property;
Schema child = null; Schema inner = ap.getItems();
for (Schema component : m.getAllOf()) { if (inner instanceof ObjectSchema) {
if (component.get$ref() == null) { ObjectSchema op = (ObjectSchema) inner;
child = component; if (op.getProperties() != null && op.getProperties().size() > 0) {
} flattenProperties(op.getProperties(), pathname);
String modelName = resolveModelName(op.getTitle(),
"inline_response_" + key);
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = this.makeSchema(existing, op);
schema.setRequired(op.getRequired());
ap.setItems(schema);
} else {
Schema schema = this.makeSchema(modelName, op);
schema.setRequired(op.getRequired());
ap.setItems(schema);
addGenerated(modelName, innerModel);
openAPI.getComponents().addSchemas(modelName, innerModel);
} }
if (child != null) { }
Map<String, Schema> properties = child.getProperties(); }
flattenProperties(properties, modelName); } else if (property instanceof MapSchema) {
MapSchema mp = (MapSchema) property;
Schema innerProperty = ModelUtils.getAdditionalProperties(mp);
if (innerProperty instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) innerProperty;
if (op.getProperties() != null && op.getProperties().size() > 0) {
flattenProperties(op.getProperties(), pathname);
String modelName = resolveModelName(op.getTitle(),
"inline_response_" + key);
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
mp.setAdditionalProperties(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
mp.setAdditionalProperties(schema);
addGenerated(modelName, innerModel);
openAPI.getComponents().addSchemas(modelName, innerModel);
} }
} }
} }
@ -317,6 +319,65 @@ public class InlineModelResolver {
} }
} }
/**
* Flatten inline models in components
*
* @param openAPI target spec
*/
private void flattenComponents(OpenAPI openAPI) {
Map<String, Schema> models = openAPI.getComponents().getSchemas();
if (models == null) {
return;
}
List<String> modelNames = new ArrayList<String>(models.keySet());
for (String modelName : modelNames) {
Schema model = models.get(modelName);
if (model instanceof Schema) {
Schema m = (Schema) model;
Map<String, Schema> properties = m.getProperties();
flattenProperties(properties, modelName);
fixStringModel(m);
} else if (ModelUtils.isArraySchema(model)) {
ArraySchema m = (ArraySchema) model;
Schema inner = m.getItems();
if (inner instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) {
String innerModelName = resolveModelName(op.getTitle(), modelName + "_inner");
Schema innerModel = modelFromProperty(op, innerModelName);
String existing = matchGenerated(innerModel);
if (existing == null) {
openAPI.getComponents().addSchemas(innerModelName, innerModel);
addGenerated(innerModelName, innerModel);
Schema schema = new Schema().$ref(innerModelName);
schema.setRequired(op.getRequired());
m.setItems(schema);
} else {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
m.setItems(schema);
}
}
}
} else if (ModelUtils.isComposedSchema(model)) {
ComposedSchema m = (ComposedSchema) model;
if (m.getAllOf() != null && !m.getAllOf().isEmpty()) {
Schema child = null;
for (Schema component : m.getAllOf()) {
if (component.get$ref() == null) {
child = component;
}
}
if (child != null) {
Map<String, Schema> properties = child.getProperties();
flattenProperties(properties, modelName);
}
}
}
}
}
/** /**
* This function fix models that are string (mostly enum). Before this fix, the * This function fix models that are string (mostly enum). Before this fix, the
* example would look something like that in the doc: "\"example from def\"" * example would look something like that in the doc: "\"example from def\""