Skip to content

Commit 4edf203

Browse files
authored
fixes #1248 validating draft4 schemas (#1249)
* fixes #1248 validating draft4 schemas * Address regex factory review comments * Rename regex specification version variable
1 parent 7407ddd commit 4edf203

8 files changed

Lines changed: 118 additions & 10 deletions

src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.networknt.schema.InvalidSchemaException;
2121
import com.networknt.schema.Error;
22+
import com.networknt.schema.SchemaContext;
2223

2324
/**
2425
* {@link RegularExpressionFactory} that allows regular expressions to be used.
@@ -34,9 +35,16 @@ public AllowRegularExpressionFactory(RegularExpressionFactory delegate, Predicat
3435

3536
@Override
3637
public RegularExpression getRegularExpression(String regex) {
38+
return getRegularExpression(regex, null);
39+
}
40+
41+
@Override
42+
public RegularExpression getRegularExpression(String regex, SchemaContext schemaContext) {
3743
if (this.allowed.test(regex)) {
3844
// Allowed to delegate
39-
return this.delegate.getRegularExpression(regex);
45+
return schemaContext != null
46+
? this.delegate.getRegularExpression(regex, schemaContext)
47+
: this.delegate.getRegularExpression(regex);
4048
}
4149
throw new InvalidSchemaException(Error.builder()
4250
.message("Regular expression ''{0}'' is not allowed to be used.").arguments(regex).build());

src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.networknt.schema.regex;
1717

18+
import com.networknt.schema.SchemaContext;
1819
import com.networknt.schema.utils.Classes;
1920

2021
/**
@@ -45,4 +46,9 @@ public static ECMAScriptRegularExpressionFactory getInstance() {
4546
public RegularExpression getRegularExpression(String regex) {
4647
return DELEGATE.getRegularExpression(regex);
4748
}
49+
50+
@Override
51+
public RegularExpression getRegularExpression(String regex, SchemaContext schemaContext) {
52+
return DELEGATE.getRegularExpression(regex, schemaContext);
53+
}
4854
}

src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ class GraalJSRegularExpression implements RegularExpression {
2828
private final Value function;
2929

3030
GraalJSRegularExpression(String regex, GraalJSRegularExpressionContext context) {
31+
this(regex, context, true);
32+
}
33+
34+
GraalJSRegularExpression(String regex, GraalJSRegularExpressionContext context, boolean unicode) {
3135
this.context = context;
3236
synchronized(context.getContext()) {
33-
this.function = context.getRegExpBuilder().execute(regex);
37+
this.function = context.getRegExpBuilder(unicode).execute(regex);
3438
}
3539
}
3640

@@ -40,4 +44,4 @@ public boolean matches(String value) {
4044
return !function.execute(value).isNull();
4145
}
4246
}
43-
}
47+
}

src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
* GraalJSRegularExpressionContext.
2323
*/
2424
public class GraalJSRegularExpressionContext {
25-
private static final String SOURCE = "pattern => {\n"
26-
+ " const regex = new RegExp(pattern, 'u');\n"
25+
private static final String SOURCE = "flags => pattern => {\n"
26+
+ " const regex = new RegExp(pattern, flags);\n"
2727
+ " return text => text.match(regex)\n"
2828
+ "};";
2929

3030
private final Context context;
31+
private final Value nonUnicodeRegExpBuilder;
3132
private final Value regExpBuilder;
3233

3334
/**
@@ -41,7 +42,9 @@ public class GraalJSRegularExpressionContext {
4142
public GraalJSRegularExpressionContext(Context context) {
4243
this.context = context;
4344
synchronized(this.context) {
44-
this.regExpBuilder = this.context.eval("js", SOURCE);
45+
Value builder = this.context.eval("js", SOURCE);
46+
this.nonUnicodeRegExpBuilder = builder.execute("");
47+
this.regExpBuilder = builder.execute("u");
4548
}
4649
}
4750

@@ -63,4 +66,14 @@ public Context getContext() {
6366
public Value getRegExpBuilder() {
6467
return regExpBuilder;
6568
}
69+
70+
/**
71+
* Gets the RegExp builder.
72+
*
73+
* @param unicode whether the unicode flag should be used
74+
* @return the regexp builder
75+
*/
76+
public Value getRegExpBuilder(boolean unicode) {
77+
return unicode ? regExpBuilder : nonUnicodeRegExpBuilder;
78+
}
6679
}

src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package com.networknt.schema.regex;
1717

18+
import com.networknt.schema.SchemaContext;
19+
import com.networknt.schema.SpecificationVersion;
20+
1821
import org.graalvm.polyglot.Context;
1922

2023
/**
@@ -62,4 +65,14 @@ public GraalJSRegularExpressionFactory(Context context) {
6265
public RegularExpression getRegularExpression(String regex) {
6366
return new GraalJSRegularExpression(regex, this.context);
6467
}
68+
69+
@Override
70+
public RegularExpression getRegularExpression(String regex, SchemaContext schemaContext) {
71+
SpecificationVersion specificationVersion = schemaContext != null
72+
? schemaContext.getDialect().getSpecificationVersion()
73+
: null;
74+
boolean unicode = specificationVersion == null
75+
|| specificationVersion.getOrder() >= SpecificationVersion.DRAFT_2019_09.getOrder();
76+
return new GraalJSRegularExpression(regex, this.context, unicode);
77+
}
6578
}

src/main/java/com/networknt/schema/regex/RegularExpression.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public interface RegularExpression {
1111

1212
static RegularExpression compile(String regex, SchemaContext schemaContext) {
1313
if (null == regex) return s -> true;
14-
return schemaContext.getSchemaRegistryConfig().getRegularExpressionFactory().getRegularExpression(regex);
14+
return schemaContext.getSchemaRegistryConfig().getRegularExpressionFactory().getRegularExpression(regex,
15+
schemaContext);
1516
}
1617

17-
}
18+
}

src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.networknt.schema.regex;
1717

18+
import com.networknt.schema.SchemaContext;
19+
1820
/**
1921
* Factory for {@link RegularExpression}.
2022
*/
@@ -26,4 +28,15 @@ public interface RegularExpressionFactory {
2628
* @return the regular expression
2729
*/
2830
RegularExpression getRegularExpression(String regex);
31+
32+
/**
33+
* Gets a {@link RegularExpression} in the context of the schema being evaluated.
34+
*
35+
* @param regex the regular expression text value
36+
* @param schemaContext the schema context
37+
* @return the regular expression
38+
*/
39+
default RegularExpression getRegularExpression(String regex, SchemaContext schemaContext) {
40+
return getRegularExpression(regex);
41+
}
2942
}

src/test/java/com/networknt/schema/regex/RegularExpressionTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
package com.networknt.schema.regex;
1717

1818
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
1920

2021
import java.util.List;
2122

2223
import org.junit.jupiter.api.Test;
2324

2425
import com.networknt.schema.Error;
2526
import com.networknt.schema.InputFormat;
27+
import com.networknt.schema.OutputFormat;
2628
import com.networknt.schema.Schema;
2729
import com.networknt.schema.SchemaRegistry;
2830
import com.networknt.schema.SchemaRegistryConfig;
@@ -32,6 +34,19 @@
3234
* RegularExpressionTest.
3335
*/
3436
public class RegularExpressionTest {
37+
private static final String DRAFT4_REGEX_FORMAT_SCHEMA = "{\r\n"
38+
+ " \"$schema\": \"http://json-schema.org/draft-04/schema#\",\r\n"
39+
+ " \"format\": \"regex\"\r\n"
40+
+ "}";
41+
42+
private static final String DRAFT7_REGEX_FORMAT_SCHEMA = "{\r\n"
43+
+ " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n"
44+
+ " \"format\": \"regex\"\r\n"
45+
+ "}";
46+
47+
private static final String LEGACY_IDENTITY_ESCAPE_REGEX = "\"\\\\a\"";
48+
private static final String ISSUE_1248_REGEX = "\"\\\\d+:\\\\[1-9]+|N\"";
49+
3550
@Test
3651
public void testInvalidRegexValidatorECMA262() throws Exception {
3752
SchemaRegistryConfig schemaRegistryConfig = SchemaRegistryConfig.builder()
@@ -41,9 +56,44 @@ public void testInvalidRegexValidatorECMA262() throws Exception {
4156
Schema schema = schemaRegistry.getSchema("{\r\n"
4257
+ " \"format\": \"regex\"\r\n"
4358
+ "}");
44-
List<Error> errors = schema.validate("\"\\\\a\"", InputFormat.JSON, executionContext -> {
59+
List<Error> errors = schema.validate(LEGACY_IDENTITY_ESCAPE_REGEX, InputFormat.JSON, executionContext -> {
4560
executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true));
4661
});
4762
assertFalse(errors.isEmpty());
4863
}
49-
}
64+
65+
@Test
66+
public void testDraft4RegexFormatValidatorUsesNonUnicodeECMA262() {
67+
assertRegexFormatValid(GraalJSRegularExpressionFactory.getInstance(), DRAFT4_REGEX_FORMAT_SCHEMA,
68+
LEGACY_IDENTITY_ESCAPE_REGEX);
69+
assertRegexFormatValid(GraalJSRegularExpressionFactory.getInstance(), DRAFT4_REGEX_FORMAT_SCHEMA,
70+
ISSUE_1248_REGEX);
71+
}
72+
73+
@Test
74+
public void testDraft7RegexFormatValidatorUsesNonUnicodeECMA262() {
75+
assertRegexFormatValid(GraalJSRegularExpressionFactory.getInstance(), DRAFT7_REGEX_FORMAT_SCHEMA,
76+
ISSUE_1248_REGEX);
77+
}
78+
79+
@Test
80+
public void testDelegatingRegularExpressionFactoriesPreserveSchemaContext() {
81+
assertRegexFormatValid(ECMAScriptRegularExpressionFactory.getInstance(), DRAFT4_REGEX_FORMAT_SCHEMA,
82+
ISSUE_1248_REGEX);
83+
assertRegexFormatValid(new AllowRegularExpressionFactory(GraalJSRegularExpressionFactory.getInstance(),
84+
regex -> true), DRAFT4_REGEX_FORMAT_SCHEMA, ISSUE_1248_REGEX);
85+
}
86+
87+
private static void assertRegexFormatValid(RegularExpressionFactory regularExpressionFactory, String schemaText,
88+
String valueText) {
89+
SchemaRegistryConfig schemaRegistryConfig = SchemaRegistryConfig.builder()
90+
.regularExpressionFactory(regularExpressionFactory).build();
91+
SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(Dialects.getDraft202012(),
92+
builder -> builder.schemaRegistryConfig(schemaRegistryConfig));
93+
Schema schema = schemaRegistry.getSchema(schemaText);
94+
boolean valid = schema.validate(valueText, InputFormat.JSON, OutputFormat.BOOLEAN,
95+
executionContext -> executionContext.executionConfig(
96+
executionConfig -> executionConfig.formatAssertionsEnabled(true)));
97+
assertTrue(valid);
98+
}
99+
}

0 commit comments

Comments
 (0)