Skip to content

Commit

Permalink
Merge pull request #1659 from swagger-api/swos-443-yaml
Browse files Browse the repository at this point in the history
add location to messages for external refs
  • Loading branch information
gracekarina authored Feb 15, 2022
2 parents 8905eab + a6c1dc6 commit 5f63f2f
Show file tree
Hide file tree
Showing 18 changed files with 4,464 additions and 81 deletions.
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

0 comments on commit 5f63f2f

Please sign in to comment.