From db6ed8d66a3fc0c95c23994c34a93be70bd7b6ed Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Tue, 31 Jul 2018 12:38:02 +0200 Subject: [PATCH] WEB-34103 JSON schema validation for enums of arrays/objects giving incorrect warning --- .../impl/EnumArrayValueWrapper.java | 25 +++++++++ .../impl/EnumObjectValueWrapper.java | 25 +++++++++ .../impl/JsonSchemaAnnotatorChecker.java | 52 ++++++++++++++++--- .../jsonSchema/impl/JsonSchemaReader.java | 11 +++- .../JsonSchemaHighlightingTest.java | 30 +++++++++++ 5 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 json/src/com/jetbrains/jsonSchema/impl/EnumArrayValueWrapper.java create mode 100644 json/src/com/jetbrains/jsonSchema/impl/EnumObjectValueWrapper.java diff --git a/json/src/com/jetbrains/jsonSchema/impl/EnumArrayValueWrapper.java b/json/src/com/jetbrains/jsonSchema/impl/EnumArrayValueWrapper.java new file mode 100644 index 000000000000..70f66a56d60d --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/EnumArrayValueWrapper.java @@ -0,0 +1,25 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.jsonSchema.impl; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class EnumArrayValueWrapper { + @NotNull private final Object[] myValues; + + public EnumArrayValueWrapper(@NotNull Object[] values) { + myValues = values; + } + + @NotNull + public Object[] getValues() { + return myValues; + } + + @Override + public String toString() { + return "[" + Arrays.stream(myValues).map(v -> v.toString()).collect(Collectors.joining(", ")) + "]"; + } +} diff --git a/json/src/com/jetbrains/jsonSchema/impl/EnumObjectValueWrapper.java b/json/src/com/jetbrains/jsonSchema/impl/EnumObjectValueWrapper.java new file mode 100644 index 000000000000..b8da41344176 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/EnumObjectValueWrapper.java @@ -0,0 +1,25 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.jsonSchema.impl; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.stream.Collectors; + +public class EnumObjectValueWrapper { + @NotNull private final Map myValues; + + public EnumObjectValueWrapper(@NotNull Map values) { + myValues = values; + } + + @NotNull + public Map getValues() { + return myValues; + } + + @Override + public String toString() { + return "{" + myValues.entrySet().stream().map(v -> "\"" + v.getKey() + "\": " + v.getValue()).collect(Collectors.joining(", ")) + "}"; + } +} diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index 2867d3143ae1..5979b0c89c9d 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -456,6 +456,51 @@ class JsonSchemaAnnotatorChecker { return position; } + private static boolean checkEnumValue(@NotNull Object object, + @NotNull JsonLikePsiWalker walker, + @Nullable JsonValueAdapter adapter, + @NotNull String text, + @NotNull BiFunction stringEq) { + if (object instanceof EnumArrayValueWrapper) { + if (adapter instanceof JsonArrayValueAdapter) { + List elements = ((JsonArrayValueAdapter)adapter).getElements(); + Object[] values = ((EnumArrayValueWrapper)object).getValues(); + if (elements.size() == values.length) { + for (int i = 0; i < values.length; i++) { + if (!checkEnumValue(values[i], walker, elements.get(i), walker.getNodeTextForValidation(elements.get(i).getDelegate()), stringEq)) return false; + } + return true; + } + } + } + else if (object instanceof EnumObjectValueWrapper) { + if (adapter instanceof JsonObjectValueAdapter) { + List props = ((JsonObjectValueAdapter)adapter).getPropertyList(); + Map values = ((EnumObjectValueWrapper)object).getValues(); + if (props.size() == values.size()) { + for (JsonPropertyAdapter prop : props) { + if (!values.containsKey(prop.getName())) return false; + JsonValueAdapter value = prop.getValue(); + if (value == null) continue; + if (!checkEnumValue(values.get(prop.getName()), walker, value, walker.getNodeTextForValidation(value.getDelegate()), stringEq)) return false; + } + + return true; + } + } + } + else { + if (walker.onlyDoubleQuotesForStringLiterals()) { + if (stringEq.apply(object.toString(), text)) return true; + } + else { + if (equalsIgnoreQuotes(object.toString(), text, walker.quotesForStringLiterals(), stringEq)) return true; + } + } + + return false; + } + private void checkForEnum(PsiElement value, JsonSchemaObject schema) { //enum values + pattern -> don't check enum values if (schema.getEnum() == null || schema.getPattern() != null) return; @@ -465,12 +510,7 @@ class JsonSchemaAnnotatorChecker { final List objects = schema.getEnum(); BiFunction eq = myOptions.isCaseInsensitiveEnumCheck() ? String::equalsIgnoreCase : String::equals; for (Object object : objects) { - if (walker.onlyDoubleQuotesForStringLiterals()) { - if (eq.apply(object.toString(), text)) return; - } - else { - if (equalsIgnoreQuotes(object.toString(), text, walker.quotesForStringLiterals(), eq)) return; - } + if (checkEnumValue(object, walker, walker.createValueAdapter(value), text, eq)) return; } error("Value should be one of: [" + StringUtil.join(objects, o -> o.toString(), ", ") + "]", value, JsonValidationError.FixableIssueKind.NonEnumValue, null, JsonErrorPriority.MEDIUM_PRIORITY); diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java index f34b3eb5a7c1..fe494844d3bc 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java @@ -5,6 +5,7 @@ import com.intellij.json.psi.*; import com.intellij.notification.NotificationGroup; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -297,6 +298,13 @@ public class JsonSchemaReader { return ((JsonBooleanLiteral)value).getValue(); } else if (value instanceof JsonNullLiteral) { return "null"; + } else if (value instanceof JsonArray) { + return new EnumArrayValueWrapper(((JsonArray)value).getValueList().stream().map(v -> readEnumValue(v)).filter(v -> v != null).toArray()); + } else if (value instanceof JsonObject) { + return new EnumObjectValueWrapper(((JsonObject)value).getPropertyList().stream() + .map(p -> Pair.create(p.getName(), readEnumValue(p.getValue()))) + .filter(p -> p.second != null) + .collect(Collectors.toMap(p -> p.first, p -> p.second))); } return null; } @@ -308,7 +316,8 @@ public class JsonSchemaReader { final List list = ((JsonArray)element).getValueList(); for (JsonValue value : list) { Object enumValue = readEnumValue(value); - if (enumValue != null) objects.add(enumValue); + if (enumValue == null) return; // don't validate if we have unsupported entity kinds + objects.add(enumValue); } object.setEnum(objects); } diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index 31e22b620f82..989915a05fa3 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -891,4 +891,34 @@ public class JsonSchemaHighlightingTest extends JsonSchemaHighlightingTestBase { " \"name\": \"aA\"\n" + "}"); } + + public void testEnumArrayValue() throws Exception { + @Language("JSON") String schema = "{\n" + + " \"properties\": {\n" + + " \"foo\": {\n" + + " \"enum\": [ [{\"x\": 5}, [true], \"q\"] ]\n" + + " }\n" + + " }\n" + + "}"; + doTest(schema, "{\"foo\": 5}"); + doTest(schema, "{\"foo\": [ ]}"); + doTest(schema, "{\"foo\": [{\"x\": 5}]}"); + doTest(schema, "{\"foo\": [{\"x\": 5}, true]}"); + doTest(schema, "{\"foo\": [{\"x\": 5}, [true]]}"); + doTest(schema, "{\"foo\": [ { \"x\" : 5 } , [ true ] , \"q\" ]}"); + } + + public void testEnumObjectValue() throws Exception { + @Language("JSON") String schema = "{\n" + + " \"properties\": {\n" + + " \"foo\": {\n" + + " \"enum\": [ {\"x\": 5} ]\n" + + " }\n" + + " }\n" + + "}"; + doTest(schema, "{\"foo\": {}}"); + doTest(schema, "{\"foo\": {\"x\": 4}}"); + doTest(schema, "{\"foo\": {\"x\": true}}"); + doTest(schema, "{\"foo\": { \r \"x\" : \t 5 \n }}"); + } }