![JAR search and dependency download from the Maven repository](/logo.png)
io.apicurio.datamodels.transform.OpenApi20to30TransformationVisitor Maven / Gradle / Ivy
/*
* Copyright 2019 Red Hat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apicurio.datamodels.transform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.apicurio.datamodels.Library;
import io.apicurio.datamodels.TraverserDirection;
import io.apicurio.datamodels.models.Contact;
import io.apicurio.datamodels.models.Document;
import io.apicurio.datamodels.models.Extensible;
import io.apicurio.datamodels.models.ExternalDocumentation;
import io.apicurio.datamodels.models.Info;
import io.apicurio.datamodels.models.License;
import io.apicurio.datamodels.models.ModelType;
import io.apicurio.datamodels.models.Node;
import io.apicurio.datamodels.models.Operation;
import io.apicurio.datamodels.models.Parameter;
import io.apicurio.datamodels.models.Referenceable;
import io.apicurio.datamodels.models.Schema;
import io.apicurio.datamodels.models.SecurityRequirement;
import io.apicurio.datamodels.models.SecurityScheme;
import io.apicurio.datamodels.models.Tag;
import io.apicurio.datamodels.models.openapi.OpenApiExample;
import io.apicurio.datamodels.models.openapi.OpenApiHeader;
import io.apicurio.datamodels.models.openapi.OpenApiOAuthFlow;
import io.apicurio.datamodels.models.openapi.OpenApiPathItem;
import io.apicurio.datamodels.models.openapi.OpenApiPaths;
import io.apicurio.datamodels.models.openapi.OpenApiResponse;
import io.apicurio.datamodels.models.openapi.OpenApiResponses;
import io.apicurio.datamodels.models.openapi.OpenApiSchema;
import io.apicurio.datamodels.models.openapi.OpenApiTag;
import io.apicurio.datamodels.models.openapi.OpenApiXML;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Contact;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Definitions;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Document;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20ExternalDocumentation;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Header;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Headers;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Info;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Items;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20License;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Operation;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Parameter;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20ParameterDefinitions;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20PathItem;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Paths;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Response;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20ResponseDefinitions;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Responses;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Schema;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20Scopes;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20SecurityDefinitions;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20SecurityRequirement;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20SecurityScheme;
import io.apicurio.datamodels.models.openapi.v20.OpenApi20XML;
import io.apicurio.datamodels.models.openapi.v20.visitors.OpenApi20Visitor;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Components;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Contact;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Discriminator;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Document;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30ExternalDocumentation;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Header;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Info;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30License;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30MediaType;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30OAuthFlows;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Operation;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Parameter;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30PathItem;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Paths;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30RequestBody;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Response;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Responses;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Schema;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30SecurityRequirement;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30SecurityScheme;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30Server;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30ServerVariable;
import io.apicurio.datamodels.models.openapi.v30.OpenApi30XML;
import io.apicurio.datamodels.models.union.BooleanUnionValue;
import io.apicurio.datamodels.models.union.BooleanUnionValueImpl;
import io.apicurio.datamodels.models.util.JsonUtil;
import io.apicurio.datamodels.models.visitors.TraversalContext;
import io.apicurio.datamodels.models.visitors.TraversingVisitor;
import io.apicurio.datamodels.paths.NodePath;
import io.apicurio.datamodels.paths.NodePathUtil;
import io.apicurio.datamodels.refs.ReferenceUtil;
import io.apicurio.datamodels.util.NodeUtil;
import io.apicurio.datamodels.visitors.ConsumesProducesFinder;
import io.apicurio.datamodels.visitors.OperationFinder;
/**
* A visitor used to transform an OpenAPI 2.0 document into an OpenAPI 3.0.x document.
* @author [email protected]
*/
public class OpenApi20to30TransformationVisitor implements OpenApi20Visitor, TraversingVisitor {
private OpenApi30Document doc30;
private TraversalContext traversalContext;
private Map _nodeMap = new HashMap<>();
private List _postProcessResponses = new ArrayList<>();
private boolean _postProcessingComplete = false;
@Override
public void setTraversalContext(TraversalContext context) {
this.traversalContext = context;
}
/**
* Retrieves the final result.
*/
public OpenApi30Document getResult() {
if (!this._postProcessingComplete) {
this.postProcess();
}
return this.doc30;
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitDocument(Document)
*/
@Override
public void visitDocument(Document node) {
this.doc30 = (OpenApi30Document) Library.createDocument(ModelType.OPENAPI30);
this.doc30.setOpenapi("3.0.3");
OpenApi20Document doc20 = (OpenApi20Document) node;
if (!NodeUtil.isNullOrUndefined(doc20.getHost())) {
String basePath = doc20.getBasePath();
if (NodeUtil.isNullOrUndefined(basePath)) {
basePath = "";
}
List schemes = doc20.getSchemes();
if (NodeUtil.isNullOrUndefined(schemes) || schemes.size() == 0) {
schemes = NodeUtil.asList("http");
}
OpenApi30Server server30 = this.doc30.createServer();
this.doc30.addServer(server30);
if (schemes.size() == 1) {
server30.setUrl(schemes.get(0) + "://" + doc20.getHost() + basePath);
} else {
server30.setUrl("{scheme}://" + doc20.getHost() + basePath);
OpenApi30ServerVariable var30 = (OpenApi30ServerVariable) server30.createServerVariable();
server30.addVariable("scheme", var30);
var30.setDefault(schemes.get(0));
var30.setEnum(NodeUtil.copyList(schemes));
var30.setDescription("The supported protocol schemes.");
}
}
this.copyExtensions(doc20, doc30);
this.mapNode(doc20, this.doc30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitInfo(Info)
*/
@Override
public void visitInfo(Info node) {
OpenApi20Info info20 = (OpenApi20Info) node;
OpenApi30Info info30 = (OpenApi30Info) this.doc30.createInfo();
this.doc30.setInfo(info30);
info30.setTitle(info20.getTitle());
info30.setDescription(info20.getDescription());
info30.setTermsOfService(info20.getTermsOfService());
info30.setVersion(info20.getVersion());
this.copyExtensions(info20, info30);
this.mapNode(info20, info30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitContact(Contact)
*/
@Override
public void visitContact(Contact node) {
OpenApi30Info info30 = (OpenApi30Info) this.lookup(node.parent());
OpenApi30Contact contact30 = (OpenApi30Contact) info30.createContact();
info30.setContact(contact30);
contact30.setName(node.getName());
contact30.setUrl(node.getUrl());
contact30.setEmail(node.getEmail());
this.copyExtensions((OpenApi20Contact) node, contact30);
this.mapNode(node, contact30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitLicense(License)
*/
@Override
public void visitLicense(License node) {
OpenApi30Info info30 = (OpenApi30Info) this.lookup(node.parent());
OpenApi30License license30 = (OpenApi30License) info30.createLicense();
info30.setLicense(license30);
license30.setName(node.getName());
license30.setUrl(node.getUrl());
this.copyExtensions((OpenApi20License) node, license30);
this.mapNode(node, license30);
}
@Override
public void visitPaths(OpenApiPaths node) {
OpenApiPaths paths30 = this.doc30.createPaths();
this.doc30.setPaths(paths30);
this.copyExtensions((OpenApi20Paths) node, (OpenApi30Paths) paths30);
this.mapNode(node, this.doc30.getPaths());
}
@Override
public void visitPathItem(OpenApiPathItem node) {
OpenApi20PathItem pathItem20 = (OpenApi20PathItem) node;
OpenApi30Paths paths30 = (OpenApi30Paths) this.lookup(pathItem20.parent());
OpenApi30PathItem pathItem30 = (OpenApi30PathItem) paths30.createPathItem();
String pathName = (String) this.traversalContext.getMostRecentStep().getValue();
paths30.addItem(pathName, pathItem30);
pathItem30.set$ref(this.updateRef(pathItem20.get$ref()));
this.copyExtensions(pathItem20, pathItem30);
this.mapNode(pathItem20, pathItem30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitOperation(Operation)
*/
@Override
public void visitOperation(Operation node) {
OpenApi20Operation op = (OpenApi20Operation) node;
OpenApi30PathItem pathItem30 = (OpenApi30PathItem) this.lookup(node.parent());
OpenApi30Operation operation30 = pathItem30.createOperation();
String operationType = (String) this.traversalContext.getMostRecentStep().getValue();
if (operationType.equals("get")) {
pathItem30.setGet(operation30);
} else if (operationType.equals("put")) {
pathItem30.setPut(operation30);
} else if (operationType.equals("post")) {
pathItem30.setPost(operation30);
} else if (operationType.equals("delete")) {
pathItem30.setDelete(operation30);
} else if (operationType.equals("head")) {
pathItem30.setHead(operation30);
} else if (operationType.equals("options")) {
pathItem30.setOptions(operation30);
} else if (operationType.equals("patch")) {
pathItem30.setPatch(operation30);
} else if (operationType.equals("trace")) {
pathItem30.setTrace(operation30);
}
operation30.setTags(op.getTags());
operation30.setSummary(op.getSummary());
operation30.setDescription(op.getDescription());
operation30.setOperationId(op.getOperationId());
operation30.setDeprecated(op.isDeprecated());
if (!NodeUtil.isNullOrUndefined(op.getSchemes()) &&
op.getSchemes().size() > 0 &&
!NodeUtil.isNullOrUndefined(this.doc30.getServers()) &&
this.doc30.getServers().size() > 0)
{
OpenApi30Server server30 = operation30.createServer();
operation30.addServer(server30);
server30.setUrl(this.doc30.getServers().get(0).getUrl());
if (op.getSchemes().size() == 1) {
server30.setUrl(server30.getUrl().replace("{scheme}", op.getSchemes().get(0)));
server30.removeVariable("scheme");
} else {
server30.setUrl("{scheme}" + server30.getUrl().substring(server30.getUrl().indexOf("://")));
OpenApi30ServerVariable var30 = (OpenApi30ServerVariable) server30.createServerVariable();
server30.addVariable("scheme", var30);
var30.setDescription("The supported protocol schemes.");
var30.setDefault(op.getSchemes().get(0));
var30.setEnum(NodeUtil.copyList(op.getSchemes()));
}
}
// Note: consumes/produces will be handled elsewhere (when Request Body and Response models are created)
this.copyExtensions(op, operation30);
this.mapNode(op, operation30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitParameter(Parameter)
*/
@Override
public void visitParameter(Parameter node) {
if (isParameterDefinition(node)) {
visitParameterDefinition(node);
return;
}
OpenApi20Parameter param20 = (OpenApi20Parameter) node;
if (NodeUtil.equals(param20.getIn(), "body")) {
OpenApi30Operation operation30 = (OpenApi30Operation) this.lookup(this.findParentOperation(param20));
if (!NodeUtil.isNullOrUndefined(operation30)) {
OpenApi30RequestBody body30 = operation30.createRequestBody();
operation30.setRequestBody(body30);
body30.setDescription(param20.getDescription());
body30.setRequired(param20.isRequired());
if (!NodeUtil.isNullOrUndefined(param20.getSchema())) {
List consumes = this.findConsumes(param20);
OpenApi20Schema schema = (OpenApi20Schema) param20.getSchema();
consumes.forEach( ct -> {
OpenApi30MediaType mediaType30 = (OpenApi30MediaType) body30.createMediaType();
body30.addContent(ct, mediaType30);
OpenApi30Schema schema30 = (OpenApi30Schema) mediaType30.createSchema();
mediaType30.setSchema(this.toSchema(schema, schema30, true));
this.mapNode(schema, schema30);
});
}
}
} else if (NodeUtil.equals(param20.getIn(), "formData")) {
OpenApi30Operation operation30 = (OpenApi30Operation) this.lookup(this.findParentOperation(param20));
if (!NodeUtil.isNullOrUndefined(operation30)) {
List consumes = this.findConsumes(param20);
if (!this.hasFormDataMimeType(consumes)) {
consumes = NodeUtil.asList("application/x-www-form-urlencoded");
}
consumes.forEach(ct -> {
if (this.isFormDataMimeType(ct)) {
OpenApi30RequestBody body30 = operation30.getRequestBody();
if (NodeUtil.isNullOrUndefined(body30)) {
body30 = operation30.createRequestBody();
operation30.setRequestBody(body30);
body30.setRequired(true);
}
OpenApi30MediaType mediaType30 = null;
if (!NodeUtil.isNullOrUndefined(body30.getContent())) {
mediaType30 = (OpenApi30MediaType) body30.getContent().get(ct);
}
if (NodeUtil.isNullOrUndefined(mediaType30)) {
mediaType30 = (OpenApi30MediaType) body30.createMediaType();
body30.addContent(ct, mediaType30);
}
OpenApi30Schema schema30 = (OpenApi30Schema) mediaType30.getSchema();
if (NodeUtil.isNullOrUndefined(schema30)) {
schema30 = (OpenApi30Schema) mediaType30.createSchema();
mediaType30.setSchema(schema30);
schema30.setType("object");
}
OpenApi30Schema property30 = schema30.createSchema();
schema30.addProperty(param20.getName(), property30);
property30.setDescription(param20.getDescription());
this.toSchema(param20, property30, false);
this.mapNode(param20, schema30);
}
});
}
} else {
if (this.isRef(param20)) {
OpenApi20Parameter paramDef = (OpenApi20Parameter) ReferenceUtil.resolveRef(param20.get$ref(), param20);
// Handle the case where there is a parameter $ref to a "body" param. All body params become
// Request Bodies. So a reference to a "body" param should become a reference to a request body.
if (!NodeUtil.isNullOrUndefined(paramDef) && NodeUtil.equals(paramDef.getIn(), "body")) {
OpenApi30Operation parent30 = (OpenApi30Operation) this.lookup(this.findParentOperation(param20));
if (!NodeUtil.isNullOrUndefined(parent30)) {
OpenApi30RequestBody body30 = parent30.createRequestBody();
parent30.setRequestBody(body30);
String newRef = param20.get$ref().replace("#/parameters/", "#/components/requestBodies/");
body30.set$ref(newRef);
this.mapNode(param20, body30);
return;
}
}
// Handle the case where the parameter is a $ref to a formData param. In this case we want to
// treat the param as though it is inlined (which will result in a requestBody model).
if (!NodeUtil.isNullOrUndefined(paramDef) && NodeUtil.equals(paramDef.getIn(), "formData")) {
// Inline the parameter definition and then re-visit it.
Library.readNode(Library.writeNode(paramDef), param20);
param20.set$ref(null);
this.visitParameter(param20);
return;
}
}
// Now we have normal handling of a parameter, examples include path params, query params, header params, etc.
Node parent30 = this.lookup(param20.parent());
OpenApi30Parameter param30 = createAndAddParameter(parent30);
this.transformParam(param20, param30);
this.copyExtensions(param20, param30);
this.mapNode(param20, param30);
}
}
private OpenApi30Parameter createAndAddParameter(Node parent30) {
OpenApiParameterCreator paramCreator = new OpenApiParameterCreator();
parent30.accept(paramCreator);
return (OpenApi30Parameter) paramCreator.parameter;
}
private OpenApi30Parameter transformParam(OpenApi20Parameter node, OpenApi30Parameter param30) {
param30.set$ref(this.updateRef(node.get$ref()));
if (!NodeUtil.isNullOrUndefined(param30.get$ref())) {
return param30;
}
param30.setName(node.getName());
param30.setIn(node.getIn());
param30.setDescription(node.getDescription());
param30.setRequired(node.isRequired());
param30.setAllowEmptyValue(node.isAllowEmptyValue());
param30.setSchema(this.toSchema(node, (OpenApi30Schema) param30.createSchema(), false));
this.collectionFormatToStyleAndExplode(node, param30);
return param30;
}
public void visitParameterDefinition(Parameter node) {
String name = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi20Parameter pd20 = (OpenApi20Parameter) node;
if (NodeUtil.equals(pd20.getIn(), "body")) {
OpenApi30Components parent30 = this.getOrCreateComponents();
OpenApi30RequestBody bodyDef30 = (OpenApi30RequestBody) parent30.createRequestBody();
parent30.addRequestBody(name, bodyDef30);
bodyDef30.setDescription(pd20.getDescription());
bodyDef30.setRequired(pd20.isRequired());
if (!NodeUtil.isNullOrUndefined(pd20.getSchema())) {
List consumes = this.findConsumes(pd20);
OpenApi20Schema schema = (OpenApi20Schema) pd20.getSchema();
consumes.forEach(ct -> {
OpenApi30MediaType mediaType30 = (OpenApi30MediaType) bodyDef30.createMediaType();
bodyDef30.addContent(ct, mediaType30);
OpenApi30Schema schema30 = (OpenApi30Schema) mediaType30.createSchema();
mediaType30.setSchema(this.toSchema(schema, schema30, true));
this.copyExtensions(schema, schema30);
this.mapNode(schema, schema30);
});
}
} else if (NodeUtil.equals(pd20.getIn(), "formData")) {
// Exclude any re-usable formData parameters - they are currently being inlined elsewhere. I'm not sure
// what we would do with them anyway.
} else {
OpenApi30Components components30 = this.getOrCreateComponents();
OpenApi30Parameter paramDef30 = (OpenApi30Parameter) components30.createParameter();
components30.addParameter(name, paramDef30);
this.transformParam(pd20, paramDef30);
this.copyExtensions(pd20, paramDef30);
this.mapNode(pd20, paramDef30);
}
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitExternalDocumentation(ExternalDocumentation)
*/
@Override
public void visitExternalDocumentation(ExternalDocumentation node) {
Node parent30 = this.lookup(node.parent());
ExternalDocsCreator externalDocsCreator = new ExternalDocsCreator();
parent30.accept(externalDocsCreator);
OpenApi30ExternalDocumentation externalDocs30 = (OpenApi30ExternalDocumentation) externalDocsCreator.externalDocs;
externalDocs30.setDescription(node.getDescription());
externalDocs30.setUrl(node.getUrl());
this.copyExtensions((OpenApi20ExternalDocumentation) node, externalDocs30);
this.mapNode(node, externalDocs30);
}
@Override
public void visitSecurityRequirement(SecurityRequirement node) {
OpenApi20SecurityRequirement req = (OpenApi20SecurityRequirement) node;
Node parent30 = this.lookup(req.parent());
SecurityRequirementCreator securityRequirementCreator = new SecurityRequirementCreator();
parent30.accept(securityRequirementCreator);
OpenApi30SecurityRequirement securityRequirement30 = (OpenApi30SecurityRequirement) securityRequirementCreator.securityRequirement;
req.getItemNames().forEach( name -> {
securityRequirement30.addItem(name, req.getItem(name));
});
this.mapNode(req, securityRequirement30);
}
@Override
public void visitResponses(OpenApiResponses node) {
OpenApi30Operation parent30 = (OpenApi30Operation) this.lookup(node.parent());
OpenApi30Responses responses30 = (OpenApi30Responses) parent30.createResponses();
parent30.setResponses(responses30);
this.copyExtensions((OpenApi20Responses) node, responses30);
this.mapNode(node, responses30);
}
@Override
public void visitResponse(OpenApiResponse node) {
if (isResponseDefinition(node)) {
visitResponseDefinition(node);
return;
}
OpenApi20Response response20 = (OpenApi20Response) node;
OpenApi30Responses parent30 = (OpenApi30Responses) this.lookup(node.parent());
OpenApi30Response response30 = (OpenApi30Response) parent30.createResponse();
String statusCode = (String) this.traversalContext.getMostRecentStep().getValue();
if ("default".equals(statusCode)) {
parent30.setDefault(response30);
} else {
parent30.addItem(statusCode, response30);
}
response30.set$ref(this.updateRef(response20.get$ref()));
this.transformResponse((OpenApi20Response) node, response30);
this.copyExtensions((OpenApi20Response) node, response30);
this.mapNode(node, response30);
}
public void visitResponseDefinition(OpenApiResponse node) {
String name = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi30Components parent30 = this.getOrCreateComponents();
OpenApi30Response responseDef30 = (OpenApi30Response) parent30.createResponse();
parent30.addResponse(name, responseDef30);
this.transformResponse((OpenApi20Response) node, responseDef30);
this.copyExtensions((OpenApi20Response) node, responseDef30);
this.mapNode(node, responseDef30);
}
private void transformResponse(OpenApi20Response node, OpenApi30Response response30) {
response30.setDescription(node.getDescription());
if (!NodeUtil.isNullOrUndefined(node.getSchema())) {
List produces = this.findProduces(node);
OpenApi20Schema schema = node.getSchema();
produces.forEach( ct -> {
OpenApi30MediaType mediaType30 = response30.createMediaType();
response30.addContent(ct, mediaType30);
OpenApi30Schema schema30 = (OpenApi30Schema) mediaType30.createSchema();
mediaType30.setSchema(this.toSchema(schema, schema30, true));
if (!NodeUtil.isNullOrUndefined(node.getExamples())) {
JsonNode ctexample = node.getExamples().getItem(ct);
if (!NodeUtil.isNullOrUndefined(ctexample)) {
mediaType30.setExample(ctexample);
}
}
this.copyExtensions(schema, schema30);
this.mapNode(schema, schema30);
});
// mark the Response as needing Content post-processing
if (produces.size() > 1) {
this._postProcessResponses.add(response30);
}
}
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitSchema(Schema)
*/
@Override
public void visitSchema(Schema node) {
if (isSchemaDefinition(node)) {
visitSchemaDefinition((OpenApiSchema) node);
} else {
OpenApi20Schema schema20 = (OpenApi20Schema) node;
// It's a property schema
String propertyName = this.traversalContext.getMostRecentPropertyStep();
if ("properties".equals(propertyName)) {
visitPropertySchema(schema20);
} else if ("allOf".equals(propertyName)) {
visitAllOfSchema(schema20);
} else if ("items".equals(propertyName)) {
visitItemsSchema(schema20);
} else if ("additionalProperties".equals(propertyName)) {
visitAdditionalPropertiesSchema(schema20);
}
}
}
@Override
public void visitHeaders(OpenApi20Headers node) {
OpenApi30Response parent30 = (OpenApi30Response) this.lookup(node.parent());
// No processing to do here, because 3.0 doesn't have a "headers" node. So instead
// we'll map the headers node to the 3.0 response node, because that will be the
// effective parent for any 3.0 Header nodes.
this.mapNode(node, parent30);
}
@Override
public void visitHeader(OpenApiHeader node) {
OpenApi20Header header20 = (OpenApi20Header) node;
String headerName = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi30Response parent30 = (OpenApi30Response) this.lookup(node.parent());
OpenApi30Header header30 = parent30.createHeader();
parent30.addHeader(headerName, header30);
header30.setDescription(header20.getDescription());
header30.setSchema(this.toSchema(node, header30.createSchema(), false));
this.copyExtensions(header20, header30);
this.mapNode(node, header30);
}
@Override
public void visitExample(OpenApiExample node) {
// Examples are processed as part of "transformResponse"
}
@Override
public void visitItems(OpenApi20Items node) {
OpenApi30Schema parent30 = this.findItemsParent(node);
OpenApi30Schema items30 = parent30.createSchema();
parent30.setItems(items30);
this.toSchema(node, items30, false);
this.mapNode(node, items30);
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitTag(Tag)
*/
@Override
public void visitTag(Tag node) {
OpenApi30Document parent30 = this.doc30;
OpenApiTag tag30 = parent30.createTag();
tag30.setName(node.getName());
tag30.setDescription(node.getDescription());
parent30.addTag(tag30);
this.mapNode(node, tag30);
}
@Override
public void visitSecurityDefinitions(OpenApi20SecurityDefinitions node) {
// OpenAPI 3 has no "Security Definitions" wrapper entity.
}
/**
* @see io.apicurio.datamodels.models.visitors.Visitor#visitSecurityScheme(SecurityScheme)
*/
@Override
public void visitSecurityScheme(SecurityScheme node) {
String name = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi20SecurityScheme scheme = (OpenApi20SecurityScheme) node;
OpenApi30Components parent30 = this.getOrCreateComponents();
OpenApi30SecurityScheme scheme30 = (OpenApi30SecurityScheme) parent30.createSecurityScheme();
parent30.addSecurityScheme(name, scheme30);
scheme30.setType(scheme.getType());
scheme30.setDescription(scheme.getDescription());
scheme30.setName(scheme.getName());
scheme30.setIn(scheme.getIn());
if (NodeUtil.equals(scheme.getType(), "oauth2")) {
OpenApi30OAuthFlows flows30 = scheme30.createOAuthFlows();
if (NodeUtil.equals(scheme.getFlow(), "implicit")) {
scheme30.setFlows(flows30);
flows30.setImplicit(flows30.createOAuthFlow());
flows30.getImplicit().setAuthorizationUrl(scheme.getAuthorizationUrl());
if (!NodeUtil.isNullOrUndefined(scheme.getScopes())) {
((OpenApiOAuthFlow) flows30.getImplicit()).setScopes(new LinkedHashMap<>());
scheme.getScopes().getItemNames().forEach(scopeName -> {
((OpenApiOAuthFlow) flows30.getImplicit()).getScopes().put(scopeName, scheme.getScopes().getItem(scopeName));
});
}
}
if (NodeUtil.equals(scheme.getFlow(), "accessCode")) {
scheme30.setFlows(flows30);
flows30.setAuthorizationCode(flows30.createOAuthFlow());
flows30.getAuthorizationCode().setAuthorizationUrl(scheme.getAuthorizationUrl());
flows30.getAuthorizationCode().setTokenUrl(scheme.getTokenUrl());
if (!NodeUtil.isNullOrUndefined(scheme.getScopes())) {
((OpenApiOAuthFlow) flows30.getAuthorizationCode()).setScopes(new LinkedHashMap<>());
scheme.getScopes().getItemNames().forEach(scopeName -> {
((OpenApiOAuthFlow) flows30.getAuthorizationCode()).getScopes().put(scopeName, scheme.getScopes().getItem(scopeName));
});
}
}
if (NodeUtil.equals(scheme.getFlow(), "password")) {
scheme30.setFlows(flows30);
flows30.setPassword(flows30.createOAuthFlow());
flows30.getPassword().setTokenUrl(scheme.getTokenUrl());
if (!NodeUtil.isNullOrUndefined(scheme.getScopes())) {
((OpenApiOAuthFlow) flows30.getPassword()).setScopes(new LinkedHashMap<>());
scheme.getScopes().getItemNames().forEach(scopeName -> {
((OpenApiOAuthFlow) flows30.getPassword()).getScopes().put(scopeName, scheme.getScopes().getItem(scopeName));
});
}
}
if (NodeUtil.equals(scheme.getFlow(), "application")) {
scheme30.setFlows(flows30);
flows30.setClientCredentials(flows30.createOAuthFlow());
flows30.getClientCredentials().setTokenUrl(scheme.getTokenUrl());
if (!NodeUtil.isNullOrUndefined(scheme.getScopes())) {
((OpenApiOAuthFlow) flows30.getClientCredentials()).setScopes(new LinkedHashMap<>());
scheme.getScopes().getItemNames().forEach(scopeName -> {
((OpenApiOAuthFlow) flows30.getClientCredentials()).getScopes().put(scopeName, scheme.getScopes().getItem(scopeName));
});
}
}
}
this.mapNode(scheme, scheme30);
}
@Override
public void visitScopes(OpenApi20Scopes node) {
// Note: scopes are handled during the processing of the security scheme. See `visitSecurityScheme` for details.
}
@Override
public void visitXML(OpenApiXML node) {
OpenApi20XML xml20 = (OpenApi20XML) node;
OpenApi30Schema parent30 = (OpenApi30Schema) this.lookup(node.parent());
OpenApi30XML xml30 = (OpenApi30XML) parent30.createXML();
parent30.setXml(xml30);
xml30.setName(node.getName());
xml30.setNamespace(node.getNamespace());
xml30.setPrefix(node.getPrefix());
xml30.setAttribute(xml20.isAttribute());
xml30.setWrapped(node.isWrapped());
this.copyExtensions(xml20, xml30);
this.mapNode(node, xml30);
}
public void visitSchemaDefinition(OpenApiSchema node) {
String name = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi20Schema sd20 = (OpenApi20Schema) node;
OpenApi30Components parent30 = this.getOrCreateComponents();
OpenApi30Schema schemaDef30 = (OpenApi30Schema) parent30.createSchema();
parent30.addSchema(name, schemaDef30);
this.toSchema(sd20, schemaDef30, true);
this.copyExtensions(sd20, schemaDef30);
this.mapNode(sd20, schemaDef30);
}
public void visitPropertySchema(OpenApiSchema node) {
OpenApi20Schema ps20 = (OpenApi20Schema) node;
String name = (String) this.traversalContext.getMostRecentStep().getValue();
OpenApi30Schema parent30 = (OpenApi30Schema) this.lookup(ps20.parent());
OpenApi30Schema property30 = parent30.createSchema();
parent30.addProperty(name, property30);
this.toSchema(ps20, property30, true);
this.copyExtensions(ps20, property30);
this.mapNode(ps20, property30);
}
public void visitAdditionalPropertiesSchema(OpenApiSchema node) {
OpenApi30Schema parent30 = (OpenApi30Schema) this.lookup(node.parent());
OpenApi30Schema additionalProps30 = parent30.createSchema();
parent30.setAdditionalProperties(additionalProps30);
this.toSchema(node, additionalProps30, true);
this.copyExtensions((OpenApi20Schema) node, additionalProps30);
this.mapNode(node, additionalProps30);
}
public void visitAllOfSchema(OpenApiSchema node) {
OpenApi30Schema parent30 = (OpenApi30Schema) this.lookup(node.parent());
OpenApi30Schema allOf30 = parent30.createSchema();
parent30.addAllOf(allOf30);
this.toSchema(node, allOf30, true);
this.copyExtensions((OpenApi20Schema) node, allOf30);
this.mapNode(node, allOf30);
}
public void visitItemsSchema(OpenApiSchema node) {
OpenApi30Schema parent30 = (OpenApi30Schema) this.lookup(node.parent());
OpenApi30Schema items30 = parent30.getItems();
// Note: OpenAPI 3+ does not support an array of Schemas for the "items" property. So this
// part of the transformation is potentially lossy. We'll keep the first schema and drop the
// rest (if any).
if (items30 == null) {
items30 = parent30.createSchema();
parent30.setItems(items30);
}
this.toSchema(node, items30, true);
this.copyExtensions((OpenApi20Schema) node, items30);
this.mapNode(node, items30);
}
@Override
public void visitDefinitions(OpenApi20Definitions node) {
// Note: there is no "definitions" entity in 3.0, so nothing to do here.
}
@Override
public void visitParameterDefinitions(OpenApi20ParameterDefinitions node) {
// Note: there is no "parameters definitions" entity in 3.0, so nothing to do here.
}
@Override
public void visitResponseDefinitions(OpenApi20ResponseDefinitions node) {
// Note: there is no "responses definitions" entity in 3.0, so nothing to do here.
}
private boolean isParameterDefinition(Node node) {
return ("parameters".equals(this.traversalContext.getMostRecentPropertyStep()) && this.traversalContext.getAllSteps().size() == 2);
}
private boolean isSchemaDefinition(Node node) {
return ("definitions".equals(this.traversalContext.getMostRecentPropertyStep()) && this.traversalContext.getAllSteps().size() == 2);
}
private boolean isResponseDefinition(Node node) {
return ("responses".equals(this.traversalContext.getMostRecentPropertyStep()) && this.traversalContext.getAllSteps().size() == 2);
}
private void mapNode(Node from, Node to) {
NodePath nodePath = NodePathUtil.createNodePath(from);
String mapIndex = nodePath.toString();
this._nodeMap.put(mapIndex, to);
}
private Node lookup(Node node) {
NodePath nodePath = NodePathUtil.createNodePath(node);
String mapIndex = nodePath.toString();
return this._nodeMap.get(mapIndex);
}
private OpenApi30Components getOrCreateComponents() {
if (NodeUtil.isNullOrUndefined(this.doc30.getComponents())) {
this.doc30.setComponents(this.doc30.createComponents());
}
return this.doc30.getComponents();
}
// from : OpenApi20ParameterBase | OpenApi20Header | OpenApi20Items | OpenApi20Schema | OpenApi20SchemaDefinition
@SuppressWarnings("unchecked")
private OpenApi30Schema toSchema(Node from, OpenApi30Schema schema30, boolean isSchema) {
String type = (String) NodeUtil.getProperty(from, "type");
String format = (String) NodeUtil.getProperty(from, "format");
Object items = NodeUtil.getProperty(from, "items");
JsonNode default_ = (JsonNode) NodeUtil.getProperty(from, "default");
Number maximum = (Number) NodeUtil.getProperty(from, "maximum");
Boolean exclusiveMaximum = (Boolean) NodeUtil.getProperty(from, "exclusiveMaximum");
Number minimum = (Number) NodeUtil.getProperty(from, "minimum");
Boolean exclusiveMinimum = (Boolean) NodeUtil.getProperty(from, "exclusiveMinimum");
Integer maxLength = (Integer) NodeUtil.getProperty(from, "maxLength");
Integer minLength = (Integer) NodeUtil.getProperty(from, "minLength");
String pattern = (String) NodeUtil.getProperty(from, "pattern");
Integer maxItems = (Integer) NodeUtil.getProperty(from, "maxItems");
Integer minItems = (Integer) NodeUtil.getProperty(from, "minItems");
Boolean uniqueItems = (Boolean) NodeUtil.getProperty(from, "uniqueItems");
List enum_ = (List) NodeUtil.getProperty(from, "enum");
Number multipleOf = (Number) NodeUtil.getProperty(from, "multipleOf");
schema30.setType(type);
schema30.setFormat(format);
if (NodeUtil.equals(type, "file")) {
schema30.setType("string");
schema30.setFormat("binary");
}
if (NodeUtil.isNode(items)) {
// This is done so that we can lookup the appropriate parent for an "Items" object later
// on in the visit. This is a special case because we're introducing a new OpenApi30Schema
// entity in between e.g. a Response and the ItemsSchema - whereas in 2.0 the ItemsSchema
// is a direct child of Response and Parameter. So when visiting a Items, we cannot lookup
// the new 3.0 Schema using the Items' parent (because the parent maps to something else -
// the grandparent, in fact). THIS IS ONLY A PROBLEM FOR "ITEMS" ON PARAM AND RESPONSE.
((Node) items).setNodeAttribute("_transformation_items-parent", schema30);
} else {
// TODO handle the case where "items" is a list of items!!
}
// Note: Not sure what to do with the "collectionFormat" of a schema. Dropping it for now.
//schema30.collectionFormat = collectionFormat;
schema30.setDefault(default_);
schema30.setMaximum(maximum);
schema30.setExclusiveMaximum(exclusiveMaximum);
schema30.setMinimum(minimum);
schema30.setExclusiveMinimum(exclusiveMinimum);
schema30.setMaxLength(maxLength);
schema30.setMinLength(minLength);
schema30.setPattern(pattern);
schema30.setMaxItems(maxItems);
schema30.setMinItems(minItems);
schema30.setUniqueItems(uniqueItems);
schema30.setEnum(enum_);
schema30.setMultipleOf(multipleOf);
if (isSchema) {
OpenApi20Schema schema20 = (OpenApi20Schema) from;
schema30.set$ref(this.updateRef(schema20.get$ref()));
if (schema20.getAdditionalProperties() != null && schema20.getAdditionalProperties().isBoolean()) {
BooleanUnionValue booleanValue = new BooleanUnionValueImpl(schema20.getAdditionalProperties().asBoolean());
schema30.setAdditionalProperties(booleanValue);
}
schema30.setReadOnly(schema20.isReadOnly());
schema30.setExample(schema20.getExample());
schema30.setTitle(schema20.getTitle());
schema30.setDescription(schema20.getDescription());
schema30.setMaxProperties(schema20.getMaxProperties());
schema30.setMinProperties(schema20.getMinProperties());
schema30.setRequired(schema20.getRequired());
if (!NodeUtil.isNullOrUndefined(schema20.getDiscriminator())) {
OpenApi30Discriminator discriminator30 = schema30.createDiscriminator();
schema30.setDiscriminator(discriminator30);
discriminator30.setPropertyName(schema20.getDiscriminator());
}
}
return schema30;
}
private OpenApi30Schema findItemsParent(OpenApi20Items node) {
OpenApi30Schema itemsParent = (OpenApi30Schema) node.getNodeAttribute("_transformation_items-parent");
if (NodeUtil.isNullOrUndefined(itemsParent)) {
itemsParent = (OpenApi30Schema) this.lookup(node.parent());
}
return itemsParent;
}
private OpenApi20Operation findParentOperation(Parameter node) {
OperationFinder finder = new OperationFinder();
Library.visitTree(node, finder, TraverserDirection.up);
return (OpenApi20Operation) finder.found;
}
private List findProduces(Node node) {
ConsumesProducesFinder finder = new ConsumesProducesFinder();
Library.visitTree(node, finder, TraverserDirection.up);
List produces = finder.produces;
if (NodeUtil.isNullOrUndefined(produces) || produces.size() == 0) {
produces = new ArrayList<>();
produces.add("application/json");
}
return produces;
}
private List findConsumes(Node node) {
ConsumesProducesFinder finder = new ConsumesProducesFinder();
Library.visitTree(node, finder, TraverserDirection.up);
List consumes = finder.consumes;
if (NodeUtil.isNullOrUndefined(consumes) || consumes.size() == 0) {
consumes = new ArrayList<>();
consumes.add("application/json");
}
return consumes;
}
private void collectionFormatToStyleAndExplode(OpenApi20Parameter node, OpenApi30Parameter param30) {
if (NodeUtil.equals(node.getType(), "array") && NodeUtil.equals(node.getCollectionFormat(), "multi") && (NodeUtil.equals(node.getIn(), "query") || NodeUtil.equals(node.getIn(), "cookie"))) {
param30.setStyle("form");
param30.setExplode(true);
return;
}
if (NodeUtil.equals(node.getType(), "array") && NodeUtil.equals(node.getCollectionFormat(), "csv") && (NodeUtil.equals(node.getIn(), "query") || NodeUtil.equals(node.getIn(), "cookie"))) {
param30.setStyle("form");
param30.setExplode(false);
return;
}
if (NodeUtil.equals(node.getType(), "array") && NodeUtil.equals(node.getCollectionFormat(), "csv") && (NodeUtil.equals(node.getIn(), "path") || NodeUtil.equals(node.getIn(), "header"))) {
param30.setStyle("simple");
return;
}
if (NodeUtil.equals(node.getType(), "array") && NodeUtil.equals(node.getCollectionFormat(), "ssv") && NodeUtil.equals(node.getIn(), "query")) {
param30.setStyle("spaceDelimited");
return;
}
if (NodeUtil.equals(node.getType(), "array") && NodeUtil.equals(node.getCollectionFormat(), "pipes") && NodeUtil.equals(node.getIn(), "query")) {
param30.setStyle("pipeDelimited");
return;
}
}
private boolean isFormDataMimeType(String mimetype) {
return !NodeUtil.isNullOrUndefined(mimetype) && (NodeUtil.equals(mimetype, "multipart/form-data") || NodeUtil.equals(mimetype, "application/x-www-form-urlencoded"));
}
private boolean hasFormDataMimeType(List mimetypes) {
if (!NodeUtil.isNullOrUndefined(mimetypes)) {
for (String mt : mimetypes) {
if (this.isFormDataMimeType(mt)) {
return true;
}
}
}
return false;
}
private boolean isRef(Referenceable node) {
String $ref = node.get$ref();
return !NodeUtil.isNullOrUndefined($ref) && $ref.length() > 0;
}
private String updateRef(String $ref) {
if (NodeUtil.isNullOrUndefined($ref) || $ref.length() == 0) {
return $ref;
}
String[] split = $ref.split("/");
if (NodeUtil.equals(split[0], "#")) {
if (NodeUtil.equals(split[1], "definitions")) {
return $ref.replace("#/definitions/", "#/components/schemas/");
} else if (NodeUtil.equals(split[1], "parameters")) {
return $ref.replace("#/parameters/", "#/components/parameters/");
} else if (NodeUtil.equals(split[1], "responses")) {
return $ref.replace("#/responses/", "#/components/responses/");
}
}
return $ref;
}
/**
* Called when visiting is complete. Any additional processing of the result can
* be done here.
*/
private void postProcess() {
// Post process all of the responses that require it. Responses may require post-processing
// when a response has multiple @Produces content types, which results in multiple MimeType
// entities in the 3.0 Response 'content'. When this happens, only one of the mime types
// will contain the visited (and thus transformed) data model. So we must post-process them
// to "clone" that info to the other mime types. Otherwise we'll have a full mime type
// definition for only ONE of the mime types, and partial definitions for the rest.
this._postProcessResponses.forEach( response -> {
// First, figure out which of the media types is the largest (has the most content)
int largest = 0;
OpenApi30MediaType srcMt = null;
Collection mediaTypes = NodeUtil.getMapValues(response.getContent());
for (OpenApi30MediaType mt : mediaTypes) {
int size = JsonUtil.stringify(Library.writeNode(mt.getSchema())).length();
if (size > largest) {
largest = size;
srcMt = mt;
}
}
// Now clone the contents of the largest media type into all the others.
for (OpenApi30MediaType mt : mediaTypes) {
if (srcMt != mt) {
ObjectNode src = Library.writeNode(srcMt.getSchema());
Library.readNode(src, mt.getSchema());
}
}
});
}
private void copyExtensions(Extensible from, Extensible to) {
Map extensions = from.getExtensions();
if (extensions != null) {
Collection keys = extensions.keySet();
for (String key : keys) {
JsonNode value = extensions.get(key);
to.addExtension(key, value);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy