Skip to content
Draft
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ SwaggerParseResult result = new OpenAPIParser().readContents("./path/to/swagger.

the Swagger/OpenAPI 2.0 document will be first converted into a comparable OpenAPI 3.0 one.

You can also directly use `OpenAPIV3Parser` which only handles OpenAPI 3.0 documents, and provides a convenience method to get directly the parsed `OpenAPI object:
You can also directly use `OpenAPIV3Parser` which handles OpenAPI 3.x documents, and provides a convenience method to get directly the parsed `OpenAPI` object.

Note: OpenAPI 3.2 documents are currently parsed through the OpenAPI 3.1 model. Elements introduced in 3.2 (e.g. the `query` operation, `additionalOperations`, `$self`, `in: querystring` parameters) are reported as validation messages but are not reproduced in the parsed model, so resolved output can differ from what a fully 3.2-aware parser would produce.

```java
import io.swagger.v3.parser.OpenAPIV3Parser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class SwaggerParseResult {
private List<String> messages = null;
private OpenAPI openAPI;
private boolean openapi31;
private boolean openapi32;

public SwaggerParseResult messages(List<String> messages) {
this.messages = messages;
Expand Down Expand Up @@ -68,16 +69,29 @@ public boolean isOpenapi31() {
return this.openapi31;
}

public void setOpenapi32(boolean openapi32) {
this.openapi32 = openapi32;
}

public SwaggerParseResult openapi32(boolean openapi32) {
this.openapi32 = openapi32;
return this;
}

public boolean isOpenapi32() {
return this.openapi32;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SwaggerParseResult result = (SwaggerParseResult) o;
return openapi31 == result.openapi31 && Objects.equals(messages, result.messages) && Objects.equals(openAPI, result.openAPI);
return openapi31 == result.openapi31 && openapi32 == result.openapi32 && Objects.equals(messages, result.messages) && Objects.equals(openAPI, result.openAPI);
}

@Override
public int hashCode() {
return Objects.hash(messages, openAPI, openapi31);
return Objects.hash(messages, openAPI, openapi31, openapi32);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private SwaggerParseResult resolve(SwaggerParseResult result, List<Authorization
try {
if (options != null) {
if (options.isResolve() || options.isResolveFully()) {
if (result.getOpenAPI().getOpenapi() != null && result.getOpenAPI().getOpenapi().startsWith("3.1")) {
if (result.getOpenAPI().getOpenapi() != null && (result.getOpenAPI().getOpenapi().startsWith("3.1") || result.getOpenAPI().getOpenapi().startsWith("3.2"))) {
if (StringUtils.isBlank(System.getenv(DISABLE_OAS31_RESOLVE))) {
DereferencerContext dereferencerContext = new DereferencerContext(
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String par
}

public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Set<String> resolveValidationMessages, ParseOptions parseOptions) {
this.openapi31 = openApi != null && openApi.getOpenapi() != null && openApi.getOpenapi().startsWith("3.1");
this.openapi31 = openApi != null && openApi.getOpenapi() != null && (openApi.getOpenapi().startsWith("3.1") || openApi.getOpenapi().startsWith("3.2"));
this.openApi = openApi;
this.auths = auths;
this.rootPath = parentFileLocation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.core.util.Json32;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
Expand Down Expand Up @@ -110,6 +111,7 @@ public Components traverseComponents(Components components, Visitor visitor) {
traverseMap(resolved.getHeaders(), visitor, this::traverseHeader);
traverseMap(resolved.getLinks(), visitor, this::traverseLink);
traverseMap(resolved.getResponses(), visitor, this::traverseResponse);
traverseMap(resolved.getMediaTypes(), visitor, this::traverseMediaType);

traverseMap(resolved.getExamples(), visitor, this::traverseExample);
visitedMap.put(components, resolved);
Expand Down Expand Up @@ -308,6 +310,12 @@ public PathItem traversePathItem(PathItem pathItem, Visitor visitor) {
if (resolvedOperation != null) {
resolved.setTrace(resolvedOperation);
}
Operation queryOp = resolved.getQuery();
resolvedOperation = traverseOperation(queryOp, visitor);
if (resolvedOperation != null) {
resolved.setQuery(resolvedOperation);
}
traverseMap(resolved.getAdditionalOperations(), visitor, this::traverseOperation);

if (resolved.getParameters() != null) {
for (int i = 0; i < resolved.getParameters().size(); i++) {
Expand Down Expand Up @@ -409,7 +417,15 @@ public MediaType traverseMediaType(MediaType mediaType, Visitor visitor) {
if (resolved.getSchema() != null) {
traverseSchema(resolved.getSchema(), visitor, new ArrayList<>());
}
if (resolved.getItemSchema() != null) {
traverseSchema(resolved.getItemSchema(), visitor, new ArrayList<>());
}
traverseMap(resolved.getEncoding(), visitor, this::traverseEncoding);
traverseEncodingList(resolved.getPrefixEncoding(), visitor);
Encoding itemEncoding = traverseEncoding(resolved.getItemEncoding(), visitor);
if (itemEncoding != null) {
resolved.setItemEncoding(itemEncoding);
}
traverseMap(resolved.getExamples(), visitor, this::traverseExample);
visitedMap.put(mediaType, resolved);
visiting.remove(mediaType);
Expand All @@ -431,12 +447,30 @@ public Encoding traverseEncoding(Encoding encoding, Visitor visitor) {
Encoding resolved = encoding;

traverseMap(resolved.getHeaders(), visitor, this::traverseHeader);
traverseMap(resolved.getEncoding(), visitor, this::traverseEncoding);
traverseEncodingList(resolved.getPrefixEncoding(), visitor);
Encoding itemEncoding = traverseEncoding(resolved.getItemEncoding(), visitor);
if (itemEncoding != null) {
resolved.setItemEncoding(itemEncoding);
}
visitedMap.put(encoding, resolved);
visiting.remove(encoding);
return resolved;

}

private void traverseEncodingList(List<Encoding> encodings, Visitor visitor) {
if (encodings == null) {
return;
}
for (int i = 0; i < encodings.size(); i++) {
Encoding resolved = traverseEncoding(encodings.get(i), visitor);
if (resolved != null) {
encodings.set(i, resolved);
}
}
}

public Header traverseHeader(Header header, Visitor visitor) {
if (header == null) {
return null;
Expand Down Expand Up @@ -520,7 +554,8 @@ public Schema traverseSchema(Schema schema, Visitor visitor, List<String> inheri
resolvedURI = ReferenceUtils.resolve(urlWithoutHash, resolvedURI);
resolvedURI = ReferenceUtils.toBaseURI(resolvedURI);
}
context.getIdsCache().put(resolvedURI, Json31.pretty(schema));
context.getIdsCache().put(resolvedURI,
isOpenapi32() ? Json32.pretty(schema) : Json31.pretty(schema));
} catch (Exception e) {
//
}
Expand Down Expand Up @@ -639,9 +674,18 @@ public Schema traverseSchema(Schema schema, Visitor visitor, List<String> inheri

}

private boolean isOpenapi32() {
return context != null && context.getSwaggerParseResult() != null
&& context.getSwaggerParseResult().isOpenapi32();
}

private com.fasterxml.jackson.databind.ObjectMapper modelMapper() {
return isOpenapi32() ? Json32.mapper() : Json31.mapper();
}

public <T> T deepcopy(T entity, Class<T> clazz) {
try {
return (T)Json31.mapper().readValue(Json31.mapper().writeValueAsString(entity), clazz);
return (T)modelMapper().readValue(modelMapper().writeValueAsString(entity), clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.core.util.Json32;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
Expand Down Expand Up @@ -70,7 +71,8 @@ public OpenAPI traverse(OpenAPI openAPI, Visitor visitor) throws Exception {
public <T> T deserializeFragment(JsonNode node, Class<T> expectedType, String uri, String fragment, Set<String> validationMessages) {

String sanitizedFragment = fragment == null ? "" : fragment;
OpenAPIDeserializer.ParseResult parseResult = new OpenAPIDeserializer.ParseResult().openapi31(true);
OpenAPIDeserializer.ParseResult parseResult = new OpenAPIDeserializer.ParseResult()
.openapi31(true).openapi32(isOpenapi32());
T result = null;
if (expectedType.equals(Schema.class)) {
result = (T) deserializer.getSchema((ObjectNode) node, sanitizedFragment.replace("/", "."), parseResult);
Expand All @@ -92,6 +94,8 @@ public <T> T deserializeFragment(JsonNode node, Class<T> expectedType, String ur
result = (T) deserializer.getSecurityScheme((ObjectNode) node, sanitizedFragment.replace("/", "."), parseResult);
}else if (expectedType.equals(PathItem.class)) {
result = (T) deserializer.getPathItem((ObjectNode) node, sanitizedFragment.replace("/", "."), parseResult);
}else if (expectedType.equals(MediaType.class)) {
result = (T) deserializer.getMediaType((ObjectNode) node, sanitizedFragment.replace("/", "."), parseResult);
}
parseResult.getMessages().forEach((m) -> {
validationMessages.add(m + " (" + uri + ")");
Expand Down Expand Up @@ -170,6 +174,7 @@ public Components traverseComponents(Components components, ReferenceVisitor vis
traverseMap(resolved.getHeaders(), visitor, this::traverseHeader);
traverseMap(resolved.getLinks(), visitor, this::traverseLink);
traverseMap(resolved.getResponses(), visitor, this::traverseResponse);
traverseMap(resolved.getMediaTypes(), visitor, this::traverseMediaType);

traverseMap(resolved.getExamples(), visitor, this::traverseExample);
visitedMap.put(components, resolved);
Expand Down Expand Up @@ -430,6 +435,12 @@ public PathItem traversePathItem(PathItem pathItem, ReferenceVisitor visitor) {
if (resolvedOperation != null) {
resolved.setTrace(resolvedOperation);
}
Operation queryOp = resolved.getQuery();
resolvedOperation = traverseOperation(queryOp, visitor);
if (resolvedOperation != null) {
resolved.setQuery(resolvedOperation);
}
traverseMap(resolved.getAdditionalOperations(), visitor, this::traverseOperation);

if (resolved.getParameters() != null) {
for (int i = 0; i < resolved.getParameters().size(); i++) {
Expand Down Expand Up @@ -462,6 +473,15 @@ public PathItem traversePathItem(PathItem pathItem, ReferenceVisitor visitor) {
if (pathItem.getSummary() != null) {
resolved.summary(pathItem.getSummary());
}
if (pathItem.getQuery() != null) {
resolved.setQuery(pathItem.getQuery());
}
if (pathItem.getAdditionalOperations() != null) {
if (resolved.getAdditionalOperations() == null) {
resolved.setAdditionalOperations(new LinkedHashMap<>());
}
resolved.getAdditionalOperations().putAll(pathItem.getAdditionalOperations());
}
// TODO additional undefined merge if other props are defined

visitedMap.put(pathItem, deepcopy(resolved, PathItem.class));
Expand Down Expand Up @@ -595,8 +615,12 @@ public MediaType traverseMediaType(MediaType mediaType, ReferenceVisitor visitor
visiting.add(mediaType);
MediaType resolved = visitor.visitMediaType(mediaType);

boolean resolvedNotNull = false;

if (resolved == null) {
resolved = mediaType;
} else {
resolvedNotNull = true;
}

if (resolved.getSchema() != null) {
Expand All @@ -605,9 +629,33 @@ public MediaType traverseMediaType(MediaType mediaType, ReferenceVisitor visitor
resolved.setSchema(schema);
}
}
if (resolved.getItemSchema() != null) {
Schema itemSchema = traverseSchema(resolved.getItemSchema(), visitor, new ArrayList<>());
if (itemSchema != null) {
resolved.setItemSchema(itemSchema);
}
}
traverseMap(resolved.getEncoding(), visitor, this::traverseEncoding);
if (resolved.getPrefixEncoding() != null) {
for (int i = 0; i < resolved.getPrefixEncoding().size(); i++) {
Encoding prefixEncoding = traverseEncoding(resolved.getPrefixEncoding().get(i), visitor);
if (prefixEncoding != null) {
resolved.getPrefixEncoding().set(i, prefixEncoding);
}
}
}
traverseEncoding(resolved.getItemEncoding(), visitor);
traverseMap(resolved.getExamples(), visitor, this::traverseExample);
visitedMap.put(mediaType, resolved);

// only if this is root and local ref
if (shouldHandleRootLocalRefs(resolvedNotNull, mediaType.get$ref(), visitor)) {
ensureComponents(context.getOpenApi());
if (context.getOpenApi().getComponents().getMediaTypes() == null) context.getOpenApi().getComponents().mediaTypes(new LinkedHashMap<>());
visitedMap.put(mediaType, deepcopy(mediaType, MediaType.class));
visiting.remove(mediaType);
return handleRootLocalRefs(mediaType.get$ref(), resolved, context.getOpenApi().getComponents().getMediaTypes());
}
visitedMap.put(mediaType, deepcopy(resolved, MediaType.class));
visiting.remove(mediaType);
return resolved;
}
Expand All @@ -630,6 +678,16 @@ public Encoding traverseEncoding(Encoding encoding, ReferenceVisitor visitor) {
resolved = encoding;
}
traverseMap(resolved.getHeaders(), visitor, this::traverseHeader);
traverseMap(resolved.getEncoding(), visitor, this::traverseEncoding);
if (resolved.getPrefixEncoding() != null) {
for (int i = 0; i < resolved.getPrefixEncoding().size(); i++) {
Encoding prefixEncoding = traverseEncoding(resolved.getPrefixEncoding().get(i), visitor);
if (prefixEncoding != null) {
resolved.getPrefixEncoding().set(i, prefixEncoding);
}
}
}
traverseEncoding(resolved.getItemEncoding(), visitor);
visitedMap.put(encoding, resolved);
visiting.remove(encoding);
return resolved;
Expand Down Expand Up @@ -793,7 +851,8 @@ public Schema traverseSchema(Schema schema, ReferenceVisitor visitor, List<Strin
resolvedURI = ReferenceUtils.resolve(urlWithoutHash, resolvedURI);
resolvedURI = ReferenceUtils.toBaseURI(resolvedURI);
}
context.getIdsCache().put(resolvedURI, Json31.pretty(schema));
context.getIdsCache().put(resolvedURI,
isOpenapi32() ? Json32.pretty(schema) : Json31.pretty(schema));
if (!visitor.reference.getReferenceSet().containsKey(resolvedURI)) {
visitor.reference.getReferenceSet().put(resolvedURI, visitor.reference);
}
Expand Down Expand Up @@ -937,9 +996,18 @@ public Schema traverseSchema(Schema schema, ReferenceVisitor visitor, List<Strin

}

private boolean isOpenapi32() {
return context != null && context.getSwaggerParseResult() != null
&& context.getSwaggerParseResult().isOpenapi32();
}

private com.fasterxml.jackson.databind.ObjectMapper modelMapper() {
return isOpenapi32() ? Json32.mapper() : Json31.mapper();
}

public <T> T deepcopy(T entity, Class<T> clazz) {
try {
return (T)Json31.mapper().readValue(Json31.mapper().writeValueAsString(entity), clazz);
return (T)modelMapper().readValue(modelMapper().writeValueAsString(entity), clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.core.util.Json32;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -15,7 +16,7 @@
public class OpenAPIDereferencer31 implements OpenAPIDereferencer {

public boolean canDereference(DereferencerContext context) {
if (context.openApi != null && context.openApi.getOpenapi().startsWith("3.1")) {
if (context.openApi != null && context.openApi.getOpenapi() != null && (context.openApi.getOpenapi().startsWith("3.1") || context.openApi.getOpenapi().startsWith("3.2"))) {
return true;
}
return false;
Expand Down Expand Up @@ -50,7 +51,8 @@ public void dereference(DereferencerContext context, Iterator<OpenAPIDereference
.referenceSet(refSet)
.uri(context.getCurrentUri())
.messages(msgs)
.jsonNode(Json31.mapper().convertValue(openAPI, JsonNode.class))
.jsonNode((result != null && result.isOpenapi32() ? Json32.mapper() : Json31.mapper())
.convertValue(openAPI, JsonNode.class))
.auths(context.getAuths());

refSet.put("local", localReference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.links.Link;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
Expand Down Expand Up @@ -205,6 +206,15 @@ public Header visitHeader(Header header){
return resolveRef(header, header.get$ref(), Header.class, openAPITraverser::traverseHeader);
}

@Override
public MediaType visitMediaType(MediaType mediaType){
if (StringUtils.isBlank(mediaType.get$ref())) {
return null;
}

return resolveRef(mediaType, mediaType.get$ref(), MediaType.class, openAPITraverser::traverseMediaType);
}

@Override
public String readHttp(String uri, List<AuthorizationValue> auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception {
if(context.getParseOptions().isSafelyResolveURL()){
Expand Down
Loading