Skip to content

Commit 1e81668

Browse files
fduttonFaron Duttonstevehu
authored
Supports uri-reference format. (#764)
* Supports relative-json-pointer validation. Resolves #761 * Supports uri-reference format. Resolves #763 --------- Co-authored-by: Faron Dutton <[email protected]> Co-authored-by: Steve Hu <[email protected]>
1 parent d87c019 commit 1e81668

File tree

8 files changed

+79
-69
lines changed

8 files changed

+79
-69
lines changed

src/main/java/com/networknt/schema/JsonMetaSchema.java

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import com.networknt.schema.format.PatternFormat;
2323
import com.networknt.schema.format.RegexFormat;
2424
import com.networknt.schema.format.TimeFormat;
25+
import com.networknt.schema.format.UriFormat;
26+
import com.networknt.schema.format.UriReferenceFormat;
2527
import com.networknt.schema.utils.StringUtils;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
@@ -34,66 +36,38 @@ public class JsonMetaSchema {
3436
private static final Logger logger = LoggerFactory.getLogger(JsonMetaSchema.class);
3537
private static Map<String, String> UNKNOWN_KEYWORDS = new ConcurrentHashMap<>();
3638

39+
static PatternFormat pattern(String name, String regex, String description) {
40+
return new PatternFormat(name, regex, description);
41+
}
42+
3743
static PatternFormat pattern(String name, String regex) {
38-
return new PatternFormat(name, regex);
44+
return pattern(name, regex, null);
3945
}
4046

4147
public static final List<Format> COMMON_BUILTIN_FORMATS = new ArrayList<>();
4248

4349
// this section contains formats that is common for all specification versions.
4450
static {
45-
// From RFC 3986
46-
// ALPHA [A-Za-z]
47-
// DIGIT [0-9]
48-
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
49-
// => [A-Za-z][A-Za-z0-9+.-]*
50-
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
51-
// => [A-Za-z0-9._~\-]
52-
// gen-delims [:/?#\[\]@]
53-
// sub-delims [!$&'()*+,;=]
54-
// reserved = = gen-delims / sub-delims
55-
// => [:/?#\[\]@!$&'()*+,;=]
56-
// pct-encoded = "%" HEXDIG HEXDIG
57-
// => [A-Za-z0-9%] (approximation)
58-
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
59-
// => [A-Za-z0-9._~\-%!$&'()*+,;=:@]
60-
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
61-
// => [A-Za-z0-9._~\-%!$&'()*+,;=:]*
62-
// host = IP-literal / IPv4address / reg-name
63-
// => [A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]* (approximation)
64-
// port = *DIGiT
65-
// => [0-9]*
66-
// authority = [ userinfo "@" ] host [ ":" port ]
67-
// => ([A-Za-z0-9._~\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]*(:[0-9]*)?
68-
// hier-part = "//" authority path-abempty
69-
// / path-absolute
70-
// / path-rootless
71-
// / path-empty
72-
// => (\/\/([A-Za-z0-9._~\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]*(:[0-9]*)?)?[A-Za-z0-9._~\-%!$&'()*+,;=:@\/]* (approximation)
73-
// query = *( pchar / "/" / "?" )
74-
// => [A-Za-z0-9._~\-%!$&'()*+,;=:@\/?]*
75-
// fragment = *( pchar / "/" / "?" )
76-
// => [A-Za-z0-9._~\-%!$&'()*+,;=:@\/?]*
77-
// uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
78-
COMMON_BUILTIN_FORMATS.add(pattern("uri", "^[A-Za-z][A-Za-z0-9+.-]*:(\\/\\/([A-Za-z0-9._~\\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\\-!$&'()*+,;=%:\\[\\]]*(:[0-9]*)?)?[A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/]*([?][A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/?]*)?([#][A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/?]*)?"));
51+
COMMON_BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$"));
52+
COMMON_BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$"));
53+
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*\\))"));
54+
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]))*$"));
7955
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]?)$"));
8056
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])$"));
8157
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*$"));
82-
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*\\))"));
83-
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]))*$"));
84-
COMMON_BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$"));
85-
COMMON_BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$"));
58+
COMMON_BUILTIN_FORMATS.add(pattern("json-pointer", "^(/([^/#~]|[~](?=[01]))*)*$"));
8659
COMMON_BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$"));
87-
COMMON_BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$"));
60+
COMMON_BUILTIN_FORMATS.add(pattern("relative-json-pointer", "^(0|([1-9]\\d*))(#|(/([^/#~]|[~](?=[01]))*)*)$"));
8861
COMMON_BUILTIN_FORMATS.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?"));
89-
COMMON_BUILTIN_FORMATS.add(pattern("uuid", "^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$"));
9062
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}|\\*)?))*\\})*$"));
91-
COMMON_BUILTIN_FORMATS.add(pattern("json-pointer", "^(/([^/#~]|[~](?=[01]))*)*$"));
92-
COMMON_BUILTIN_FORMATS.add(pattern("relative-json-pointer", "^(0|([1-9]\\d*))(#|(/([^/#~]|[~](?=[01]))*)*)$"));
63+
COMMON_BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$"));
64+
COMMON_BUILTIN_FORMATS.add(pattern("uuid", "^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$"));
9365
COMMON_BUILTIN_FORMATS.add(new DateFormat());
9466
COMMON_BUILTIN_FORMATS.add(new EmailFormat());
9567
COMMON_BUILTIN_FORMATS.add(new RegexFormat());
9668
COMMON_BUILTIN_FORMATS.add(new TimeFormat());
69+
COMMON_BUILTIN_FORMATS.add(new UriFormat());
70+
COMMON_BUILTIN_FORMATS.add(new UriReferenceFormat());
9771
}
9872

9973
public static class Builder {

src/main/java/com/networknt/schema/format/AbstractFormat.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ public abstract class AbstractFormat implements Format {
2222
private final String name;
2323
private final String errorMessageDescription;
2424

25-
public AbstractFormat(String name) {
26-
this(name, "");
27-
}
28-
2925
public AbstractFormat(String name, String errorMessageDescription) {
3026
this.name = name;
3127
this.errorMessageDescription = errorMessageDescription;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.networknt.schema.format;
2+
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
6+
public abstract class AbstractRFC3339Format extends AbstractFormat {
7+
8+
public AbstractRFC3339Format(String name, String errorMessageDescription) {
9+
super(name, errorMessageDescription);
10+
}
11+
12+
@Override
13+
public final boolean matches(String value) {
14+
try {
15+
URI uri = new URI(value);
16+
return validate(uri);
17+
} catch (URISyntaxException e) {
18+
return false;
19+
}
20+
}
21+
22+
protected abstract boolean validate(URI uri);
23+
24+
}

src/main/java/com/networknt/schema/format/PatternFormat.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,17 @@
1818

1919
import java.util.regex.Pattern;
2020

21-
import com.networknt.schema.Format;
22-
23-
public class PatternFormat implements Format {
24-
private final String name;
21+
public class PatternFormat extends AbstractFormat {
2522
private final Pattern pattern;
2623

27-
public PatternFormat(String name, String regex) {
28-
this.name = name;
24+
public PatternFormat(String name, String regex, String errorMessageDescription) {
25+
super(name, null != errorMessageDescription ? errorMessageDescription : regex);
2926
this.pattern = Pattern.compile(regex);
3027
}
3128

3229
@Override
3330
public boolean matches(String value) {
34-
return pattern.matcher(value).matches();
35-
}
36-
37-
@Override
38-
public String getName() {
39-
return name;
31+
return this.pattern.matcher(value).matches();
4032
}
4133

42-
@Override
43-
public String getErrorMessageDescription() {
44-
return pattern.pattern();
45-
}
46-
}
34+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.networknt.schema.format;
2+
3+
import java.net.URI;
4+
5+
public class UriFormat extends AbstractRFC3339Format {
6+
7+
public UriFormat() {
8+
super("uri", "must be a valid RFC 3986 URI");
9+
}
10+
11+
@Override
12+
protected boolean validate(URI uri) {
13+
return uri.isAbsolute();
14+
}
15+
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.networknt.schema.format;
2+
3+
import java.net.URI;
4+
5+
public class UriReferenceFormat extends AbstractRFC3339Format {
6+
7+
public UriReferenceFormat() {
8+
super("uri-reference", "must be a valid RFC 3986 URI-reference");
9+
}
10+
11+
@Override
12+
protected boolean validate(URI uri) {
13+
return true;
14+
}
15+
16+
}

src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ private void disableV202012Tests() {
8282
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json"));
8383
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/iri-reference.json"));
8484
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/iri.json"));
85-
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/optional/format/uri-reference.json"));
8685
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/ref.json"));
8786
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/refRemote.json"));
8887
this.disabled.add(Paths.get("src/test/suite/tests/draft2020-12/vocabulary.json"));
@@ -98,7 +97,6 @@ private void disableV201909Tests() {
9897
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json"));
9998
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/iri-reference.json"));
10099
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/iri.json"));
101-
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/optional/format/uri-reference.json"));
102100
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/recursiveRef.json"));
103101
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/ref.json"));
104102
this.disabled.add(Paths.get("src/test/suite/tests/draft2019-09/refRemote.json"));
@@ -115,15 +113,13 @@ private void disableV7Tests() {
115113
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/idn-hostname.json"));
116114
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/iri-reference.json"));
117115
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/iri.json"));
118-
this.disabled.add(Paths.get("src/test/suite/tests/draft7/optional/format/uri-reference.json"));
119116
this.disabled.add(Paths.get("src/test/suite/tests/draft7/ref.json"));
120117
this.disabled.add(Paths.get("src/test/suite/tests/draft7/refRemote.json"));
121118
}
122119

123120
private void disableV6Tests() {
124121
this.disabled.add(Paths.get("src/test/suite/tests/draft6/optional/float-overflow.json"));
125122
this.disabled.add(Paths.get("src/test/suite/tests/draft6/optional/format.json"));
126-
this.disabled.add(Paths.get("src/test/suite/tests/draft6/optional/format/uri-reference.json"));
127123
this.disabled.add(Paths.get("src/test/suite/tests/draft6/ref.json"));
128124
this.disabled.add(Paths.get("src/test/suite/tests/draft6/refRemote.json"));
129125
}

src/test/java/com/networknt/schema/OverrideValidatorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void overrideDefaultValidator() throws JsonProcessingException, IOExcepti
5454
// Override EmailValidator
5555
final JsonMetaSchema overrideValidatorMetaSchema = JsonMetaSchema
5656
.builder(URI, JsonMetaSchema.getV201909())
57-
.addFormat(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"))
57+
.addFormat(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$", null))
5858
.build();
5959

6060
final JsonSchemaFactory overrideValidatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(overrideValidatorMetaSchema).build();

0 commit comments

Comments
 (0)