Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add location to messages for external refs #1659

Merged
merged 2 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class ParseOptions {
private boolean camelCaseFlattenNaming;
private boolean skipMatches;
private boolean allowEmptyStrings = true;
private boolean validateExternalRefs = false;
private boolean legacyYamlDeserialization = false;

public boolean isResolve() {
return resolve;
Expand Down Expand Up @@ -68,4 +70,23 @@ public boolean isAllowEmptyString() {
public void setAllowEmptyString(boolean allowEmptyStrings) {
this.allowEmptyStrings = allowEmptyStrings;
}

public boolean isValidateExternalRefs() {
return validateExternalRefs;
}

public void setValidateExternalRefs(boolean validateExternalRefs) {
this.validateExternalRefs = validateExternalRefs;
}

/**
* if set to true, triggers YAML deserialization as done up to 2.0.30, not supporting YAML Anchors safe resolution.
*/
public boolean isLegacyYamlDeserialization() {
return legacyYamlDeserialization;
}

public void setLegacyYamlDeserialization(boolean legacyYamlDeserialization) {
this.legacyYamlDeserialization = legacyYamlDeserialization;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.swagger.v3.parser.core.models;

import io.swagger.v3.oas.models.OpenAPI;
import java.util.Arrays;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

Expand All @@ -22,6 +23,22 @@ public void setMessages(List<String> messages) {
this.messages = messages;
}

public SwaggerParseResult message(String message) {
if (messages == null) {
messages = new ArrayList<>();
}
messages.add(message);
return this;
}

public SwaggerParseResult addMessages(List<String> messages) {
if (this.messages == null) {
this.messages = new ArrayList<>();
}
this.messages.addAll(messages);
return this;
}

public OpenAPI getOpenAPI() {
return openAPI;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import io.swagger.v3.parser.processors.ComponentsProcessor;
import io.swagger.v3.parser.processors.OperationProcessor;
import io.swagger.v3.parser.processors.PathsProcessor;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class OpenAPIResolver {

Expand All @@ -19,6 +23,11 @@ public class OpenAPIResolver {
private final PathsProcessor pathProcessor;
private final OperationProcessor operationsProcessor;
private Settings settings = new Settings();
private Set<String> resolveValidationMessages = new HashSet<>();

public ResolverCache getCache() {
return cache;
}

public OpenAPIResolver(OpenAPI openApi) {
this(openApi, null, null, null);
Expand All @@ -33,14 +42,28 @@ public OpenAPIResolver(OpenAPI openApi, List<AuthorizationValue> auths, String p
}

public OpenAPIResolver(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Settings settings) {
this(openApi, auths, parentFileLocation, settings, new ParseOptions());
}

public OpenAPIResolver(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Settings settings, ParseOptions parseOptions) {
this.openApi = openApi;
this.settings = settings != null ? settings : new Settings();
this.cache = new ResolverCache(openApi, auths, parentFileLocation);
this.cache = new ResolverCache(openApi, auths, parentFileLocation, resolveValidationMessages, parseOptions);
componentsProcessor = new ComponentsProcessor(openApi,this.cache);
pathProcessor = new PathsProcessor(cache, openApi,this.settings);
operationsProcessor = new OperationProcessor(cache, openApi);
}

public void resolve(SwaggerParseResult result) {

OpenAPI resolved = resolve();
if (resolved == null) {
return;
}
result.setOpenAPI(resolved);
result.getMessages().addAll(resolveValidationMessages);
}

public OpenAPI resolve() {
if (openApi == null) {
return null;
Expand All @@ -49,7 +72,6 @@ public OpenAPI resolve() {
pathProcessor.processPaths();
componentsProcessor.processComponents();


if(openApi.getPaths() != null) {
for(String pathname : openApi.getPaths().keySet()) {
PathItem pathItem = openApi.getPaths().get(pathname);
Expand All @@ -60,7 +82,6 @@ public OpenAPI resolve() {
}
}
}

return openApi;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.swagger.v3.parser.exception.EncodingNotSupportedException;
import io.swagger.v3.parser.exception.ReadContentException;
import io.swagger.v3.parser.util.ClasspathHelper;
import io.swagger.v3.parser.util.DeserializationUtils;
import io.swagger.v3.parser.util.InlineModelResolver;
import io.swagger.v3.parser.util.OpenAPIDeserializer;
import io.swagger.v3.parser.util.RemoteUrl;
Expand All @@ -23,7 +24,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -153,21 +153,41 @@ private SwaggerParseResult readContents(String swaggerAsString, List<Authorizati

try {
final ObjectMapper mapper = getRightMapper(swaggerAsString);
final JsonNode rootNode = mapper.readTree(swaggerAsString);
final SwaggerParseResult result;
JsonNode rootNode;
final SwaggerParseResult deserializationUtilsResult = new SwaggerParseResult();
if (options != null && options.isLegacyYamlDeserialization()) {
rootNode = mapper.readTree(swaggerAsString);
} else {
try {
rootNode = DeserializationUtils.deserializeIntoTree(swaggerAsString, location, options, deserializationUtilsResult);
} catch (Exception e) {
rootNode = mapper.readTree(swaggerAsString);
}
}

SwaggerParseResult result;
if (options != null) {
result = parseJsonNode(location, rootNode, options);
}else {
result = parseJsonNode(location, rootNode);
}
if (result.getOpenAPI() != null) {
return resolve(result, auth, options, location);
result = resolve(result, auth, options, location);
}
if (deserializationUtilsResult.getMessages() != null) {
for (String s: deserializationUtilsResult.getMessages()) {
result.message(getParseErrorMessage(s, location));
}
}
return result;
} catch (JsonProcessingException e) {
LOGGER.warn("Exception while parsing:", e);
final String message = getParseErrorMessage(e.getOriginalMessage(), location);
return SwaggerParseResult.ofError(message);
} catch (Exception e) {
LOGGER.warn("Exception while parsing:", e);
final String message = getParseErrorMessage(e.getMessage(), location);
return SwaggerParseResult.ofError(message);
}
}

Expand All @@ -181,8 +201,9 @@ private SwaggerParseResult resolve(SwaggerParseResult result, List<Authorization
try {
if (options != null) {
if (options.isResolve() || options.isResolveFully()) {
result.setOpenAPI(new OpenAPIResolver(result.getOpenAPI(), emptyListIfNull(auth),
location).resolve());
OpenAPIResolver resolver = new OpenAPIResolver(result.getOpenAPI(), emptyListIfNull(auth),
location, null, options);
resolver.resolve(result);
if (options.isResolveFully()) {
new ResolverFully(options.isResolveCombinators()).resolveFully(result.getOpenAPI());
}
Expand All @@ -198,7 +219,9 @@ private SwaggerParseResult resolve(SwaggerParseResult result, List<Authorization
}
} catch (Exception e) {
LOGGER.warn("Exception while resolving:", e);
result.setMessages(Collections.singletonList(e.getMessage()));
// TODO verify if this change makes sense (adding resolve messages instead of replacing)
result.getMessages().add(e.getMessage());
// result.setMessages(Collections.singletonList(e.getMessage()));
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.callbacks.Callback;
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.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import io.swagger.v3.parser.models.RefFormat;
import io.swagger.v3.parser.models.RefType;
import io.swagger.v3.parser.util.DeserializationUtils;
Expand All @@ -20,6 +28,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -57,17 +66,31 @@ public class ResolverCache {
private final String rootPath;
private Map<String, Object> resolutionCache = new HashMap<>();
private Map<String, String> externalFileCache = new HashMap<>();
private Set<String> referencedModelKeys = new HashSet<>();
private List<String> referencedModelKeys = new ArrayList<>();
private Set<String> resolveValidationMessages;

private final ParseOptions parseOptions;


/*
a map that stores original external references, and their associated renamed references
*/
private Map<String, String> renameCache = new HashMap<>();

public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation) {
this(openApi, auths, parentFileLocation, new HashSet<>());
}

public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Set<String> resolveValidationMessages) {
this(openApi, auths, parentFileLocation, resolveValidationMessages, new ParseOptions());
}

public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Set<String> resolveValidationMessages, ParseOptions parseOptions) {
this.openApi = openApi;
this.auths = auths;
this.rootPath = parentFileLocation;
this.resolveValidationMessages = resolveValidationMessages;
this.parseOptions = parseOptions;

if(parentFileLocation != null) {
if(parentFileLocation.startsWith("http") || parentFileLocation.startsWith("jar")) {
Expand Down Expand Up @@ -134,16 +157,26 @@ else if (rootPath != null) {
}
externalFileCache.put(file, contents);
}
SwaggerParseResult deserializationUtilResult = new SwaggerParseResult();
JsonNode tree = DeserializationUtils.deserializeIntoTree(contents, file, parseOptions, deserializationUtilResult);

if (definitionPath == null) {
T result = DeserializationUtils.deserialize(contents, file, expectedType);
T result = null;
if (parseOptions.isValidateExternalRefs()) {
result = deserializeFragment(tree, expectedType, file, "/");
} else {
result = DeserializationUtils.deserialize(contents, file, expectedType);
}
resolutionCache.put(ref, result);
if (deserializationUtilResult.getMessages() != null) {
if (this.resolveValidationMessages != null) {
this.resolveValidationMessages.addAll(deserializationUtilResult.getMessages());
}
}
return result;
}

//a definition path is defined, meaning we need to "dig down" through the JSON tree and get the desired entity
JsonNode tree = DeserializationUtils.deserializeIntoTree(contents, file);

String[] jsonPathElements = definitionPath.split("/");
for (String jsonPathElement : jsonPathElements) {
tree = tree.get(unescapePointer(jsonPathElement));
Expand All @@ -152,21 +185,62 @@ else if (rootPath != null) {
throw new RuntimeException("Could not find " + definitionPath + " in contents of " + file);
}
}

T result;
if (expectedType.equals(Schema.class)) {
OpenAPIDeserializer deserializer = new OpenAPIDeserializer();
result = (T) deserializer.getSchema((ObjectNode) tree, definitionPath.replace("/", "."), null);
T result = null;
if (parseOptions.isValidateExternalRefs()) {
result = deserializeFragment(tree, expectedType, file, definitionPath);
} else {
result = DeserializationUtils.deserialize(tree, file, expectedType);
if (expectedType.equals(Schema.class)) {
OpenAPIDeserializer deserializer = new OpenAPIDeserializer();
result = (T) deserializer.getSchema((ObjectNode) tree, definitionPath.replace("/", "."), null);
} else {
result = DeserializationUtils.deserialize(tree, file, expectedType);
}
}

updateLocalRefs(file, result);
resolutionCache.put(ref, result);

if (deserializationUtilResult.getMessages() != null) {
if (this.resolveValidationMessages != null) {
this.resolveValidationMessages.addAll(deserializationUtilResult.getMessages());
}
}
return result;
}

private <T> T deserializeFragment(JsonNode node, Class<T> expectedType, String file, String definitionPath) {
OpenAPIDeserializer deserializer = new OpenAPIDeserializer();
OpenAPIDeserializer.ParseResult parseResult = new OpenAPIDeserializer.ParseResult();
T result = null;
if (expectedType.equals(Schema.class)) {
result = (T) deserializer.getSchema((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
} else if (expectedType.equals(RequestBody.class)) {
result = (T) deserializer.getRequestBody((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
} else if (expectedType.equals(ApiResponse.class)) {
result = (T) deserializer.getResponse((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(Callback.class)) {
result = (T) deserializer.getCallback((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(Example.class)) {
result = (T) deserializer.getExample((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(Header.class)) {
result = (T) deserializer.getHeader((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(Link.class)) {
result = (T) deserializer.getLink((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(Parameter.class)) {
result = (T) deserializer.getParameter((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(SecurityScheme.class)) {
result = (T) deserializer.getSecurityScheme((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}else if (expectedType.equals(PathItem.class)) {
result = (T) deserializer.getPathItem((ObjectNode) node, definitionPath.replace("/", "."), parseResult);
}
parseResult.getMessages().forEach((m) -> {
resolveValidationMessages.add(m + " (" + file + ")");
});
if (result != null) {
return result;
}
// TODO ensure core deserialization exceptions get added to result messages resolveValidationMessages
return DeserializationUtils.deserialize(node, file, expectedType);
}

protected <T> void updateLocalRefs(String file, T result) {
if(result instanceof Parameter){
Parameter parameter = (Parameter)result;
Expand Down
Loading