Skip to content

Commit

Permalink
Merge pull request #358 from RepreZen/task/353
Browse files Browse the repository at this point in the history
[#353] Validation of external $ref property values should show error …
  • Loading branch information
tfesenko committed Oct 5, 2017
2 parents 246b11c + 00d892b commit 26f604d
Show file tree
Hide file tree
Showing 12 changed files with 658 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ private IRegion getTarget() throws CoreException {
} catch (IOException e) {
return null;
}
if (doc == null) {
return null;
}

return doc.getRegion(pointer);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
*******************************************************************************/
package com.reprezen.swagedit.core.json.references;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
Expand Down Expand Up @@ -54,51 +53,23 @@ public JsonDocumentManager() {
* @return JSON tree
*/
public JsonNode getDocument(URL url) {
if (documents.containsKey(url)) {
return documents.get(url);
}
URL normalized = normalize(url);

JsonNode document;
if (url.getFile().endsWith("json")) {
try {
document = mapper.readTree(url);
} catch (Exception e) {
document = null;
}
} else if (url.getFile().endsWith("yaml") || url.getFile().endsWith("yml")) {
try {
document = yamlMapper.readTree(url);
} catch (IOException e) {
document = null;
}
} else {
// cannot decide which format, so we try both parsers
try {
document = mapper.readTree(url);
} catch (Exception e) {
try {
document = yamlMapper.readTree(url);
} catch (IOException ee) {
document = null;
}
}
if (documents.containsKey(normalized)) {
return documents.get(normalized);
}

JsonNode document = parse(normalized);
if (document != null) {
documents.put(url, document);
documents.put(normalized, document);
}
return document;
}

public JsonNode getDocument(URI uri) {
final IFile file = getFile(uri);
if (file == null || !file.exists()) {
return null;
}

try {
return getDocument(uri.toURL());
} catch (MalformedURLException e) {
} catch (IllegalArgumentException | MalformedURLException e) {
return null;
}
}
Expand All @@ -114,4 +85,39 @@ public IFile getFile(URI uri) {
return uri != null ? DocumentUtils.getWorkspaceFile(uri) : null;
}

private JsonNode parse(URL url) {
if (url.getFile().endsWith("json")) {
try {
return mapper.readTree(url);
} catch (Exception e) {
return null;
}
} else if (url.getFile().endsWith("yaml") || url.getFile().endsWith("yml")) {
try {
return yamlMapper.readTree(url);
} catch (Exception e) {
return null;
}
} else {
// cannot decide which format, so we try both parsers
try {
return mapper.readTree(url);
} catch (Exception e) {
try {
return yamlMapper.readTree(url);
} catch (Exception ee) {
return null;
}
}
}
}

private URL normalize(URL url) {
try {
return new URL(url.getProtocol(), url.getHost(), url.getFile());
} catch (MalformedURLException e) {
return url;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public boolean isInvalid() {
}

/**
* Returns true if the reference points to an existing JSON document and the pointer to an existing node inside that
* document.
* Returns true if the reference cannot be resolved and the pointer points to an inexistent element.
*
* @param document
* @param baseURI
* @return true if the reference can be resolved.
*/
Expand All @@ -100,19 +100,13 @@ public boolean isMissing(JsonDocument document, URI baseURI) {
* If the resolution of the referenced node fails, this method returns null. If the pointer does not points to an
* existing node, this method will return a missing node (see JsonNode.isMissingNode()).
*
* @param document
* @param baseURI
* @return referenced node
*/
public JsonNode resolve(JsonDocument document, URI baseURI) {
if (resolved == null) {

final JsonNode doc;
if (isLocal()) {
doc = document.asJson();
} else {
doc = manager.getDocument(resolveURI(baseURI));
}

JsonNode doc = getDocument(document, baseURI);
if (doc != null) {
try {
resolved = doc.at(pointer);
Expand Down Expand Up @@ -223,4 +217,19 @@ public static JsonPointer getPointer(ObjectNode node) {
}
}

/**
* Returns the JSON document that contains the node referenced by this reference.
*
* @param document
* @param baseURI
* @return referenced node
*/
public JsonNode getDocument(JsonDocument document, URI baseURI) {
if (isLocal()) {
return document.asJson();
} else {
return manager.getDocument(resolveURI(baseURI));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import static com.reprezen.swagedit.core.json.references.JsonReference.PROPERTY;

import java.net.URI;
import java.util.Set;
import java.util.Map;

import com.google.common.collect.Sets;
import com.google.common.collect.Maps;
import com.reprezen.swagedit.core.model.AbstractNode;
import com.reprezen.swagedit.core.model.Model;

Expand All @@ -40,8 +40,8 @@ public JsonReferenceCollector(JsonReferenceFactory factory) {
* @param model
* @return all reference nodes
*/
public Iterable<JsonReference> collect(URI baseURI, Model model) {
final Set<JsonReference> references = Sets.newHashSet();
public Map<AbstractNode, JsonReference> collect(URI baseURI, Model model) {
final Map<AbstractNode, JsonReference> references = Maps.newHashMap();

for (AbstractNode node : model.allNodes()) {
if (JsonReference.isReference(node)) {
Expand All @@ -50,7 +50,7 @@ public Iterable<JsonReference> collect(URI baseURI, Model model) {
reference = factory.create(node);
}
if (reference != null) {
references.add(reference);
references.put(node, reference);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@

import java.net.URI;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IMarker;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Sets;
import com.reprezen.swagedit.core.editor.JsonDocument;
import com.reprezen.swagedit.core.model.AbstractNode;
import com.reprezen.swagedit.core.model.Model;
import com.reprezen.swagedit.core.schema.TypeDefinition;
import com.reprezen.swagedit.core.validation.Messages;
import com.reprezen.swagedit.core.validation.SwaggerError;

/**
Expand All @@ -45,9 +52,11 @@ public Collection<? extends SwaggerError> validate(URI baseURI, JsonDocument doc
}

protected Collection<? extends SwaggerError> doValidate(URI baseURI, JsonDocument doc,
Iterable<JsonReference> references) {
Map<AbstractNode, JsonReference> references) {
Set<SwaggerError> errors = Sets.newHashSet();
for (JsonReference reference : references) {
for (AbstractNode node : references.keySet()) {
JsonReference reference = references.get(node);

if (reference instanceof JsonReference.SimpleReference) {
errors.add(createReferenceError(SEVERITY_WARNING, Messages.warning_simple_reference, reference));
} else if (reference.isInvalid()) {
Expand All @@ -56,11 +65,68 @@ protected Collection<? extends SwaggerError> doValidate(URI baseURI, JsonDocumen
errors.add(createReferenceError(SEVERITY_WARNING, Messages.error_missing_reference, reference));
} else if (reference.containsWarning()) {
errors.add(createReferenceError(SEVERITY_WARNING, Messages.error_invalid_reference, reference));
} else {
validateType(doc, baseURI, node, reference, errors);
}
}
return errors;
}

/**
* This method checks that referenced objects are of expected type as defined in the schema.
*
* @param doc
* current document
* @param node
* node holding the reference
* @param reference
* actual reference
* @param errors
* current set of errors
*/
private void validateType(JsonDocument doc, URI baseURI, AbstractNode node, JsonReference reference,
Set<SwaggerError> errors) {
if (node == null) {
return;
}

Model model = doc.getModel();
TypeDefinition type = node.getType();

AbstractNode nodeValue = node.get(JsonReference.PROPERTY);
String pointer = (String) nodeValue.asValue().getValue();
AbstractNode valueNode = model.find(pointer);

if (valueNode == null) {
// Try to load the referenced node from an external document
JsonNode externalDoc = reference.getDocument(doc, baseURI);
if (externalDoc != null) {
try {
Model externalModel = Model.parse(model.getSchema(), externalDoc);

int pos = pointer.indexOf("#");
if (pos == -1) {
valueNode = externalModel.getRoot();
} else {
valueNode = externalModel.find(pointer.substring(pos, pointer.length()));
}

if (valueNode == null) {
return;
}
} catch (Exception e) {
// fail to parse the model or the pointer
return;
}
}
}

if (!type.validate(valueNode)) {
errors.add(
createReferenceError(IMarker.SEVERITY_WARNING, Messages.error_invalid_reference_type, reference));
}
}

protected SwaggerError createReferenceError(int severity, String message, JsonReference reference) {
Object source = reference.getSource();
int line;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,13 @@ public List<AbstractNode> findByType(JsonPointer typePointer) {
return instances;
}

/**
* Returns the schema associated to this model.
*
* @return schema
*/
public CompositeSchema getSchema() {
return schema;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
import com.reprezen.swagedit.core.model.Model;
import com.reprezen.swagedit.core.model.ObjectNode;
import com.reprezen.swagedit.core.model.ValueNode;
import com.reprezen.swagedit.core.schema.TypeDefinition;

/**
* This class contains methods for validating a Swagger YAML document.
Expand Down Expand Up @@ -179,7 +178,6 @@ protected Set<SwaggerError> validateModel(Model model) {
protected void executeModelValidation(Model model, AbstractNode node, Set<SwaggerError> errors) {
checkArrayTypeDefinition(errors, node);
checkObjectTypeDefinition(errors, node);
checkReferenceType(errors, node);
}

/**
Expand Down Expand Up @@ -295,32 +293,6 @@ protected void checkMissingRequiredProperties(Set<SwaggerError> errors, Abstract
}
}

/**
* This method checks that referenced objects are of expected type as defined in the schema.
*
* @param errors
* @param node
*/
protected void checkReferenceType(Set<SwaggerError> errors, AbstractNode node) {
if (JsonReference.isReference(node)) {
Model model = node.getModel();
TypeDefinition type = node.getType();

AbstractNode nodeValue = node.get(JsonReference.PROPERTY);
AbstractNode valueNode = model.find((String) nodeValue.asValue().getValue());

if (valueNode == null) {
// probably external node,
// do not validate for now.
return;
}

if (!type.validate(valueNode)) {
errors.add(error(nodeValue, IMarker.SEVERITY_WARNING, Messages.error_invalid_reference_type));
}
}
}

protected SwaggerError error(AbstractNode node, int level, String message) {
return new SwaggerError(node.getStart().getLine() + 1, level, message);
}
Expand Down
Loading

0 comments on commit 26f604d

Please sign in to comment.