[Core] Resolve Inline Models (#736)

* Add InlineModelResolver.java

* fix inline schema for OAS2 body parameter

* skip warning, fix MapSchema cast error
This commit is contained in:
antihax 2018-08-09 10:27:51 -05:00 committed by William Cheng
parent 9920c435ca
commit a0984a9be8
3 changed files with 1547 additions and 2 deletions

View File

@ -787,8 +787,8 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
configureOpenAPIInfo();
// resolve inline models
//InlineModelResolver inlineModelResolver = new InlineModelResolver();
//inlineModelResolver.flatten(openAPI);
InlineModelResolver inlineModelResolver = new InlineModelResolver();
inlineModelResolver.flatten(openAPI);
List<File> files = new ArrayList<File>();
// models

View File

@ -0,0 +1,533 @@
package org.openapitools.codegen;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.*;
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.RequestBody;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.core.util.Json;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import java.util.ArrayList;
import io.swagger.v3.oas.models.media.XML;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class InlineModelResolver {
private OpenAPI openapi;
private boolean skipMatches;
static Logger LOGGER = LoggerFactory.getLogger(InlineModelResolver.class);
Map<String, Schema> addedModels = new HashMap<String, Schema>();
Map<String, String> generatedSignature = new HashMap<String, String>();
public void flatten(OpenAPI openapi) {
this.openapi = openapi;
if (openapi.getComponents() == null) {
return; // There's nothing here
}
if (openapi.getComponents().getSchemas() == null) {
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
RequestBody rb = new RequestBody();
Content content = new Content();
MediaType mt = new MediaType();
Schema schema = new Schema();
schema.set$ref(modelName);
mt.setSchema(schema);
// TODO assume JSON, need to support other payload later
content.addMediaType("application/json", mt);
rb.setContent(content);
// add to openapi "components"
if (openapi.getComponents().getRequestBodies() == null) {
Map<String, RequestBody> requestBodies = new HashMap<String, RequestBody>();
requestBodies.put(modelName, rb);
openapi.getComponents().setRequestBodies(requestBodies);
} else {
openapi.getComponents().getRequestBodies().put(modelName, rb);
}
// update requestBody to use $ref instead of inline def
requestBody.set$ref(modelName);
}
}
} 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(), 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();
if (parameters != null) {
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) {
for (String key : responses.keySet()) {
ApiResponse response = responses.get(key);
if (ModelUtils.getSchemaFromResponse(response) != null) {
Schema property = ModelUtils.getSchemaFromResponse(response);
if (property instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) property;
if (op.getProperties() != null && op.getProperties().size() > 0) {
String modelName = resolveModelName(op.getTitle(), "inline_response_" + key);
Schema model = modelFromProperty(op, modelName);
String existing = matchGenerated(model);
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 (property instanceof ArraySchema) {
ArraySchema ap = (ArraySchema) property;
Schema inner = ap.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(),
"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);
}
}
}
} else if (property instanceof MapSchema) {
MapSchema mp = (MapSchema) property;
Schema innerProperty = (Schema) mp.getAdditionalProperties();
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);
}
}
}
}
}
}
}
}
}
}
// definitions
if (models != null) {
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
* example would look something like that in the doc: "\"example from def\""
*
* @param m Schema implementation
*/
private void fixStringModel(Schema m) {
if (m.getType() != null && m.getType().equals("string") && m.getExample() != null) {
String example = m.getExample().toString();
if (example.substring(0, 1).equals("\"") && example.substring(example.length() - 1).equals("\"")) {
m.setExample(example.substring(1, example.length() - 1));
}
}
}
private String resolveModelName(String title, String key) {
if (title == null) {
return uniqueName(key);
} else {
return uniqueName(title);
}
}
public String matchGenerated(Schema model) {
if (this.skipMatches) {
return null;
}
String json = Json.pretty(model);
if (generatedSignature.containsKey(json)) {
return generatedSignature.get(json);
}
return null;
}
public void addGenerated(String name, Schema model) {
generatedSignature.put(Json.pretty(model), name);
}
public String uniqueName(String key) {
if (key == null) {
key = "NULL_UNIQUE_NAME";
LOGGER.warn("null key found. Default to NULL_UNIQUE_NAME");
}
int count = 0;
boolean done = false;
key = key.replaceAll("[^a-z_\\.A-Z0-9 ]", ""); // FIXME: a parameter
// should not be
// assigned. Also declare
// the methods parameters
// as 'final'.
while (!done) {
String name = key;
if (count > 0) {
name = key + "_" + count;
}
if (openapi.getComponents().getSchemas() == null) {
return name;
} else if (!openapi.getComponents().getSchemas().containsKey(name)) {
return name;
}
count += 1;
}
return key;
}
public void flattenProperties(Map<String, Schema> properties, String path) {
if (properties == null) {
return;
}
Map<String, Schema> propsToUpdate = new HashMap<String, Schema>();
Map<String, Schema> modelsToAdd = new HashMap<String, Schema>();
for (String key : properties.keySet()) {
Schema property = properties.get(key);
if (property instanceof ObjectSchema && ((ObjectSchema) property).getProperties() != null
&& ((ObjectSchema) property).getProperties().size() > 0) {
ObjectSchema op = (ObjectSchema) property;
String modelName = resolveModelName(op.getTitle(), path + "_" + key);
Schema model = modelFromProperty(op, modelName);
String existing = matchGenerated(model);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
propsToUpdate.put(key, schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
propsToUpdate.put(key, schema);
modelsToAdd.put(modelName, model);
addGenerated(modelName, model);
openapi.getComponents().addSchemas(modelName, model);
}
} else if (property instanceof ArraySchema) {
ArraySchema ap = (ArraySchema) property;
Schema inner = ap.getItems();
if (inner instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) {
flattenProperties(op.getProperties(), path);
String modelName = resolveModelName(op.getTitle(), path + "_" + key);
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
ap.setItems(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
ap.setItems(schema);
addGenerated(modelName, innerModel);
openapi.getComponents().addSchemas(modelName, innerModel);
}
}
}
}
if (ModelUtils.isMapSchema(property)) {
Schema inner = (Schema) property.getAdditionalProperties();
if (inner instanceof ObjectSchema) {
ObjectSchema op = (ObjectSchema) inner;
if (op.getProperties() != null && op.getProperties().size() > 0) {
flattenProperties(op.getProperties(), path);
String modelName = resolveModelName(op.getTitle(), path + "_" + key);
Schema innerModel = modelFromProperty(op, modelName);
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.setRequired(op.getRequired());
property.setAdditionalProperties(schema);
} else {
Schema schema = new Schema().$ref(modelName);
schema.setRequired(op.getRequired());
property.setAdditionalProperties(schema);
addGenerated(modelName, innerModel);
openapi.getComponents().addSchemas(modelName, innerModel);
}
}
}
}
}
if (propsToUpdate.size() > 0) {
for (String key : propsToUpdate.keySet()) {
properties.put(key, propsToUpdate.get(key));
}
}
for (String key : modelsToAdd.keySet()) {
openapi.getComponents().addSchemas(key, modelsToAdd.get(key));
this.addedModels.put(key, modelsToAdd.get(key));
}
}
@SuppressWarnings("static-method")
public Schema modelFromProperty(ArraySchema object, @SuppressWarnings("unused") String path) {
String description = object.getDescription();
String example = null;
Object obj = object.getExample();
if (obj != null) {
example = obj.toString();
}
Schema inner = object.getItems();
if (inner instanceof ObjectSchema) {
ArraySchema model = new ArraySchema();
model.setDescription(description);
model.setExample(example);
model.setItems(object.getItems());
model.setName(object.getName());
return model;
}
return null;
}
public Schema modelFromProperty(ObjectSchema object, String path) {
String description = object.getDescription();
String example = null;
Object obj = object.getExample();
if (obj != null) {
example = obj.toString();
}
XML xml = object.getXml();
Map<String, Schema> properties = object.getProperties();
Schema model = new Schema();
model.setDescription(description);
model.setExample(example);
model.setName(object.getName());
model.setXml(xml);
if (properties != null) {
flattenProperties(properties, path);
model.setProperties(properties);
}
return model;
}
@SuppressWarnings("static-method")
public Schema modelFromProperty(MapSchema object, @SuppressWarnings("unused") String path) {
String description = object.getDescription();
String example = null;
Object obj = object.getExample();
if (obj != null) {
example = obj.toString();
}
ArraySchema model = new ArraySchema();
model.setDescription(description);
model.setName(object.getName());
model.setExample(example);
model.setItems((Schema) object.getAdditionalProperties());
return model;
}
/**
* Make a Schema
*
* @param ref new property name
* @param property Schema
* @return {@link Schema} A constructed OpenAPI property
*/
public Schema makeSchema(String ref, Schema property) {
Schema newProperty = new Schema().$ref(ref);
this.copyVendorExtensions(property, newProperty);
return newProperty;
}
/**
* Copy vendor extensions from Model to another Model
*
* @param source source property
* @param target target property
*/
public void copyVendorExtensions(Schema source, Schema target) {
Map<String, Object> vendorExtensions = source.getExtensions();
for (String extName : vendorExtensions.keySet()) {
target.addExtension(extName, vendorExtensions.get(extName));
}
}
public boolean isSkipMatches() {
return skipMatches;
}
public void setSkipMatches(boolean skipMatches) {
this.skipMatches = skipMatches;
}
}