665: Can't load JSON schemas with URN value in id field (#906)
* 665: Can't load JSON schemas with URN value in id field
* Better handling of URNs
diff --git a/pom.xml b/pom.xml
index c9ae09a..cb6966f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index 84d8c31..d5050ae 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -214,7 +214,7 @@
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);
diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
index 77ce452..b6fbd21 100644
--- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java
+++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
@@ -59,6 +59,8 @@
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();
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
index 356ffba..83e8afe 100644
--- a/src/main/java/com/networknt/schema/RefValidator.java
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -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;
@@ -35,6 +36,7 @@
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);
@@ -78,6 +80,12 @@
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.
@@ -91,6 +99,13 @@
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);
diff --git a/src/main/java/com/networknt/schema/uri/URLFactory.java b/src/main/java/com/networknt/schema/uri/URLFactory.java
index 5850a3b..61fa56d 100644
--- a/src/main/java/com/networknt/schema/uri/URLFactory.java
+++ b/src/main/java/com/networknt/schema/uri/URLFactory.java
@@ -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")));
/**
diff --git a/src/main/java/com/networknt/schema/uri/URLFetcher.java b/src/main/java/com/networknt/schema/uri/URLFetcher.java
index 14899cf..d5bb8d9 100644
--- a/src/main/java/com/networknt/schema/uri/URLFetcher.java
+++ b/src/main/java/com/networknt/schema/uri/URLFetcher.java
@@ -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;
/**
@@ -32,7 +34,7 @@
// 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}
diff --git a/src/main/java/com/networknt/schema/uri/URNURIFactory.java b/src/main/java/com/networknt/schema/uri/URNURIFactory.java
new file mode 100644
index 0000000..8ea7914
--- /dev/null
+++ b/src/main/java/com/networknt/schema/uri/URNURIFactory.java
@@ -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);
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue665Test.java b/src/test/java/com/networknt/schema/Issue665Test.java
new file mode 100644
index 0000000..1b9f8bd
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue665Test.java
@@ -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);
+ }
+ }
+
+}
diff --git a/src/test/resources/draft7/urn/issue665.json b/src/test/resources/draft7/urn/issue665.json
new file mode 100644
index 0000000..57a3bf8
--- /dev/null
+++ b/src/test/resources/draft7/urn/issue665.json
@@ -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"
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/resources/draft7/urn/issue665_external_urn_ref.json b/src/test/resources/draft7/urn/issue665_external_urn_ref.json
new file mode 100644
index 0000000..2929686
--- /dev/null
+++ b/src/test/resources/draft7/urn/issue665_external_urn_ref.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "myData"
+ ],
+ "properties": {
+ "myData": {
+ "$ref": "urn:data"
+ }
+ }
+}
diff --git a/src/test/resources/draft7/urn/issue665_external_urn_subschema.json b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json
new file mode 100644
index 0000000..026f879
--- /dev/null
+++ b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "urn:data",
+ "type": "object",
+ "required": [
+ "value"
+ ],
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ }
+}