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
This commit is contained in:
Irina.Chernushina
2017-08-08 14:17:46 +02:00
parent 36a3503c9b
commit fb1677fcd2
4 changed files with 294 additions and 250 deletions
@@ -61,9 +61,9 @@ public class JsonSchemaResolver {
private Collection<JsonSchemaObject> resolve(boolean skipLastExpand, boolean literalResolve, boolean acceptAdditionalPropertiesSchema) {
final MatchResult result = detailedResolve(skipLastExpand, literalResolve, acceptAdditionalPropertiesSchema);
final Set<JsonSchemaObject> set = new HashSet<>(result.mySchemas);
set.addAll(result.myExcludingSchemas.stream().flatMap(Set::stream).collect(Collectors.toSet()));
return set;
final List<JsonSchemaObject> list = new ArrayList<>(result.mySchemas);
list.addAll(result.myExcludingSchemas.stream().flatMap(Set::stream).collect(Collectors.toSet()));
return list;
}
@Nullable
@@ -25,16 +25,16 @@ import java.util.*;
* @author Irina.Chernushina on 4/22/2017.
*/
public class MatchResult {
public final Set<JsonSchemaObject> mySchemas;
public final List<JsonSchemaObject> mySchemas;
public final List<Set<JsonSchemaObject>> myExcludingSchemas;
private MatchResult(@NotNull final Set<JsonSchemaObject> schemas, @NotNull final List<Set<JsonSchemaObject>> excludingSchemas) {
mySchemas = Collections.unmodifiableSet(schemas);
private MatchResult(@NotNull final List<JsonSchemaObject> schemas, @NotNull final List<Set<JsonSchemaObject>> excludingSchemas) {
mySchemas = Collections.unmodifiableList(schemas);
myExcludingSchemas = Collections.unmodifiableList(excludingSchemas);
}
public static MatchResult create(@NotNull JsonSchemaTreeNode root) {
final Set<JsonSchemaObject> schemas = new HashSet<>();
final List<JsonSchemaObject> schemas = new ArrayList<>();
final Map<Integer, Set<JsonSchemaObject>> oneOfGroups = new HashMap<>();
ContainerUtil.process(new JBTreeTraverser<JsonSchemaTreeNode>(node -> node.getChildren()).withRoot(root).preOrderDfsTraversal(),
node -> {
@@ -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\": {}}}", "{<caret>}", "\"prima\"", "\"primus\"", "\"proto\"");
}
public void testTopLevelVariant() throws Exception {
testImpl("{\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}", "{\"pri<caret>\"}", "prima", "primus", "proto");
}
public void testBoolean() throws Exception {
testImpl("{\"properties\": {\"prop\": {\"type\": \"boolean\"}}}", "{\"prop\": <caret>}", "false", "true");
}
public void testEnum() throws Exception {
testImpl("{\"properties\": {\"prop\": {\"enum\": [\"prima\", \"proto\", \"primus\"]}}}",
"{\"prop\": <caret>}", "\"prima\"", "\"primus\"", "\"proto\"");
}
public void testTopLevelAnyOfValues() throws Exception {
testImpl("{\"properties\": {\"prop\": {\"anyOf\": [{\"enum\": [\"prima\", \"proto\", \"primus\"]}," +
"{\"type\": \"boolean\"}]}}}",
"{\"prop\": <caret>}", "\"prima\"", "\"primus\"", "\"proto\"", "false", "true");
}
public void testTopLevelAnyOf() throws Exception {
testImpl("{\"anyOf\": [ {\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}," +
"{\"properties\": {\"abrakadabra\": {}}}]}",
"{<caret>}", "\"abrakadabra\"", "\"prima\"", "\"primus\"", "\"proto\"");
}
public void testSimpleHierarchy() throws Exception {
testImpl("{\"properties\": {\"top\": {\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}}}",
"{\"top\": {<caret>}}", "\"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\": [{<caret>}]}", "\"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\": <caret>}]}", "\"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\": {<caret>}}]}", "\"a1\"", "\"a2\"", "\"b1\"", "\"b2\"");
}
public void testArrayValuesInsideObject() throws Exception {
final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\"," +
"\"items\": {\"enum\": [1,2,3]}}}}";
testImpl(schema, "{\"prop\": [<caret>]}", "1", "2", "3");
}
public void testAllOfTerminal() throws Exception {
final String schema = "{\"allOf\": [{\"type\": \"object\", \"properties\": {\"first\": {}}}," +
" {\"properties\": {\"second\": {\"enum\": [33,44]}}}]}";
testImpl(schema, "{\"<caret>\"}", "first", "second");
}
public void testAllOfInTheMiddle() throws Exception {
final String schema = "{\"allOf\": [{\"type\": \"object\", \"properties\": {\"first\": {}}}," +
" {\"properties\": {\"second\": {\"enum\": [33,44]}}}]}";
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");
}
public void testPatternPropertyCompletion() throws Exception {
final String schema = "{\n" +
" \"patternProperties\": {\n" +
" \"C\": {\n" +
" \"enum\": [\"test\", \"em\"]\n" +
" }\n" +
" }\n" +
"}";
testImpl(schema, "{\"Cyan\": <caret>}", "\"em\"", "\"test\"");
}
public void testRootObjectRedefined() throws Exception {
testImpl(JsonSchemaHighlightingTest.rootObjectRedefinedSchema(), "{<caret>}",
"\"r1\"", "\"r2\"");
}
public void testSimpleNullCompletion() throws Exception {
final String schema = "{\n" +
" \"properties\": {\n" +
" \"null\": {\n" +
" \"type\": \"null\"\n" +
" }\n" +
" }\n" +
"}";
testImpl(schema, "{\"null\": <caret>}", "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\": <caret>}", "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\": <caret>}", "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, "{<caret>}", "\"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, "{<caret>}", "\"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);
}
}
@@ -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": {}}}""", "{<caret>}", "\"prima\"", "\"primus\"", "\"proto\"")
}
@Throws(Exception::class)
fun testTopLevelVariant() {
testImpl("""{"properties": {"prima": {}, "proto": {}, "primus": {}}}""", "{\"pri<caret>\"}", "prima", "primus", "proto")
}
@Throws(Exception::class)
fun testBoolean() {
testImpl("""{"properties": {"prop": {"type": "boolean"}}}""", "{\"prop\": <caret>}", "false", "true")
}
@Throws(Exception::class)
fun testEnum() {
testImpl("""{"properties": {"prop": {"enum": ["prima", "proto", "primus"]}}}""",
"""{"prop": <caret>}""", "\"prima\"", "\"primus\"", "\"proto\"")
}
@Throws(Exception::class)
fun testTopLevelAnyOfValues() {
testImpl("""{"properties": {"prop": {"anyOf": [{"enum": ["prima", "proto", "primus"]},""" + "{\"type\": \"boolean\"}]}}}",
"""{"prop": <caret>}""", "\"prima\"", "\"primus\"", "\"proto\"", "false", "true")
}
@Throws(Exception::class)
fun testTopLevelAnyOf() {
testImpl(
"""{"anyOf": [ {"properties": {"prima": {}, "proto": {}, "primus": {}}},""" + "{\"properties\": {\"abrakadabra\": {}}}]}",
"""{<caret>}""", "\"abrakadabra\"", "\"prima\"", "\"primus\"", "\"proto\"")
}
@Throws(Exception::class)
fun testSimpleHierarchy() {
testImpl("""{"properties": {"top": {"properties": {"prima": {}, "proto": {}, "primus": {}}}}}""",
"""{"top": {<caret>}}""", "\"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": [{<caret>}]}""", "\"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": <caret>}]}""", "\"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": {<caret>}}]}""", "\"a1\"", "\"a2\"", "\"b1\"", "\"b2\"")
}
@Throws(Exception::class)
fun testArrayValuesInsideObject() {
val schema = """{"properties": {"prop": {"type": "array","items": {"enum": [1,2,3]}}}}"""
testImpl(schema, """{"prop": [<caret>]}""", "1", "2", "3")
}
@Throws(Exception::class)
fun testAllOfTerminal() {
val schema = """{"allOf": [{"type": "object", "properties": {"first": {}}}, {"properties": {"second": {"enum": [33,44]}}}]}"""
testImpl(schema, """{"<caret>"}""", "first", "second")
}
@Throws(Exception::class)
fun testAllOfInTheMiddle() {
val schema = """{"allOf": [{"type": "object", "properties": {"first": {}}}, {"properties": {"second": {"enum": [33,44]}}}]}"""
testImpl(schema, """{"second": <caret>}""", "33", "44")
}
@Throws(Exception::class)
fun testValueCompletion() {
val schema = """{
"properties": {
"top": {
"enum": ["test", "me"]
}
}
}"""
testImpl(schema, """{"top": <caret>}""", "\"me\"", "\"test\"")
}
@Throws(Exception::class)
fun testTopLevelArrayPropNameCompletion() {
val schema = parcelShopSchema()
testImpl(schema, "[{<caret>}]", "\"address\"")
testImpl(schema, """[{"address": {<caret>}}]""", "\"fax\"", "\"houseNumber\"")
testImpl(schema, """[{"address": {"houseNumber": <caret>}}]""", "1", "2")
}
@Throws(Exception::class)
fun testPatternPropertyCompletion() {
val schema = """{
"patternProperties": {
"C": {
"enum": ["test", "em"]
}
}
}"""
testImpl(schema, """{"Cyan": <caret>}""", "\"em\"", "\"test\"")
}
@Throws(Exception::class)
fun testRootObjectRedefined() {
testImpl(JsonSchemaHighlightingTest.rootObjectRedefinedSchema(), "{<caret>}", "\"r1\"", "\"r2\"")
}
@Throws(Exception::class)
fun testSimpleNullCompletion() {
val schema = """{
"properties": {
"null": {
"type": "null"
}
}
}"""
testImpl(schema, """{"null": <caret>}""", "null")
}
@Throws(Exception::class)
fun testNullCompletionInEnum() {
val schema = """{
"properties": {
"null": {
"type": ["null", "integer"],
"enum": [null, 1, 2]
}
}
}"""
testImpl(schema, """{"null": <caret>}""", "1", "2", "null")
}
@Throws(Exception::class)
fun testNullCompletionInTypeVariants() {
val schema = """{
"properties": {
"null": {
"type": ["null", "boolean"]
}
}
}"""
testImpl(schema, """{"null": <caret>}""", "false", "null", "true")
}
@Throws(Exception::class)
fun testDescriptionFromDefinitionInCompletion() {
val schema = """{
"definitions": {
"target": {
"description": "Target description"
}
},
"properties": {
"source": {
"${"$"}ref": "#/definitions/target"
}
}
}"""
testImpl(schema, "{<caret>}", "\"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, "{<caret>}", "\"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, "{<caret>}", "\"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)
}
}