Skip to content

Commit 437806e

Browse files
author
Matti Hansson
committed
Add support for subschema references in getSchema(URI) (networknt#619)
1 parent c41b83d commit 437806e

File tree

4 files changed

+191
-12
lines changed

4 files changed

+191
-12
lines changed

src/main/java/com/networknt/schema/JsonSchema.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@
1616

1717
package com.networknt.schema;
1818

19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.ObjectNode;
21+
import com.networknt.schema.ValidationContext.DiscriminatorContext;
22+
import com.networknt.schema.utils.StringUtils;
23+
import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
24+
import com.networknt.schema.walk.JsonSchemaWalker;
25+
import com.networknt.schema.walk.WalkListenerRunner;
26+
1927
import java.io.UnsupportedEncodingException;
2028
import java.net.URI;
29+
import java.net.URISyntaxException;
2130
import java.net.URLDecoder;
2231
import java.util.*;
2332
import java.util.Map.Entry;
2433
import java.util.regex.Matcher;
2534
import java.util.regex.Pattern;
2635

27-
import com.fasterxml.jackson.databind.JsonNode;
28-
import com.fasterxml.jackson.databind.node.ObjectNode;
29-
import com.networknt.schema.ValidationContext.DiscriminatorContext;
30-
import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
31-
import com.networknt.schema.walk.JsonSchemaWalker;
32-
import com.networknt.schema.walk.WalkListenerRunner;
33-
3436
/**
3537
* This is the core of json constraint implementation. It parses json constraint
3638
* file and generates JsonValidators. The class is thread safe, once it is
@@ -52,7 +54,6 @@ public class JsonSchema extends BaseJsonValidator {
5254
* 'id' would still be able to specify an absolute uri.
5355
*/
5456
private final URI currentUri;
55-
5657
private JsonValidator requiredValidator = null;
5758

5859
private JsonValidator unevaluatedPropertiesValidator = null;
@@ -79,7 +80,7 @@ private JsonSchema(ValidationContext validationContext, String schemaPath, URI c
7980
validationContext.getConfig() != null ? validationContext.getConfig().getApplyDefaultsStrategy() : null);
8081
this.validationContext = validationContext;
8182
this.metaSchema = validationContext.getMetaSchema();
82-
this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode);
83+
this.currentUri = currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode);
8384
if (validationContext.getConfig() != null) {
8485
keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(this.validationContext.getConfig().getKeywordWalkListenersMap());
8586
if (validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
@@ -89,6 +90,24 @@ private JsonSchema(ValidationContext validationContext, String schemaPath, URI c
8990
}
9091
}
9192
}
93+
// If the URI references a subschema, update the schema path and node to use that instead.
94+
if (currentUri != null && StringUtils.isNotBlank(currentUri.getRawFragment()) &&
95+
(StringUtils.isBlank(schemaPath) || "#".equals(schemaPath))) {
96+
String fragment = "#" + currentUri.getFragment();
97+
JsonNode fragmentSchemaNode = getRefSchemaNode(fragment);
98+
if (fragmentSchemaNode == null) {
99+
throw new JsonSchemaException("Fragment " + fragment + " cannot be resolved");
100+
}
101+
URI currentUriWithoutFragment;
102+
try {
103+
currentUriWithoutFragment = new URI(currentUri.getScheme(), currentUri.getSchemeSpecificPart(), null);
104+
} catch (URISyntaxException ex) {
105+
throw new JsonSchemaException("Unable to create URI without fragment from " + currentUri + ": " + ex.getMessage());
106+
}
107+
this.parentSchema = new JsonSchema(this.validationContext, this.schemaPath, currentUriWithoutFragment, this.schemaNode, this.parentSchema);
108+
this.schemaPath = fragment;
109+
this.schemaNode = fragmentSchemaNode;
110+
}
92111
}
93112

94113
ValidationContext getValidationContext() {

src/main/java/com/networknt/schema/JsonSchemaFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,9 @@ public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig co
375375

376376
JsonSchema jsonSchema;
377377
if (idMatchesSourceUri(jsonMetaSchema, schemaNode, schemaUri)) {
378-
jsonSchema = new JsonSchema(
379-
new ValidationContext(this.uriFactory, this.urnFactory, jsonMetaSchema, this, config),
380-
mappedUri, schemaNode, true /* retrieved via id, resolving will not change anything */);
378+
jsonSchema = new JsonSchema(
379+
new ValidationContext(this.uriFactory, this.urnFactory, jsonMetaSchema, this, config),
380+
mappedUri, schemaNode, true /* retrieved via id, resolving will not change anything */);
381381
} else {
382382
final ValidationContext validationContext = createValidationContext(schemaNode);
383383
validationContext.setConfig(config);
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2020 Network New Technologies Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.networknt.schema;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import java.net.URI;
23+
24+
import static org.junit.jupiter.api.Assertions.*;
25+
26+
public class Issue619Test extends BaseJsonSchemaValidatorTest {
27+
28+
private JsonSchemaFactory factory;
29+
private JsonNode one;
30+
private JsonNode two;
31+
private JsonNode three;
32+
33+
@BeforeEach
34+
public void setup() throws Exception {
35+
factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
36+
one = getJsonNodeFromStringContent("1");
37+
two = getJsonNodeFromStringContent("2");
38+
three = getJsonNodeFromStringContent("3");
39+
}
40+
41+
@Test
42+
public void checkThatBundledSchemaLoadsAndValidatesCorrectly() throws Exception {
43+
JsonSchema rootSchema = factory.getSchema(new URI("resource:schema/issue619.json"));
44+
45+
assertTrue(rootSchema.validate(one).isEmpty());
46+
assertTrue(rootSchema.validate(two).isEmpty());
47+
assertFalse(rootSchema.validate(three).isEmpty());
48+
}
49+
50+
@Test
51+
public void checkThatSchemaThatReferencesBundledSchemaLoadsAndValidatesCorrectly() {
52+
JsonSchema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json\" }");
53+
54+
assertTrue(referencingRootSchema.validate(one).isEmpty());
55+
assertTrue(referencingRootSchema.validate(two).isEmpty());
56+
assertFalse(referencingRootSchema.validate(three).isEmpty());
57+
}
58+
59+
@Test
60+
public void checkThatUriThatPointsToEmptyFragmentLoadsAndValidatesCorrectly() throws Exception {
61+
JsonSchema rootSchema = factory.getSchema(new URI("resource:schema/issue619.json#"));
62+
63+
assertTrue(rootSchema.validate(one).isEmpty());
64+
assertTrue(rootSchema.validate(two).isEmpty());
65+
assertFalse(rootSchema.validate(three).isEmpty());
66+
}
67+
68+
@Test
69+
public void checkThatSchemaThatReferencesEmptyFragmentLoadsAndValidatesCorrectly() {
70+
JsonSchema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#\" }");
71+
72+
assertTrue(referencingRootSchema.validate(one).isEmpty());
73+
assertTrue(referencingRootSchema.validate(two).isEmpty());
74+
assertFalse(referencingRootSchema.validate(three).isEmpty());
75+
}
76+
77+
@Test
78+
public void loadingSchemaWithUriThatPointsToOneShouldOnlyValidateOne() throws Exception {
79+
JsonSchema oneSchema = factory.getSchema(new URI("resource:schema/issue619.json#/definitions/one"));
80+
81+
assertTrue(oneSchema.validate(one).isEmpty());
82+
assertFalse(oneSchema.validate(two).isEmpty());
83+
assertFalse(oneSchema.validate(three).isEmpty());
84+
}
85+
86+
@Test
87+
public void referencingSchemaWithUriThatPointsToTwoShouldOnlyValidateTwo() {
88+
JsonSchema referencingTwoSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/two\" }");
89+
90+
assertFalse(referencingTwoSchema.validate(one).isEmpty());
91+
assertTrue(referencingTwoSchema.validate(two).isEmpty());
92+
assertFalse(referencingTwoSchema.validate(three).isEmpty());
93+
}
94+
95+
@Test
96+
public void loadingSchemaWithUriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne() throws Exception {
97+
JsonSchema oneSchema = factory.getSchema(new URI("resource:schema/issue619.json#/definitions/refToOne"));
98+
99+
assertTrue(oneSchema.validate(one).isEmpty());
100+
assertFalse(oneSchema.validate(two).isEmpty());
101+
assertFalse(oneSchema.validate(three).isEmpty());
102+
}
103+
104+
@Test
105+
public void referencingSchemaWithUriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne() {
106+
JsonSchema referencingTwoSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/refToOne\" }");
107+
108+
assertTrue(referencingTwoSchema.validate(one).isEmpty());
109+
assertFalse(referencingTwoSchema.validate(two).isEmpty());
110+
assertFalse(referencingTwoSchema.validate(three).isEmpty());
111+
}
112+
113+
@Test
114+
public void loadingSchemaWithUriThatPointsToSchemaThatDoesNotExistShouldFail() {
115+
assertThrows(JsonSchemaException.class, () -> factory.getSchema(new URI("resource:data/schema-that-does-not-exist.json#/definitions/something")));
116+
}
117+
118+
@Test
119+
public void referencingSchemaWithUriThatPointsToSchemaThatDoesNotExistShouldFail() {
120+
JsonSchema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:data/schema-that-does-not-exist.json#/definitions/something\" }");
121+
122+
assertThrows(JsonSchemaException.class, () -> referencingNonexistentSchema.validate(one));
123+
}
124+
125+
@Test
126+
public void loadingSchemaWithUriThatPointsToNodeThatDoesNotExistShouldFail() {
127+
assertThrows(JsonSchemaException.class, () -> factory.getSchema(new URI("resource:schema/issue619.json#/definitions/node-that-does-not-exist")));
128+
}
129+
130+
@Test
131+
public void referencingSchemaWithUriThatPointsToNodeThatDoesNotExistShouldFail() {
132+
JsonSchema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/node-that-does-not-exist\" }");
133+
134+
assertThrows(JsonSchemaException.class, () -> referencingNonexistentSchema.validate(one));
135+
}
136+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"oneOf": [
4+
{
5+
"$ref": "#/definitions/one"
6+
},
7+
{
8+
"$ref": "#/definitions/two"
9+
}
10+
],
11+
"definitions": {
12+
"one": {
13+
"type": "integer",
14+
"enum": [1]
15+
},
16+
"two": {
17+
"type": "integer",
18+
"enum": [2]
19+
},
20+
"refToOne": {
21+
"$ref": "#/definitions/one"
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)