Skip to content

Adds support for validating an IRI #768

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

Merged
merged 1 commit into from
May 17, 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
32 changes: 18 additions & 14 deletions src/main/java/com/networknt/schema/JsonMetaSchema.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.format.DateFormat;
import com.networknt.schema.format.EmailFormat;
import com.networknt.schema.format.IriFormat;
import com.networknt.schema.format.IriReferenceFormat;
import com.networknt.schema.format.PatternFormat;
import com.networknt.schema.format.RegexFormat;
Expand Down Expand Up @@ -49,27 +50,30 @@ static PatternFormat pattern(String name, String regex) {

// this section contains formats common to all dialects.
static {
COMMON_BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$"));
COMMON_BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$"));
COMMON_BUILTIN_FORMATS.add(pattern("color", "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))"));
COMMON_BUILTIN_FORMATS.add(pattern("hostname", "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$"));
COMMON_BUILTIN_FORMATS.add(pattern("ip-address", "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
COMMON_BUILTIN_FORMATS.add(pattern("ipv4", "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$"));
COMMON_BUILTIN_FORMATS.add(pattern("ipv6", "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$"));
COMMON_BUILTIN_FORMATS.add(pattern("json-pointer", "^(/([^/#~]|[~](?=[01]))*)*$"));
COMMON_BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$"));
COMMON_BUILTIN_FORMATS.add(pattern("relative-json-pointer", "^(0|([1-9]\\d*))(#|(/([^/#~]|[~](?=[01]))*)*)$"));
COMMON_BUILTIN_FORMATS.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?"));
COMMON_BUILTIN_FORMATS.add(pattern("uri-template", "^([^\\p{Cntrl}\"'%<>\\^`\\{|\\}]|%\\p{XDigit}{2}|\\{[+#./;?&=,!@|]?((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?)(,((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?))*\\})*$"));
COMMON_BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$"));
COMMON_BUILTIN_FORMATS.add(pattern("uuid", "^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$"));
COMMON_BUILTIN_FORMATS.add(pattern("hostname", "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$", "must be a valid RFC 1123 host name"));
COMMON_BUILTIN_FORMATS.add(pattern("ipv4", "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$", "must be a valid RFC 2673 IP address"));
COMMON_BUILTIN_FORMATS.add(pattern("ipv6", "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$", "must be a valid RFC 4291 IP address"));
COMMON_BUILTIN_FORMATS.add(pattern("json-pointer", "^(/([^/#~]|[~](?=[01]))*)*$", "must be a valid RFC 6901 JSON Pointer"));
COMMON_BUILTIN_FORMATS.add(pattern("relative-json-pointer", "^(0|([1-9]\\d*))(#|(/([^/#~]|[~](?=[01]))*)*)$", "must be a valid IETF Relative JSON Pointer"));
COMMON_BUILTIN_FORMATS.add(pattern("uri-template", "^([^\\p{Cntrl}\"'%<>\\^`\\{|\\}]|%\\p{XDigit}{2}|\\{[+#./;?&=,!@|]?((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?)(,((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?))*\\})*$", "must be a valid RFC 6570 URI Template"));
COMMON_BUILTIN_FORMATS.add(pattern("uuid", "^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$", "must be a valid RFC 4122 UUID"));
COMMON_BUILTIN_FORMATS.add(new DateFormat());
COMMON_BUILTIN_FORMATS.add(new EmailFormat());
COMMON_BUILTIN_FORMATS.add(new IriFormat());
COMMON_BUILTIN_FORMATS.add(new IriReferenceFormat());
COMMON_BUILTIN_FORMATS.add(new RegexFormat());
COMMON_BUILTIN_FORMATS.add(new TimeFormat());
COMMON_BUILTIN_FORMATS.add(new UriFormat());
COMMON_BUILTIN_FORMATS.add(new UriReferenceFormat());

// The following formats do not appear in any draft
COMMON_BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$"));
COMMON_BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$"));
COMMON_BUILTIN_FORMATS.add(pattern("color", "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))"));
COMMON_BUILTIN_FORMATS.add(pattern("ip-address", "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
COMMON_BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$"));
COMMON_BUILTIN_FORMATS.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?"));
COMMON_BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$"));
}

public static class Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.net.URI;
import java.net.URISyntaxException;

public abstract class AbstractRFC3339Format extends AbstractFormat {
public abstract class AbstractRFC3986Format extends AbstractFormat {

public AbstractRFC3339Format(String name, String errorMessageDescription) {
public AbstractRFC3986Format(String name, String errorMessageDescription) {
super(name, errorMessageDescription);
}

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/networknt/schema/format/IriFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.networknt.schema.format;

import java.net.URI;

public class IriFormat extends AbstractRFC3986Format {

public IriFormat() {
super("iri", "must be a valid RFC 3987 IRI");
}

@Override
protected boolean validate(URI uri) {
return uri.isAbsolute();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import java.net.URI;

public class IriReferenceFormat extends AbstractRFC3339Format {
public class IriReferenceFormat extends AbstractRFC3986Format {

public IriReferenceFormat() {
super("iri-reference", "must be a valid RFC 3986 IRI-reference");
super("iri-reference", "must be a valid RFC 3987 IRI-reference");
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/format/RegexFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
public class RegexFormat extends AbstractFormat {

public RegexFormat() {
super("regex", "must be a valid regex");
super("regex", "must be a valid ECMA-262 regular expression");
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/format/TimeFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class TimeFormat extends AbstractFormat {
.toFormatter();

public TimeFormat() {
super("time", "^(?:(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])(?:\\.\\d+)?(?:Z|[+-](?:(?:0[0-9]|2[0-3]):[0-5][0-9]))$");
super("time", "must be a valid RFC 3339 time");
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/format/UriFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.net.URI;

public class UriFormat extends AbstractRFC3339Format {
public class UriFormat extends AbstractRFC3986Format {

public UriFormat() {
super("uri", "must be a valid RFC 3986 URI");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.net.URI;

public class UriReferenceFormat extends AbstractRFC3339Format {
public class UriReferenceFormat extends AbstractRFC3986Format {

public UriReferenceFormat() {
super("uri-reference", "must be a valid RFC 3986 URI-reference");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ private void disableV202012Tests() {
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format-assertion.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/idn-email.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/iri.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/ref.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/refRemote.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/vocabulary.json"));
Expand All @@ -94,7 +93,6 @@ private void disableV201909Tests() {
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/float-overflow.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/idn-email.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/iri.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/recursiveRef.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/ref.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/refRemote.json"));
Expand All @@ -109,7 +107,6 @@ private void disableV7Tests() {
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/float-overflow.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/idn-email.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/idn-hostname.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/iri.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft7/ref.json"));
this.disabled.add(Paths.get("src/test/suite/tests/draft7/refRemote.json"));
}
Expand Down
4 changes: 3 additions & 1 deletion src/test/suite/tests/draft2019-09/optional/format/iri.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
{
"description": "an invalid IRI based on IPv6",
"data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"valid": false
"valid": false,
"disabled": true,
"reason": "URI syntax cannot always distinguish a malformed server-based authority from a legitimate registry-based authority"
},
{
"description": "an invalid relative IRI Reference",
Expand Down
4 changes: 3 additions & 1 deletion src/test/suite/tests/draft2020-12/optional/format/iri.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
{
"description": "an invalid IRI based on IPv6",
"data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"valid": false
"valid": false,
"disabled": true,
"reason": "URI syntax cannot always distinguish a malformed server-based authority from a legitimate registry-based authority"
},
{
"description": "an invalid relative IRI Reference",
Expand Down
4 changes: 3 additions & 1 deletion src/test/suite/tests/draft7/optional/format/iri.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
{
"description": "an invalid IRI based on IPv6",
"data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"valid": false
"valid": false,
"disabled": true,
"reason": "URI syntax cannot always distinguish a malformed server-based authority from a legitimate registry-based authority"
},
{
"description": "an invalid relative IRI Reference",
Expand Down