From 17db82279dac6d2b0d5c689391cecf5032bd8523 Mon Sep 17 00:00:00 2001 From: Maxim Kropotov Date: Tue, 10 Apr 2018 14:24:57 +0300 Subject: [PATCH] json schema: resolve array pointers, validate additional items Fixes WEB-32242 --- .../impl/JsonSchemaAnnotatorChecker.java | 3 + .../jsonSchema/impl/JsonSchemaObject.java | 47 +++++++++++- .../jsonSchema/impl/JsonSchemaReader.java | 4 +- .../JsonSchemaHighlightingTest.java | 72 +++++++++++++++++++ 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index d0dcce6997dd..90fef6f27e4e 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -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); + } } } } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java index 35309b44feff..2feb02fe389d 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java @@ -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 myDefinitionsMap; @@ -641,12 +644,54 @@ public class JsonSchemaObject { current = current.getProperties().get(parts.get(++i)); continue; } - + Pair 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 handleArrayItem(@NotNull String name, + List parts, + int i, + @Nullable JsonSchemaObject arraySchema, + @Nullable List 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; diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java index cd2bd5f4cdfa..72d8e2c613cb 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java @@ -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()); diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index e8f915d04896..dc254502917a 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -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, \"foo\"]"); + } + + 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, \"3\"]"); + } + + 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, \"2\"]"); + } + public static String rootObjectRedefinedSchema() { return "{\n" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" +