Skip to content

Commit 1852f51

Browse files
committed
InstanceValidator: improve test, documentation, error message
Also rewrite a schema for a more simple example Signed-off-by: Francis Galiegue <fgaliegue@gmail.com>
1 parent 31e15cc commit 1852f51

File tree

4 files changed

+46
-39
lines changed

4 files changed

+46
-39
lines changed

src/main/java/com/github/fge/jsonschema/processors/validation/InstanceValidator.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ public final class InstanceValidator
6161
private final MessageBundle validationMessages;
6262
private final Processor<SchemaContext, ValidatorList> keywordBuilder;
6363

64+
/*
65+
* It is possible to trigger a validation loop if there is a repeated
66+
* triplet schema ID/schema pointer/instance pointer while we validate;
67+
* example schema:
68+
*
69+
* { "oneOf": [ {}, { "$ref": "#" } ] }
70+
*
71+
* Whatever the data, validation will end up validating, for a same pointer
72+
* into the instance, the following pointers into the schema:
73+
*
74+
* "" -> "/oneOf/0" -> "/oneOf/1" -> "" <-- LOOP
75+
*
76+
* This is not a JSON Reference loop here, but truly a validation loop.
77+
*
78+
* We therefore use this set to record the triplets seen by using an
79+
* Equivalence over FullData which detects this. This is helped by the fact
80+
* that SchemaTree now implements equals()/hashCode() in -core; since this
81+
* class is instantiated for each instance validation, we are certain that
82+
* what instance pointer is seen is the one of the instance we validate.
83+
*/
6484
private final Set<Equivalence.Wrapper<FullData>> visited
6585
= Sets.newLinkedHashSet();
6686

@@ -88,7 +108,10 @@ public FullData process(final ProcessingReport report,
88108
final ProcessingMessage message = input.newMessage()
89109
.put("domain", "validation")
90110
.setMessage(errmsg)
91-
.put("visited", node);
111+
.putArgument("alreadyVisited", toJson(input))
112+
.putArgument("instancePointer",
113+
input.getInstance().getPointer().toString())
114+
.put("validationPath", node);
92115
throw new ProcessingException(message);
93116
}
94117

@@ -231,15 +254,16 @@ public String toString()
231254
return "instance validator";
232255
}
233256

234-
private static JsonNode toJson(final FullData data)
257+
private static String toJson(final FullData data)
235258
{
236259
final SchemaTree tree = data.getSchema();
237260
final URI baseUri = tree.getLoadingRef().getLocator();
238261
try {
262+
// TODO: there should be an easier way to do that...
239263
final URITemplate template = new URITemplate(baseUri + "{+ptr}");
240264
final VariableMap vars = VariableMap.newBuilder().addScalarValue(
241265
"ptr", tree.getPointer()).freeze();
242-
return JacksonUtils.nodeFactory().textNode(template.toString(vars));
266+
return template.toString(vars);
243267
} catch (URITemplateException e) {
244268
throw new IllegalStateException("wtf??", e);
245269
}

src/main/resources/com/github/fge/jsonschema/validator/validation.properties

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,4 @@ err.format.UUID.invalid = input string "%s" is not a valid UUID
7878
#
7979
# Other messages
8080
#
81-
err.common.validationLoop = validation loop: same schema visited more than \
82-
once for the same instance
81+
err.common.validationLoop = validation loop: schema "%s" visited twice for pointer "%s" into validated instance

src/test/java/com/github/fge/jsonschema/processors/validation/ValidationProcessorTest.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@
2626
import com.github.fge.jackson.JacksonUtils;
2727
import com.github.fge.jackson.JsonLoader;
2828
import com.github.fge.jackson.NodeType;
29+
import com.github.fge.jackson.jsonpointer.JsonPointer;
30+
import com.github.fge.jackson.jsonpointer.JsonPointerException;
2931
import com.github.fge.jsonschema.cfg.ValidationConfiguration;
3032
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
3133
import com.github.fge.jsonschema.core.keyword.syntax.checkers.SyntaxChecker;
3234
import com.github.fge.jsonschema.core.processing.Processor;
35+
import com.github.fge.jsonschema.core.report.ProcessingMessage;
3336
import com.github.fge.jsonschema.core.report.ProcessingReport;
3437
import com.github.fge.jsonschema.core.tree.CanonicalSchemaTree;
3538
import com.github.fge.jsonschema.core.tree.JsonTree;
@@ -49,8 +52,11 @@
4952
import org.testng.annotations.Test;
5053

5154
import java.io.IOException;
55+
import java.net.URI;
56+
import java.util.Arrays;
5257
import java.util.concurrent.atomic.AtomicInteger;
5358

59+
import static com.github.fge.jsonschema.matchers.ProcessingMessageAssert.assertMessage;
5460
import static org.mockito.Mockito.mock;
5561
import static org.testng.Assert.assertEquals;
5662
import static org.testng.Assert.assertTrue;
@@ -120,23 +126,30 @@ public void childrenAreExploredOnDemandEvenIfContainerFails()
120126

121127
@Test(timeOut = 1000)
122128
public void circularReferencingDuringValidationIsDetected()
123-
throws IOException, ProcessingException
129+
throws IOException, ProcessingException, JsonPointerException
124130
{
125131
final JsonNode schemaNode
126132
= JsonLoader.fromResource("/other/issue102.json");
127133
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
128134
final JsonValidator validator = factory.getValidator();
129135
final MessageBundle bundle
130136
= MessageBundles.getBundle(JsonSchemaValidationBundle.class);
131-
final String expectedMsg
132-
= bundle.getMessage("err.common.validationLoop");
133137

134138
try {
135139
validator.validate(schemaNode,
136140
JacksonUtils.nodeFactory().nullNode());
137141
fail("No exception thrown!");
138142
} catch (ProcessingException e) {
139-
assertEquals(e.getProcessingMessage().getMessage(), expectedMsg);
143+
final URI uri = URI.create("#/oneOf/0");
144+
final ProcessingMessage message = e.getProcessingMessage();
145+
final String expectedMessage
146+
= bundle.printf("err.common.validationLoop", uri, "");
147+
assertMessage(message)
148+
.hasMessage(expectedMessage)
149+
.hasField("alreadyVisited", uri)
150+
.hasField("instancePointer", JsonPointer.empty().toString())
151+
.hasField("validationPath",
152+
Arrays.asList("#", "#/oneOf/0", "#/oneOf/1"));
140153
}
141154
assertTrue(true);
142155
}
Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1 @@
1-
{
2-
"$schema": "http://json-schema.org/draft-04/schema#",
3-
"definitions": {
4-
"S": {
5-
"allOf": [
6-
{
7-
"type": "string",
8-
"enum": ["a"]
9-
},
10-
{
11-
"oneOf": [
12-
{
13-
"$ref": "#/definitions/S"
14-
},
15-
{
16-
"type": "string",
17-
"enum": [""]
18-
}
19-
]
20-
},
21-
{
22-
"type": "string",
23-
"enum": ["b"]
24-
}
25-
],
26-
"additionalItems": false
27-
}
28-
},
29-
"$ref": "#/definitions/S"
30-
}
1+
{ "oneOf": [ {}, { "$ref": "#" } ] }

0 commit comments

Comments
 (0)