Skip to content

Commit 4dbca91

Browse files
committed
relax root schema constraint, add tests
1 parent f7d3fa2 commit 4dbca91

File tree

2 files changed

+142
-14
lines changed

2 files changed

+142
-14
lines changed

src/main/java/dev/harrel/jsonschema/Validator.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,22 +150,24 @@ public Result validate(URI schemaUri, JsonNode instanceNode) {
150150
}
151151

152152
private Schema getRootSchema(URI uri) {
153-
if (UriUtil.hasNonEmptyFragment(uri)) {
154-
throw new IllegalArgumentException(String.format("Root schema [%s] cannot contain non-empty fragments", uri));
155-
}
153+
CompoundUri compoundUri = CompoundUri.fromString(uri.toString());
156154
return OptionalUtil.firstPresent(
157-
() -> Optional.ofNullable(schemaRegistry.get(UriUtil.getUriWithoutFragment(uri))),
158-
() -> resolveExternalSchema(uri)
155+
() -> Optional.ofNullable(schemaRegistry.get(compoundUri)),
156+
() -> resolveExternalSchema(compoundUri)
159157
)
160-
.orElseThrow(() -> new SchemaNotFoundException(new CompoundUri(uri, "")));
158+
.orElseThrow(() -> new SchemaNotFoundException(compoundUri));
161159
}
162160

163-
private Optional<Schema> resolveExternalSchema(URI uri) {
164-
return schemaResolver.resolve(uri.toString())
161+
private Optional<Schema> resolveExternalSchema(CompoundUri compoundUri) {
162+
if (schemaRegistry.get(compoundUri.uri) != null) {
163+
return Optional.empty();
164+
}
165+
166+
return schemaResolver.resolve(compoundUri.uri.toString())
165167
.toJsonNode(schemaNodeFactory)
166168
.map(node -> {
167-
jsonParser.parseRootSchema(uri, node);
168-
return schemaRegistry.get(uri);
169+
jsonParser.parseRootSchema(compoundUri.uri, node);
170+
return schemaRegistry.get(compoundUri);
169171
});
170172
}
171173

src/test/java/dev/harrel/jsonschema/ValidatorTest.java

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,46 @@ void failsForUriWithNonEmptyFragments() {
3535
Validator validator = new ValidatorFactory().createValidator();
3636
URI uri = URI.create("https://test.com/x#/$def/x");
3737
assertThatThrownBy(() -> validator.validate(uri, "{}"))
38-
.isInstanceOf(IllegalArgumentException.class)
39-
.hasMessage("Root schema [https://test.com/x#/$def/x] cannot contain non-empty fragments");
38+
.isInstanceOf(SchemaNotFoundException.class)
39+
.hasMessage("Couldn't find schema with uri [https://test.com/x#/$def/x]");
4040
}
4141

4242
@Test
43-
void registersUriWithEmptyFragments() {
43+
void failsForNonExistentSchemaWithEmptyFragment() {
4444
Validator validator = new ValidatorFactory().createValidator();
4545
URI uri = URI.create("https://test.com/x#");
4646
assertThatThrownBy(() -> validator.validate(uri, "{}"))
4747
.isInstanceOf(SchemaNotFoundException.class)
48-
.hasMessage("Couldn't find schema with uri [https://test.com/x#]");
48+
.hasMessage("Couldn't find schema with uri [https://test.com/x]");
49+
}
50+
51+
@Test
52+
void findsSchemaByGeneratedUri() {
53+
Validator validator = new ValidatorFactory().createValidator();
54+
URI uri = validator.registerSchema("{}");
55+
Validator.Result result = validator.validate(uri, "{}");
56+
assertThat(result.isValid()).isTrue();
57+
}
58+
59+
@Test
60+
void findsSchemaByProvidedUri() {
61+
Validator validator = new ValidatorFactory().createValidator();
62+
URI uri = URI.create("https://test.com/schema");
63+
validator.registerSchema(URI.create("https://test.com/schema"), "{}");
64+
Validator.Result result = validator.validate(uri, "{}");
65+
assertThat(result.isValid()).isTrue();
66+
}
67+
68+
@Test
69+
void findsSchemaByIdKeyword() {
70+
Validator validator = new ValidatorFactory().createValidator();
71+
URI uri = URI.create("https://test.com/schema/id");
72+
validator.registerSchema("""
73+
{
74+
"$id": "https://test.com/schema/id"
75+
}""");
76+
Validator.Result result = validator.validate(uri, "{}");
77+
assertThat(result.isValid()).isTrue();
4978
}
5079

5180
@Test
@@ -184,6 +213,72 @@ void shouldFallbackToSchemaResolver() {
184213
);
185214
}
186215

216+
@Test
217+
void shouldFallbackToSchemaResolverWithFragment() {
218+
Validator validator = new ValidatorFactory()
219+
.withDisabledSchemaValidation(true)
220+
.withSchemaResolver(uri -> SchemaResolver.Result.fromString("""
221+
{
222+
"$defs": {
223+
"sub": {
224+
"type": "string"
225+
}
226+
}
227+
}"""))
228+
.createValidator();
229+
230+
Validator.Result result = validator.validate(URI.create("urn:test#/$defs/sub"), "{}");
231+
assertThat(result.isValid()).isFalse();
232+
233+
List<Error> errors = result.getErrors();
234+
assertThat(errors).hasSize(1);
235+
assertError(
236+
errors.get(0),
237+
"/$defs/sub/type",
238+
"urn:test#/$defs/sub",
239+
"",
240+
"type",
241+
"Value is [object] but should be [string]"
242+
);
243+
}
244+
245+
@Test
246+
void shouldFailResolvingExternalSchemaWithInvalidFragment() {
247+
Validator validator = new ValidatorFactory()
248+
.withDisabledSchemaValidation(true)
249+
.withSchemaResolver(uri -> SchemaResolver.Result.fromString("""
250+
{
251+
"$defs": {
252+
"sub": {
253+
"type": "string"
254+
}
255+
}
256+
}"""))
257+
.createValidator();
258+
259+
URI uri = URI.create("urn:test#/$defs/non-existent");
260+
assertThatThrownBy(() -> validator.validate(uri, "{}"))
261+
.isInstanceOf(SchemaNotFoundException.class)
262+
.hasMessage("Couldn't find schema with uri [urn:test#/$defs/non-existent]");
263+
}
264+
265+
@Test
266+
void shouldFailResolvingSchemaWithInvalidFragment() {
267+
Validator validator = new ValidatorFactory().createValidator();
268+
URI uri = URI.create("urn:test#/$defs/non-existent");
269+
validator.registerSchema(uri, """
270+
{
271+
"$defs": {
272+
"sub": {
273+
"type": "string"
274+
}
275+
}
276+
}""");
277+
assertThatThrownBy(() -> validator.validate(uri, "{}"))
278+
.isInstanceOf(SchemaNotFoundException.class)
279+
.hasMessage("Couldn't find schema with uri [urn:test#/$defs/non-existent]");
280+
}
281+
187282
@Test
188283
void allowsEmptyFragmentsInIdRootSchema() {
189284
// with disabled schema validation
@@ -312,4 +407,35 @@ void disallowsNonEmptyFragmentsInIdSubSchema() {
312407
.isInstanceOf(InvalidSchemaException.class)
313408
.hasMessage("Schema [urn:sub] failed to validate against meta-schema [https://json-schema.org/draft/2020-12/schema]");
314409
}
410+
411+
@Test
412+
void supportsMultipleWaysOfReferencing() {
413+
String schema = """
414+
{
415+
"$defs": {
416+
"x": {
417+
"$id": "urn:sub#"
418+
}
419+
}
420+
}""";
421+
Validator validator = new ValidatorFactory()
422+
.withDisabledSchemaValidation(true)
423+
.createValidator();
424+
425+
validator.registerSchema(URI.create("https://test.com/schema"), schema);
426+
Validator.Result result = validator.validate(URI.create("https://test.com/schema"), "true");
427+
assertThat(result.isValid()).isTrue();
428+
429+
result = validator.validate(URI.create("https://test.com/schema#"), "true");
430+
assertThat(result.isValid()).isTrue();
431+
432+
result = validator.validate(URI.create("https://test.com/schema#/$defs/x"), "true");
433+
assertThat(result.isValid()).isTrue();
434+
435+
result = validator.validate(URI.create("urn:sub"), "true");
436+
assertThat(result.isValid()).isTrue();
437+
438+
result = validator.validate(URI.create("urn:sub#"), "true");
439+
assertThat(result.isValid()).isTrue();
440+
}
315441
}

0 commit comments

Comments
 (0)