Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

665: Can't load JSON schemas with URN value in id field #906

Merged
merged 2 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.87</version>
<version>1.0.88</version>
<packaging>bundle</packaging>
<name>JsonSchemaValidator</name>
<description>A json schema validator that supports draft v4, v6, v7, v2019-09 and v2020-12</description>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public JsonNode getRefSchemaNode(String ref) {
if (node.isMissingNode()) {
node = handleNullNode(ref, schema);
}
} else if (ref.startsWith("#") && ref.length() > 1) {
} else if ((ref.startsWith("#") && ref.length() > 1) || (ref.startsWith("urn:") && ref.length() > 4)) {
node = this.metaSchema.getNodeByFragmentRef(ref, node);
if (node == null) {
node = handleNullNode(ref, schema);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/networknt/schema/JsonSchemaFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public Builder() {
for (final String scheme : URLFactory.SUPPORTED_SCHEMES) {
this.uriFactoryMap.put(scheme, urlFactory);
}
// Adds support for creating URNs.
this.uriFactoryMap.put(URNURIFactory.SCHEME, new URNURIFactory());

// Adds support for fetching with {@link URL}s.
final URIFetcher urlFetcher = new URLFetcher();
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/networknt/schema/RefValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.CollectorContext.Scope;
import com.networknt.schema.uri.URIFactory;
import com.networknt.schema.uri.URNURIFactory;
import com.networknt.schema.urn.URNFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,6 +36,7 @@ public class RefValidator extends BaseJsonValidator {
private JsonSchema parentSchema;

private static final String REF_CURRENT = "#";
private static final String URN_SCHEME = URNURIFactory.SCHEME;

public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext);
Expand Down Expand Up @@ -78,6 +80,12 @@ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext val
if (schemaUri == null) {
return null;
}
} else if (URN_SCHEME.equals(schemaUri.getScheme())) {
// Try to resolve URN schema as a JsonSchemaRef to some sub-schema of the parent
JsonSchemaRef ref = getJsonSchemaRef(parent, validationContext, schemaUri.toString(), refValueOriginal);
if (ref != null) {
return ref;
}
}

// This should retrieve schemas regardless of the protocol that is in the uri.
Expand All @@ -91,6 +99,13 @@ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext val
if (refValue.equals(REF_CURRENT)) {
return new JsonSchemaRef(parent.findAncestor());
}
return getJsonSchemaRef(parent, validationContext, refValue, refValueOriginal);
}

private static JsonSchemaRef getJsonSchemaRef(JsonSchema parent,
ValidationContext validationContext,
String refValue,
String refValueOriginal) {
JsonNode node = parent.getRefSchemaNode(refValue);
if (node != null) {
JsonSchemaRef ref = validationContext.getReferenceParsingInProgress(refValueOriginal);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/uri/URLFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
public final class URLFactory implements URIFactory {
// These supported schemes are defined in {@link #URL(String, String, int, String)}.
public static final Set<String> SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet<String>(
public static final Set<String> SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("http", "https", "ftp", "file", "jar")));

/**
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/networknt/schema/uri/URLFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
Expand All @@ -32,7 +34,7 @@ public final class URLFetcher implements URIFetcher {

// These supported schemes are defined in {@link #URL(String, String, int, String)}.
// This fetcher also supports the {@link URL}s created with the {@link ClasspathURIFactory}.
public static final Set<String> SUPPORTED_SCHEMES = Collections.unmodifiableSet(URLFactory.SUPPORTED_SCHEMES);
public static final Set<String> SUPPORTED_SCHEMES = URLFactory.SUPPORTED_SCHEMES;

/**
* {@inheritDoc}
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/networknt/schema/uri/URNURIFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.networknt.schema.uri;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

/**
* A URIFactory that handles "urn" scheme of {@link URI}s.
*/
public final class URNURIFactory implements URIFactory {

public static final String SCHEME = "urn";

@Override
public URI create(final String uri) {
try {
return URI.create(uri);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unable to create URI.", e);
}
}

@Override
public URI create(final URI baseURI, final String segment) {
String urnPart = baseURI.getRawSchemeSpecificPart();
int pos = urnPart.indexOf(':');
String namespace = pos < 0 ? urnPart : urnPart.substring(0, pos);
return URI.create(SCHEME + ":" + namespace + ":" + segment);
}
}
49 changes: 49 additions & 0 deletions src/test/java/com/networknt/schema/Issue665Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.networknt.schema;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.Collections;
import java.util.Set;

public class Issue665Test extends BaseJsonSchemaValidatorTest {

@Test
void testUrnUriAsLocalRef() throws IOException {
JsonSchema schema = getJsonSchemaFromClasspath("draft7/urn/issue665.json", SpecVersion.VersionFlag.V7);
Assertions.assertNotNull(schema);
Assertions.assertDoesNotThrow(schema::initializeValidators);
Set<ValidationMessage> messages = schema.validate(getJsonNodeFromStringContent(
"{\"myData\": {\"value\": \"hello\"}}"));
Assertions.assertEquals(messages, Collections.emptySet());
}

@Test
void testUrnUriAsLocalRef_ExternalURN() {
JsonSchemaFactory factory = JsonSchemaFactory
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
.uriFetcher(uri -> uri.equals(URI.create("urn:data"))
? Thread.currentThread().getContextClassLoader()
.getResourceAsStream("draft7/urn/issue665_external_urn_subschema.json")
: null,
"urn")
.build();

try (InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("draft7/urn/issue665_external_urn_ref.json")) {
JsonSchema schema = factory.getSchema(is);
Assertions.assertNotNull(schema);
Assertions.assertDoesNotThrow(schema::initializeValidators);
Set<ValidationMessage> messages = schema.validate(getJsonNodeFromStringContent(
"{\"myData\": {\"value\": \"hello\"}}"));
Assertions.assertEquals(messages, Collections.emptySet());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

}
26 changes: 26 additions & 0 deletions src/test/resources/draft7/urn/issue665.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"myData"
],
"properties": {
"myData": {
"$ref": "urn:data"
}
},
"definitions": {
"data": {
"$id": "urn:data",
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string"
}
}
}
}
}
12 changes: 12 additions & 0 deletions src/test/resources/draft7/urn/issue665_external_urn_ref.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"myData"
],
"properties": {
"myData": {
"$ref": "urn:data"
}
}
}
13 changes: 13 additions & 0 deletions src/test/resources/draft7/urn/issue665_external_urn_subschema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "urn:data",
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string"
}
}
}
Loading