json schema: resolve array pointers, validate additional items

Fixes WEB-32242
This commit is contained in:
Maxim Kropotov
2018-04-10 16:48:52 +03:00
parent c3b534b28c
commit 17db82279d
4 changed files with 123 additions and 3 deletions
@@ -457,6 +457,9 @@ class JsonSchemaAnnotatorChecker {
if (!Boolean.TRUE.equals(schema.getAdditionalItemsAllowed())) {
error("Additional items are not allowed", arrayValue.getDelegate());
}
else if (schema.getAdditionalItemsSchema() != null) {
checkObjectBySchemaRecordErrors(schema.getAdditionalItemsSchema(), arrayValue);
}
}
}
}
@@ -9,6 +9,7 @@ import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -28,6 +29,8 @@ import java.util.stream.Collectors;
public class JsonSchemaObject {
@NonNls public static final String DEFINITIONS = "definitions";
@NonNls public static final String PROPERTIES = "properties";
@NonNls public static final String ITEMS = "items";
@NonNls public static final String ADDITIONAL_ITEMS = "additionalItems";
@NonNls public static final String X_INTELLIJ_HTML_DESCRIPTION = "x-intellij-html-description";
@NotNull private final JsonObject myJsonObject;
@Nullable private Map<String, JsonSchemaObject> myDefinitionsMap;
@@ -641,12 +644,54 @@ public class JsonSchemaObject {
current = current.getProperties().get(parts.get(++i));
continue;
}
Pair<JsonSchemaObject, Integer> arrayResult = ObjectUtils.coalesce(
handleArrayItem(ITEMS, parts, i, current.getItemsSchema(), current.getItemsSchemaList()),
handleArrayItem(ADDITIONAL_ITEMS, parts, i, current.getAdditionalItemsSchema(), ContainerUtil.emptyList()));
if (arrayResult != null) {
//noinspection AssignmentToForLoopParameter
i = arrayResult.second;
current = arrayResult.first;
continue;
}
current = current.getDefinitionsMap() == null ? null : current.getDefinitionsMap().get(part);
}
return current;
}
@Nullable
private static Pair<JsonSchemaObject, Integer> handleArrayItem(@NotNull String name,
List<String> parts,
int i,
@Nullable JsonSchemaObject arraySchema,
@Nullable List<JsonSchemaObject> arraySchemaList) {
final String part = parts.get(i);
if (!name.equals(part)) {
return null;
}
if (i == (parts.size() - 1)) {
if (arraySchema != null) {
return Pair.create(arraySchema, i);
}
}
else {
Integer next = tryParseInt(parts.get(++i));
if (arraySchemaList != null && next != null && next >= 0 && next < arraySchemaList.size()) {
return Pair.create(arraySchemaList.get(next), i);
}
}
return null;
}
@Nullable
private static Integer tryParseInt(String s) {
try {
return Integer.parseInt(s);
}
catch (Exception __) {
return null;
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
@@ -155,8 +155,8 @@ public class JsonSchemaReader {
READERS_MAP.put("pattern", (element, object, queue) -> {
if (element instanceof JsonStringLiteral) object.setPattern(StringUtil.unquoteString(element.getText()));
});
READERS_MAP.put("additionalItems", createAdditionalItems());
READERS_MAP.put("items", createItems());
READERS_MAP.put(JsonSchemaObject.ADDITIONAL_ITEMS, createAdditionalItems());
READERS_MAP.put(JsonSchemaObject.ITEMS, createItems());
READERS_MAP.put("contains", createContains());
READERS_MAP.put("maxItems", (element, object, queue) -> {
if (element instanceof JsonNumberLiteral) object.setMaxItems((int)((JsonNumberLiteral)element).getValue());
@@ -546,6 +546,78 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
doTest(schema, "{\"withFormat\": \"localhost\"}");
}
public void testArrayItemReference() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"items\": [\n" +
" {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" {\n" +
" \"$ref\": \"#/items/0\"\n" +
" }\n" +
" ]\n" +
"}";
doTest(schema, "[1, 2]");
doTest(schema, "[1, <warning>\"foo\"</warning>]");
}
public void testArrayReference() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"definitions\": {\n" +
" \"options\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"items\":{\n" +
" \"$ref\": \"#/definitions/options/items\"\n" +
" }\n" +
" \n" +
"}";
doTest(schema, "[2, 3 ,4]");
doTest(schema, "[2, <warning>\"3\"</warning>]");
}
public void testSelfArrayReferenceDoesNotThrowSOE() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"items\": [\n" +
" {\n" +
" \"$ref\": \"#/items/0\"\n" +
" }\n" +
" ]\n" +
"}";
doTest(schema, "[]");
}
public void testValidateAdditionalItems() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"definitions\": {\n" +
" \"options\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"items\": [\n" +
" {\n" +
" \"type\": \"boolean\"\n" +
" },\n" +
" {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" ],\n" +
" \"additionalItems\": {\n" +
" \"$ref\": \"#/definitions/options/items\"\n" +
" }\n" +
"}";
doTest(schema, "[true, true]");
doTest(schema, "[true, true, 1, 2, 3]");
doTest(schema, "[true, true, 1, <warning>\"2\"</warning>]");
}
public static String rootObjectRedefinedSchema() {
return "{\n" +
" \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" +