From fb1677fcd2eedd3ce82bb93e3aabe52dbc067abd Mon Sep 17 00:00:00 2001 From: "Irina.Chernushina" Date: Tue, 8 Aug 2017 14:16:34 +0200 Subject: [PATCH] json schema, matching schema search: we can not compare combined schemas - ... using their existing equals method, because schemas created as a result of merge are pointing to one of the parts of the merged participants, which in turn can be the same for different schema combinations - solution: use found schema merges as as, in future write separate compare method which would pay attention to all schema details (it could theoretically be a problem only for oneOf validation when only one schema match is allowed) - see test data for detailed explanation --- .../jsonSchema/impl/JsonSchemaResolver.java | 6 +- .../jsonSchema/impl/MatchResult.java | 8 +- .../impl/JsonBySchemaCompletionTest.java | 243 --------------- .../impl/JsonBySchemaCompletionTest.kt | 287 ++++++++++++++++++ 4 files changed, 294 insertions(+), 250 deletions(-) delete mode 100644 json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.java create mode 100644 json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.kt diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaResolver.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaResolver.java index 2f635c73dc09..dfbc89a2c225 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaResolver.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaResolver.java @@ -61,9 +61,9 @@ public class JsonSchemaResolver { private Collection resolve(boolean skipLastExpand, boolean literalResolve, boolean acceptAdditionalPropertiesSchema) { final MatchResult result = detailedResolve(skipLastExpand, literalResolve, acceptAdditionalPropertiesSchema); - final Set set = new HashSet<>(result.mySchemas); - set.addAll(result.myExcludingSchemas.stream().flatMap(Set::stream).collect(Collectors.toSet())); - return set; + final List list = new ArrayList<>(result.mySchemas); + list.addAll(result.myExcludingSchemas.stream().flatMap(Set::stream).collect(Collectors.toSet())); + return list; } @Nullable diff --git a/json/src/com/jetbrains/jsonSchema/impl/MatchResult.java b/json/src/com/jetbrains/jsonSchema/impl/MatchResult.java index 7a900281cfea..10b770a6dfc1 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/MatchResult.java +++ b/json/src/com/jetbrains/jsonSchema/impl/MatchResult.java @@ -25,16 +25,16 @@ import java.util.*; * @author Irina.Chernushina on 4/22/2017. */ public class MatchResult { - public final Set mySchemas; + public final List mySchemas; public final List> myExcludingSchemas; - private MatchResult(@NotNull final Set schemas, @NotNull final List> excludingSchemas) { - mySchemas = Collections.unmodifiableSet(schemas); + private MatchResult(@NotNull final List schemas, @NotNull final List> excludingSchemas) { + mySchemas = Collections.unmodifiableList(schemas); myExcludingSchemas = Collections.unmodifiableList(excludingSchemas); } public static MatchResult create(@NotNull JsonSchemaTreeNode root) { - final Set schemas = new HashSet<>(); + final List schemas = new ArrayList<>(); final Map> oneOfGroups = new HashMap<>(); ContainerUtil.process(new JBTreeTraverser(node -> node.getChildren()).withRoot(root).preOrderDfsTraversal(), node -> { diff --git a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.java b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.java deleted file mode 100644 index 631165f20686..000000000000 --- a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.jetbrains.jsonSchema.impl; - -import com.intellij.codeInsight.lookup.LookupElementPresentation; -import com.jetbrains.jsonSchema.JsonSchemaHighlightingTest; -import org.jetbrains.annotations.NotNull; -import org.junit.Assert; - -/** - * @author Irina.Chernushina on 10/1/2015. - */ -public class JsonBySchemaCompletionTest extends JsonBySchemaCompletionBaseTest { - public void testTopLevel() throws Exception { - testImpl("{\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}", "{}", "\"prima\"", "\"primus\"", "\"proto\""); - } - - public void testTopLevelVariant() throws Exception { - testImpl("{\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}", "{\"pri\"}", "prima", "primus", "proto"); - } - - public void testBoolean() throws Exception { - testImpl("{\"properties\": {\"prop\": {\"type\": \"boolean\"}}}", "{\"prop\": }", "false", "true"); - } - - public void testEnum() throws Exception { - testImpl("{\"properties\": {\"prop\": {\"enum\": [\"prima\", \"proto\", \"primus\"]}}}", - "{\"prop\": }", "\"prima\"", "\"primus\"", "\"proto\""); - } - - public void testTopLevelAnyOfValues() throws Exception { - testImpl("{\"properties\": {\"prop\": {\"anyOf\": [{\"enum\": [\"prima\", \"proto\", \"primus\"]}," + - "{\"type\": \"boolean\"}]}}}", - "{\"prop\": }", "\"prima\"", "\"primus\"", "\"proto\"", "false", "true"); - } - - public void testTopLevelAnyOf() throws Exception { - testImpl("{\"anyOf\": [ {\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}," + - "{\"properties\": {\"abrakadabra\": {}}}]}", - "{}", "\"abrakadabra\"", "\"prima\"", "\"primus\"", "\"proto\""); - } - - public void testSimpleHierarchy() throws Exception { - testImpl("{\"properties\": {\"top\": {\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}}}", - "{\"top\": {}}", "\"prima\"", "\"primus\"", "\"proto\""); - } - - public void testObjectsInsideArray() throws Exception { - final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\", \"items\": {\"type\": \"object\"," + - "\"properties\": {" + - "\"innerType\":{}, \"innerValue\":{}" + - "}, \"additionalProperties\": false" + - "}}}}"; - testImpl(schema, "{\"prop\": [{}]}", "\"innerType\"", "\"innerValue\""); - } - - public void testObjectValuesInsideArray() throws Exception { - final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\", \"items\": {\"type\": \"object\"," + - "\"properties\": {" + - "\"innerType\":{\"enum\": [115,117, \"nothing\"]}, \"innerValue\":{}" + - "}, \"additionalProperties\": false" + - "}}}}"; - testImpl(schema, "{\"prop\": [{\"innerType\": }]}", "\"nothing\"", "115", "117"); - } - - public void testLowLevelOneOf() throws Exception { - final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\", \"items\": {\"type\": \"object\"," + - "\"properties\": {" + - "\"innerType\":{\"oneOf\": [" + - "{\"properties\": {\"a1\": {}, \"a2\": {}}}" + "," + - "{\"properties\": {\"b1\": {}, \"b2\": {}}}" + - "]}, \"innerValue\":{}" + - "}, \"additionalProperties\": false" + - "}}}}"; - testImpl(schema, "{\"prop\": [{\"innerType\": {}}]}", "\"a1\"", "\"a2\"", "\"b1\"", "\"b2\""); - } - - public void testArrayValuesInsideObject() throws Exception { - final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\"," + - "\"items\": {\"enum\": [1,2,3]}}}}"; - testImpl(schema, "{\"prop\": []}", "1", "2", "3"); - } - - public void testAllOfTerminal() throws Exception { - final String schema = "{\"allOf\": [{\"type\": \"object\", \"properties\": {\"first\": {}}}," + - " {\"properties\": {\"second\": {\"enum\": [33,44]}}}]}"; - testImpl(schema, "{\"\"}", "first", "second"); - } - - public void testAllOfInTheMiddle() throws Exception { - final String schema = "{\"allOf\": [{\"type\": \"object\", \"properties\": {\"first\": {}}}," + - " {\"properties\": {\"second\": {\"enum\": [33,44]}}}]}"; - testImpl(schema, "{\"second\": }", "33", "44"); - } - - public void testValueCompletion() throws Exception { - final String schema = "{\n" + - " \"properties\": {\n" + - " \"top\": {\n" + - " \"enum\": [\"test\", \"me\"]\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{\"top\": }", "\"me\"", "\"test\""); - } - - public void testTopLevelArrayPropNameCompletion() throws Exception { - final String schema = parcelShopSchema(); - testImpl(schema, "[{}]", "\"address\""); - testImpl(schema, "[{\"address\": {}}]", "\"fax\"", "\"houseNumber\""); - testImpl(schema, "[{\"address\": {\"houseNumber\": }}]", "1", "2"); - } - - public void testPatternPropertyCompletion() throws Exception { - final String schema = "{\n" + - " \"patternProperties\": {\n" + - " \"C\": {\n" + - " \"enum\": [\"test\", \"em\"]\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{\"Cyan\": }", "\"em\"", "\"test\""); - } - - public void testRootObjectRedefined() throws Exception { - testImpl(JsonSchemaHighlightingTest.rootObjectRedefinedSchema(), "{}", - "\"r1\"", "\"r2\""); - } - - public void testSimpleNullCompletion() throws Exception { - final String schema = "{\n" + - " \"properties\": {\n" + - " \"null\": {\n" + - " \"type\": \"null\"\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{\"null\": }", "null"); - } - - public void testNullCompletionInEnum() throws Exception { - final String schema = "{\n" + - " \"properties\": {\n" + - " \"null\": {\n" + - " \"type\": [\"null\", \"integer\"],\n" + - " \"enum\": [null, 1, 2]\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{\"null\": }", "1", "2", "null"); - } - - public void testNullCompletionInTypeVariants() throws Exception { - final String schema = "{\n" + - " \"properties\": {\n" + - " \"null\": {\n" + - " \"type\": [\"null\", \"boolean\"]\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{\"null\": }", "false", "null", "true"); - } - - public void testDescriptionFromDefinitionInCompletion() throws Exception { - final String schema = "{\n" + - " \"definitions\": {\n" + - " \"target\": {\n" + - " \"description\": \"Target description\"\n" + - " }\n" + - " },\n" + - " \"properties\": {\n" + - " \"source\": {\n" + - " \"$ref\": \"#/definitions/target\"\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{}", "\"source\""); - Assert.assertEquals(1, myItems.length); - final LookupElementPresentation presentation = new LookupElementPresentation(); - myItems[0].renderElement(presentation); - Assert.assertEquals("Target description", presentation.getTypeText()); - } - - public void testDescriptionFromTitleInCompletion() throws Exception { - final String schema = "{\n" + - " \"definitions\": {\n" + - " \"target\": {\n" + - " \"title\": \"Target title\",\n" + - " \"description\": \"Target description\"\n" + - " }\n" + - " },\n" + - " \"properties\": {\n" + - " \"source\": {\n" + - " \"$ref\": \"#/definitions/target\"\n" + - " }\n" + - " }\n" + - "}"; - testImpl(schema, "{}", "\"source\""); - Assert.assertEquals(1, myItems.length); - final LookupElementPresentation presentation = new LookupElementPresentation(); - myItems[0].renderElement(presentation); - Assert.assertEquals("Target title", presentation.getTypeText()); - } - - @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" + - "}"; - } - - @SuppressWarnings("TestMethodWithIncorrectSignature") - private void testImpl(@NotNull final String schema, final @NotNull String text, - final @NotNull String... variants) throws Exception { - testBySchema(schema, text, ".json", variants); - } -} diff --git a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.kt b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.kt new file mode 100644 index 000000000000..6d82c77f1fd3 --- /dev/null +++ b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaCompletionTest.kt @@ -0,0 +1,287 @@ +package com.jetbrains.jsonSchema.impl + +import com.intellij.codeInsight.lookup.LookupElementPresentation +import com.jetbrains.jsonSchema.JsonSchemaHighlightingTest +import org.junit.Assert + +/** + * @author Irina.Chernushina on 10/1/2015. + */ +class JsonBySchemaCompletionTest : JsonBySchemaCompletionBaseTest() { + @Throws(Exception::class) + fun testTopLevel() { + testImpl("""{"properties": {"prima": {}, "proto": {}, "primus": {}}}""", "{}", "\"prima\"", "\"primus\"", "\"proto\"") + } + + @Throws(Exception::class) + fun testTopLevelVariant() { + testImpl("""{"properties": {"prima": {}, "proto": {}, "primus": {}}}""", "{\"pri\"}", "prima", "primus", "proto") + } + + @Throws(Exception::class) + fun testBoolean() { + testImpl("""{"properties": {"prop": {"type": "boolean"}}}""", "{\"prop\": }", "false", "true") + } + + @Throws(Exception::class) + fun testEnum() { + testImpl("""{"properties": {"prop": {"enum": ["prima", "proto", "primus"]}}}""", + """{"prop": }""", "\"prima\"", "\"primus\"", "\"proto\"") + } + + @Throws(Exception::class) + fun testTopLevelAnyOfValues() { + testImpl("""{"properties": {"prop": {"anyOf": [{"enum": ["prima", "proto", "primus"]},""" + "{\"type\": \"boolean\"}]}}}", + """{"prop": }""", "\"prima\"", "\"primus\"", "\"proto\"", "false", "true") + } + + @Throws(Exception::class) + fun testTopLevelAnyOf() { + testImpl( + """{"anyOf": [ {"properties": {"prima": {}, "proto": {}, "primus": {}}},""" + "{\"properties\": {\"abrakadabra\": {}}}]}", + """{}""", "\"abrakadabra\"", "\"prima\"", "\"primus\"", "\"proto\"") + } + + @Throws(Exception::class) + fun testSimpleHierarchy() { + testImpl("""{"properties": {"top": {"properties": {"prima": {}, "proto": {}, "primus": {}}}}}""", + """{"top": {}}""", "\"prima\"", "\"primus\"", "\"proto\"") + } + + @Throws(Exception::class) + fun testObjectsInsideArray() { + val schema = """{"properties": {"prop": {"type": "array", "items": + {"type": "object","properties": {"innerType":{}, "innerValue":{}}, "additionalProperties": false}}}}""" + testImpl(schema, """{"prop": [{}]}""", "\"innerType\"", "\"innerValue\"") + } + + @Throws(Exception::class) + fun testObjectValuesInsideArray() { + val schema = """{"properties": {"prop": {"type": "array", "items": + {"type": "object","properties": {"innerType":{"enum": [115,117, "nothing"]}, "innerValue":{}}, "additionalProperties": false}}}}""" + testImpl(schema, """{"prop": [{"innerType": }]}""", "\"nothing\"", "115", "117") + } + + @Throws(Exception::class) + fun testLowLevelOneOf() { + val schema = """{"properties": {"prop": {"type": "array", "items": + {"type": "object","properties": {"innerType":{"oneOf": [{"properties": {"a1": {}, "a2": {}}}, + {"properties": {"b1": {}, "b2": {}}}]}, "innerValue":{}}, "additionalProperties": false}}}}""" + testImpl(schema, """{"prop": [{"innerType": {}}]}""", "\"a1\"", "\"a2\"", "\"b1\"", "\"b2\"") + } + + @Throws(Exception::class) + fun testArrayValuesInsideObject() { + val schema = """{"properties": {"prop": {"type": "array","items": {"enum": [1,2,3]}}}}""" + testImpl(schema, """{"prop": []}""", "1", "2", "3") + } + + @Throws(Exception::class) + fun testAllOfTerminal() { + val schema = """{"allOf": [{"type": "object", "properties": {"first": {}}}, {"properties": {"second": {"enum": [33,44]}}}]}""" + testImpl(schema, """{""}""", "first", "second") + } + + @Throws(Exception::class) + fun testAllOfInTheMiddle() { + val schema = """{"allOf": [{"type": "object", "properties": {"first": {}}}, {"properties": {"second": {"enum": [33,44]}}}]}""" + testImpl(schema, """{"second": }""", "33", "44") + } + + @Throws(Exception::class) + fun testValueCompletion() { + val schema = """{ + "properties": { + "top": { + "enum": ["test", "me"] + } + } +}""" + testImpl(schema, """{"top": }""", "\"me\"", "\"test\"") + } + + @Throws(Exception::class) + fun testTopLevelArrayPropNameCompletion() { + val schema = parcelShopSchema() + testImpl(schema, "[{}]", "\"address\"") + testImpl(schema, """[{"address": {}}]""", "\"fax\"", "\"houseNumber\"") + testImpl(schema, """[{"address": {"houseNumber": }}]""", "1", "2") + } + + @Throws(Exception::class) + fun testPatternPropertyCompletion() { + val schema = """{ + "patternProperties": { + "C": { + "enum": ["test", "em"] + } + } +}""" + testImpl(schema, """{"Cyan": }""", "\"em\"", "\"test\"") + } + + @Throws(Exception::class) + fun testRootObjectRedefined() { + testImpl(JsonSchemaHighlightingTest.rootObjectRedefinedSchema(), "{}", "\"r1\"", "\"r2\"") + } + + @Throws(Exception::class) + fun testSimpleNullCompletion() { + val schema = """{ + "properties": { + "null": { + "type": "null" + } + } +}""" + testImpl(schema, """{"null": }""", "null") + } + + @Throws(Exception::class) + fun testNullCompletionInEnum() { + val schema = """{ + "properties": { + "null": { + "type": ["null", "integer"], + "enum": [null, 1, 2] + } + } +}""" + testImpl(schema, """{"null": }""", "1", "2", "null") + } + + @Throws(Exception::class) + fun testNullCompletionInTypeVariants() { + val schema = """{ + "properties": { + "null": { + "type": ["null", "boolean"] + } + } +}""" + testImpl(schema, """{"null": }""", "false", "null", "true") + } + + @Throws(Exception::class) + fun testDescriptionFromDefinitionInCompletion() { + val schema = """{ + "definitions": { + "target": { + "description": "Target description" + } + }, + "properties": { + "source": { + "${"$"}ref": "#/definitions/target" + } + } +}""" + testImpl(schema, "{}", "\"source\"") + Assert.assertEquals(1, myItems.size.toLong()) + val presentation = LookupElementPresentation() + myItems[0].renderElement(presentation) + Assert.assertEquals("Target description", presentation.typeText) + } + + @Throws(Exception::class) + fun testDescriptionFromTitleInCompletion() { + val schema = """{ + "definitions": { + "target": { + "title": "Target title", + "description": "Target description" + } + }, + "properties": { + "source": { + "${"$"}ref": "#/definitions/target" + } + } +}""" + testImpl(schema, "{}", "\"source\"") + Assert.assertEquals(1, myItems.size.toLong()) + val presentation = LookupElementPresentation() + myItems[0].renderElement(presentation) + Assert.assertEquals("Target title", presentation.typeText) + } + + @Throws(Exception::class) + fun testAnyOfInsideAllOfWithInnerProperties() { + val schema = """ +{ + "definitions": { + "aaa": { + "properties": { + "aaa_prop": {} + } + }, + "bbb": { + "properties": { + "bbb_prop": {} + } + }, + "excl1": { + "properties": { + "excl1_prop": {} + } + }, + "excl2": { + "properties": { + "excl2_prop": {} + } + } + }, + "allOf": [ + {"${"$"}ref": "#/definitions/aaa"}, + {"${"$"}ref": "#/definitions/bbb"}, + { + "anyOf": [ + {"${"$"}ref": "#/definitions/excl1"}, + {"${"$"}ref": "#/definitions/excl2"} + ] + } + ] +}""" + testImpl(schema, "{}", "\"aaa_prop\"", "\"bbb_prop\"", "\"excl1_prop\"", "\"excl2_prop\"") + } + + private fun parcelShopSchema(): String { + return """{ + "${"$"}schema": "http://json-schema.org/draft-04/schema#", + + "title": "parcelshop search response schema", + + "definitions": { + "address": { + "type": "object", + "properties": { + "houseNumber": { "type": "integer", "enum": [1,2]}, + "fax": { "${"$"}ref": "#/definitions/phone" } + } + }, + "phone": { + "type": "object", + "properties": { + "countryPrefix": { "type": "string" }, + "number": { "type": "string" } + } + } + }, + + "type": "array", + + "items": { + "type": "object", + "properties": { + "address": { "${"$"}ref": "#/definitions/address" } + } + } +}""" + } + + @Throws(Exception::class) + private fun testImpl(schema: String, text: String, + vararg variants: String) { + testBySchema(schema, text, ".json", *variants) + } +}