Skip to content

Commit

Permalink
Merge pull request #74 from harrel56/feature/disallow-non-empty-fragm…
Browse files Browse the repository at this point in the history
…ents2

Add id validation to subschemas too
  • Loading branch information
harrel56 authored Oct 14, 2023
2 parents 01d5896 + 79c3a92 commit b2e7085
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 29 deletions.
25 changes: 25 additions & 0 deletions src/main/java/dev/harrel/jsonschema/JsonNodeUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.harrel.jsonschema;

import java.net.URI;
import java.util.Map;
import java.util.Optional;

final class JsonNodeUtil {
private JsonNodeUtil() {}

static Optional<Map<String, JsonNode>> getAsObject(JsonNode node) {
return node.isObject() ? Optional.of(node.asObject()) : Optional.empty();
}

static Optional<String> getStringField(Map<String, JsonNode> objectMap, String fieldName) {
return Optional.ofNullable(objectMap.get(fieldName))
.filter(JsonNode::isString)
.map(JsonNode::asString);
}

static void validateIdField(String id) {
if (UriUtil.hasNonEmptyFragment(URI.create(id))) {
throw new IllegalArgumentException(String.format("$id [%s] cannot contain non-empty fragments", id));
}
}
}
32 changes: 7 additions & 25 deletions src/main/java/dev/harrel/jsonschema/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,16 @@ final class JsonParser {
}

URI parseRootSchema(URI baseUri, JsonNode node) {
Optional<Map<String, JsonNode>> objectMapOptional = getAsObject(node);
Optional<Map<String, JsonNode>> objectMapOptional = JsonNodeUtil.getAsObject(node);
String metaSchemaUri = objectMapOptional
.map(obj -> obj.get(Keyword.SCHEMA))
.filter(JsonNode::isString)
.map(JsonNode::asString)
.flatMap(obj -> JsonNodeUtil.getStringField(obj, Keyword.SCHEMA))
.orElse(dialect.getMetaSchema());
Optional<String> providedSchemaId = objectMapOptional
.map(obj -> obj.get(Keyword.ID))
.filter(JsonNode::isString)
.map(JsonNode::asString)
.flatMap(obj -> JsonNodeUtil.getStringField(obj, Keyword.ID))
.filter(id -> !baseUri.toString().equals(id));
providedSchemaId
.map(URI::create)
.ifPresent(uri -> {
if (UriUtil.hasNonEmptyFragment(uri)) {
throw new IllegalArgumentException(String.format("$id [%s] cannot contain non-empty fragments", uri));
}
});

MetaValidationResult metaValidationResult = validateSchemaOrPostpone(node, metaSchemaUri, baseUri.toString(), providedSchemaId);
providedSchemaId.ifPresent(JsonNodeUtil::validateIdField);

if (node.isBoolean()) {
SchemaParsingContext ctx = new SchemaParsingContext(dialect, schemaRegistry, baseUri.toString(), emptyMap());
Expand All @@ -67,10 +57,6 @@ URI parseRootSchema(URI baseUri, JsonNode node) {
return providedSchemaId.map(URI::create).orElse(baseUri);
}

private Optional<Map<String, JsonNode>> getAsObject(JsonNode node) {
return node.isObject() ? Optional.of(node.asObject()) : Optional.empty();
}

private void parseNode(SchemaParsingContext ctx, JsonNode node) {
if (node.isBoolean()) {
parseBoolean(ctx, node);
Expand All @@ -96,13 +82,9 @@ private void parseArray(SchemaParsingContext ctx, JsonNode node) {

private void parseObject(SchemaParsingContext ctx, JsonNode node) {
Map<String, JsonNode> objectMap = node.asObject();
String metaSchemaUri = Optional.ofNullable(objectMap.get(Keyword.SCHEMA))
.filter(JsonNode::isString)
.map(JsonNode::asString)
.orElse(null);
Optional<String> providedSchemaId = Optional.ofNullable(objectMap.get(Keyword.ID))
.filter(JsonNode::isString)
.map(JsonNode::asString);
String metaSchemaUri = JsonNodeUtil.getStringField(objectMap, Keyword.SCHEMA).orElse(null);
Optional<String> providedSchemaId = JsonNodeUtil.getStringField(objectMap, Keyword.ID);
providedSchemaId.ifPresent(JsonNodeUtil::validateIdField);
String absoluteUri = ctx.getAbsoluteUri(node);
MetaValidationResult metaValidationResult = validateSchemaOrPostpone(node, metaSchemaUri, absoluteUri, providedSchemaId);

Expand Down
62 changes: 58 additions & 4 deletions src/test/java/dev/harrel/jsonschema/ValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ void shouldFallbackToSchemaResolver() {
}

@Test
void allowsEmptyFragmentsInId() {
Validator validator = new ValidatorFactory().createValidator();
void allowsEmptyFragmentsInIdRootSchema() {
Validator validator = new ValidatorFactory()
.withDisabledSchemaValidation(true)
.createValidator();
String schema = """
{
"$id": "urn:test#"
Expand All @@ -198,8 +200,10 @@ void allowsEmptyFragmentsInId() {
}

@Test
void disallowsNonEmptyFragmentsInId() {
Validator validator = new ValidatorFactory().createValidator();
void disallowsNonEmptyFragmentsInIdRootSchema() {
Validator validator = new ValidatorFactory()
.withDisabledSchemaValidation(true)
.createValidator();

String schema1 = """
{
Expand All @@ -217,4 +221,54 @@ void disallowsNonEmptyFragmentsInId() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("$id [urn:test#/$defs/x] cannot contain non-empty fragments");
}

@Test
void allowsEmptyFragmentsInIdSubSchema() {
Validator validator = new ValidatorFactory()
.withDisabledSchemaValidation(true)
.createValidator();
String schema = """
{
"$defs": {
"x": {
"$id": "urn:sub#"
}
}
}""";

URI uri = validator.registerSchema(schema);
Validator.Result result = validator.validate(uri, "true");
assertThat(result.isValid()).isTrue();
}

@Test
void disallowsNonEmptyFragmentsInIdSubSchema() {
Validator validator = new ValidatorFactory()
.withDisabledSchemaValidation(true)
.createValidator();

String schema1 = """
{
"$defs": {
"x": {
"$id": "urn:sub#anchor"
}
}
}""";
assertThatThrownBy(() -> validator.registerSchema(schema1))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("$id [urn:sub#anchor] cannot contain non-empty fragments");

String schema2 = """
{
"$defs": {
"x": {
"$id": "urn:sub#/$defs/x"
}
}
}""";
assertThatThrownBy(() -> validator.registerSchema(schema2))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("$id [urn:sub#/$defs/x] cannot contain non-empty fragments");
}
}

0 comments on commit b2e7085

Please sign in to comment.