WEB-23995 JSON schema: "type is not allowed" for valid schema and instance

This commit is contained in:
irengrig
2016-11-09 15:24:01 +01:00
parent 9943f26d5d
commit 47204fa8ee
4 changed files with 115 additions and 11 deletions
@@ -49,7 +49,7 @@ class JsonBySchemaObjectAnnotator implements Annotator {
final PsiFile psiFile = element.getContainingFile();
if (! (psiFile instanceof JsonFile)) return;
final JsonProperty firstProp = PsiTreeUtil.getParentOfType(element, JsonProperty.class, false);
final JsonProperty firstProp = getFirstProperty(element);
if (firstProp == null) {
checkRootObject(holder, element);
return;
@@ -90,8 +90,21 @@ class JsonBySchemaObjectAnnotator implements Annotator {
}
}
private static JsonProperty getFirstProperty(@NotNull PsiElement element) {
JsonProperty firstProp = PsiTreeUtil.getParentOfType(element, JsonProperty.class, false);
if (firstProp == null) {
final JsonObject firstObject = PsiTreeUtil.getParentOfType(element, JsonObject.class, false);
if (firstObject != null && firstObject.getParent() instanceof JsonValue) {
final List<JsonProperty> propertyList = firstObject.getPropertyList();
if (!propertyList.isEmpty()) firstProp = propertyList.get(0);
}
}
return firstProp;
}
private void checkRootObject(@NotNull AnnotationHolder holder, PsiElement property) {
final JsonObject object = PsiTreeUtil.getParentOfType(property, JsonObject.class);
JsonValue object = PsiTreeUtil.getParentOfType(property, JsonObject.class);
if (object == null) object = PsiTreeUtil.getParentOfType(property, JsonArray.class);
if (object != null) {
final BySchemaChecker rootChecker = new BySchemaChecker();
@@ -373,10 +386,13 @@ class JsonBySchemaObjectAnnotator implements Annotator {
return;
}
}
if (schema.getMultipleOf() != null) {
final double leftOver = value.doubleValue() % schema.getMultipleOf().doubleValue();
final Number multipleOf = schema.getMultipleOf();
if (multipleOf != null) {
final double leftOver = value.doubleValue() % multipleOf.doubleValue();
if (leftOver > 0.000001) {
error("Is not multiple of " + propValue.getText(), propValue);
final String multipleOfValue = String.valueOf(Math.abs(multipleOf.doubleValue() - multipleOf.intValue()) < 0.000001 ?
multipleOf.intValue() : multipleOf);
error("Is not multiple of " + multipleOfValue, propValue);
return;
}
}
@@ -60,7 +60,10 @@ public class JsonSchemaWalker {
if (checkable == null) return;
final boolean isName = isName(checkable);
final List<Step> position = findPosition(checkable, isName);
if (position == null || position.isEmpty()) return;
if (position == null || position.isEmpty()) {
if (isName) consumer.consume(true, rootSchema);
return;
}
extractSchemaVariants(consumer, rootSchema, isName, position);
}
@@ -73,8 +76,9 @@ public class JsonSchemaWalker {
final Pair<JsonSchemaObject, Integer> pair = queue.removeFirst();
final JsonSchemaObject schema = pair.getFirst();
if (position.size() <= pair.getSecond()) return;
final Step step = position.get(pair.getSecond());
if (step.getTransition() == null || (pair.getSecond() == (position.size() - 1))) {
if (step.getTransition() == null) {
consumer.consume(isName, schema);
continue;
}
@@ -109,7 +113,8 @@ public class JsonSchemaWalker {
step.getTransition().step(object, transitionResultConsumer);
// nothing or anything does not contribute to competion
if (transitionResultConsumer.getSchema() != null) {
queue.add(Pair.create(transitionResultConsumer.getSchema(), pair.getSecond() + 1));
if ((pair.getSecond() + 1) >= position.size()) consumer.consume(isName, transitionResultConsumer.getSchema());
else queue.add(Pair.create(transitionResultConsumer.getSchema(), pair.getSecond() + 1));
}
}
}
@@ -159,6 +164,7 @@ public class JsonSchemaWalker {
steps.add(new Step(StateType._value, null));
}
PsiElement current = element;
//PsiElement current = element instanceof JsonProperty ? ((JsonProperty)element).getNameElement() : element;
while (! (current instanceof PsiFile)) {
final PsiElement position = current;
current = current.getParent();
@@ -178,7 +184,16 @@ public class JsonSchemaWalker {
final String propertyName = ((JsonProperty)current).getName();
current = current.getParent();
if (!(current instanceof JsonObject)) return null;//incorrect syntax?
steps.add(new Step(StateType._object, new PropertyTransition(propertyName)));
// if either value or not first in the chain - needed for completion variant
if (position != element || !isName) {
steps.add(new Step(StateType._object, new PropertyTransition(propertyName)));
}
} else if (current instanceof JsonObject && position instanceof JsonProperty) {
// if either value or not first in the chain - needed for completion variant
if (position != element || !isName) {
final String propertyName = ((JsonProperty)position).getName();
steps.add(new Step(StateType._object, new PropertyTransition(propertyName)));
}
} else if (current instanceof PsiFile) {
break;
} else {
@@ -50,7 +50,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
public void testNumberMultipleWrong() throws Exception {
testImpl("{ \"properties\": { \"prop\": {\"type\": \"number\", \"multipleOf\": 2}}}",
"{ \"prop\": <warning descr=\"Is not multiple of 3\">3</warning>}");
"{ \"prop\": <warning descr=\"Is not multiple of 2\">3</warning>}");
}
public void testNumberMultipleCorrect() throws Exception {
@@ -96,6 +96,27 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
testImpl(schema, "{\"prop\": [<warning descr=\"Type is not allowed\">\"test\"</warning>]}");
}
public void testTopLevelArray() throws Exception {
final String schema = "{\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\", \"minimum\": 18" +
" }\n" +
"}";
testImpl(schema, "[101, 102]");
}
public void testTopLevelObjectArray() throws Exception {
final String schema = "{\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"object\", \"properties\": {\"a\": {\"type\": \"number\"}}" +
" }\n" +
"}";
testImpl(schema, "[{\"a\": <warning descr=\"Type is not allowed\">true</warning>}]");
testImpl(schema, "[{\"a\": 18}]");
}
public void testArrayTuples1() throws Exception {
final String schema = schema("{\n" +
" \"type\": \"array\",\n" +
@@ -10,7 +10,6 @@ import org.junit.Assert;
import java.io.StringReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
@@ -99,6 +98,59 @@ public class JsonBySchemaCompletionTest extends CompletionTestCase {
testImpl(schema, "{\"second\": <caret>}", "33", "44");
}
public void testValueCompletion() throws Exception {
final String schema = "{\n" +
" \"properties\": {\n" +
" \"top\": {\n" +
" \"enum\": [\"test\", \"me\"]\n" +
" }\n" +
" }\n" +
"}";
testImpl(schema, "{\"top\": <caret>}", "\"me\"", "\"test\"");
}
public void testTopLevelArrayPropNameCompletion() throws Exception {
final String schema = parcelShopSchema();
testImpl(schema, "[{<caret>}]", "\"address\"");
testImpl(schema, "[{\"address\": {<caret>}}]", "\"fax\"", "\"houseNumber\"");
testImpl(schema, "[{\"address\": {\"houseNumber\": <caret>}}]", "1", "2");
}
@NotNull
private static String parcelShopSchema() {
return "{\n" +
" \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" +
"\n" +
" \"title\": \"parcelshop search response schema\",\n" +
"\n" +
" \"definitions\": {\n" +
" \"address\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"houseNumber\": { \"type\": \"integer\", \"enum\": [1,2]},\n" +
" \"fax\": { \"$ref\": \"#/definitions/phone\" }\n" +
" }\n" +
" },\n" +
" \"phone\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"countryPrefix\": { \"type\": \"string\" },\n" +
" \"number\": { \"type\": \"string\" }\n" +
" }\n" +
" }\n" +
" },\n" +
"\n" +
" \"type\": \"array\",\n" +
"\n" +
" \"items\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"address\": { \"$ref\": \"#/definitions/address\" }\n" +
" }\n" +
" }\n" +
"}";
}
private void testImpl(@NotNull final String schema, final @NotNull String text,
final @NotNull String... variants) throws Exception {
final int position = EditorTestUtil.getCaretPosition(text);