Skip to content

Commit 21d2e21

Browse files
committed
merging master
2 parents dae1b7b + 08f89c6 commit 21d2e21

22 files changed

+386
-54
lines changed

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* [Draft 4 or Draft 6?](#draft-4-or-draft-6)
1010
* [Investigating failures](#investigating-failures)
1111
* [JSON report of the failures](#json-report-of-the-failures)
12+
* [Eary failure mode](#early-failure-mode)
13+
* [Default values](#default-values)
1214
* [Format validators](#format-validators)
1315
* [Example](#example)
1416
* [Resolution scopes](#resolution-scopes)
@@ -40,7 +42,7 @@ Add the JitPack repository and the dependency to your `pom.xml` as follows:
4042
<dependency>
4143
<groupId>com.github.everit-org.json-schema</groupId>
4244
<artifactId>org.everit.json.schema</artifactId>
43-
<version>1.6.1</version>
45+
<version>1.7.0</version>
4446
</dependency>
4547
...
4648
<repositories>
@@ -191,6 +193,7 @@ This will print the following output:
191193
#/rectangle/a: -5.0 is not higher or equal to 0
192194
#/rectangle/b: expected type: Number, found: String
193195
```
196+
194197
### JSON report of the failures
195198

196199
Since version `1.4.0` it is possible to print the `ValidationException` instances as
@@ -208,6 +211,61 @@ following keys:
208211
Please take into account that the complete failure report is a *hierarchical tree structure*: sub-causes of a cause can
209212
be obtained using `#getCausingExceptions()` .
210213

214+
## Early failure mode
215+
216+
By default the validation error reporting in collecting mode (see the "Investigating failures" chapter). That is convenient for having a
217+
detailed error report, but under some circumstances it is more appropriate to stop the validation when a failure is found without
218+
checking the rest of the JSON document. To toggle this fast-failing validation mode
219+
* you have to explicitly build a `Validator` instance for your schema instead of calling `Schema#validate(input)`
220+
* you have to call the `failEarly()` method of `ValidatorBuilder`
221+
222+
Example:
223+
224+
```java
225+
import org.everit.json.schema.Validator;
226+
...
227+
Validator validator = Validator.builder()
228+
.failEarly()
229+
.build();
230+
validator.performValidation(schema, input);
231+
```
232+
233+
_Note: the `Validator` class is immutable and thread-safe, so you don't have to create a new one for each validation, it is enough
234+
to configure it only once._
235+
236+
237+
## Default values
238+
239+
The JSON Schema specification defines the "default" keyword for denoting default values, though it doesn't explicitly state how it should
240+
affect the validation process. By default this library doesn't set the default values, but if you need this feature, you can turn it on
241+
by the `SchemaLoaderBuilder#useDefaults(boolean)` method, before loading the schema:
242+
243+
```json
244+
{
245+
"properties": {
246+
"prop": {
247+
"type": "number",
248+
"default": 1
249+
}
250+
}
251+
}
252+
```
253+
254+
255+
```java
256+
JSONObject input = new JSONObject("{}");
257+
System.out.println(input.get("prop")); // prints null
258+
Schema schema = SchemaLoader.builder()
259+
.useDefaults(true)
260+
.schemaJson(rawSchema)
261+
.build()
262+
.load().build();
263+
schema.validate(input);
264+
System.out.println(input.get("prop")); // prints 1
265+
```
266+
267+
If there are some properties missing from `input` which have `"default"` values in the schema, then they will be set by the validator
268+
during validation.
211269

212270
## Format validators
213271

core/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<modelVersion>4.0.0</modelVersion>
2222
<groupId>org.everit.json</groupId>
2323
<artifactId>org.everit.json.schema</artifactId>
24-
<version>1.7.0-SNAPSHOT</version>
24+
<version>1.7.0</version>
2525
<packaging>bundle</packaging>
2626
<properties>
2727
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -136,7 +136,7 @@
136136
<dependency>
137137
<groupId>org.json</groupId>
138138
<artifactId>json</artifactId>
139-
<version>20171018</version>
139+
<version>20180130</version>
140140
</dependency>
141141
<dependency>
142142
<groupId>com.google.guava</groupId>

core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,7 @@ public ArraySchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
3030
}
3131

3232
@Override void visitArraySchema(ArraySchema arraySchema) {
33-
if (!(subject instanceof JSONArray)) {
34-
if (arraySchema.requiresArray()) {
35-
owner.failure(JSONArray.class, subject);
36-
}
37-
} else {
33+
if (owner.passesTypeCheck(JSONArray.class, arraySchema.requiresArray(), arraySchema.isNullable())) {
3834
this.arraySubject = (JSONArray) subject;
3935
this.subjectLength = arraySubject.length();
4036
this.arraySchema = arraySchema;

core/src/main/java/org/everit/json/schema/ConstSchema.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static org.everit.json.schema.EnumSchema.toJavaValue;
44

5+
import org.everit.json.schema.internal.JSONPrinter;
6+
57
public class ConstSchema extends Schema {
68

79
public static class ConstSchemaBuilder extends Schema.Builder<ConstSchema> {
@@ -29,6 +31,11 @@ protected ConstSchema(ConstSchemaBuilder builder) {
2931
this.permittedValue = toJavaValue(builder.permittedValue);
3032
}
3133

34+
@Override void describePropertiesTo(JSONPrinter writer) {
35+
writer.key("const");
36+
writer.value(this.permittedValue);
37+
}
38+
3239
@Override void accept(Visitor visitor) {
3340
visitor.visitConstSchema(this);
3441
}

core/src/main/java/org/everit/json/schema/NumberSchemaValidatingVisitor.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,23 @@ class NumberSchemaValidatingVisitor extends Visitor {
88

99
private final Object subject;
1010

11-
private final ValidationFailureReporter failureReporter;
11+
private final ValidatingVisitor owner;
1212

1313
private boolean exclusiveMinimum;
1414

1515
private boolean exclusiveMaximum;
1616

1717
private double numberSubject;
1818

19-
NumberSchemaValidatingVisitor(Object subject, ValidationFailureReporter failureReporter) {
19+
NumberSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
2020
this.subject = subject;
21-
this.failureReporter = failureReporter;
21+
this.owner= owner;
2222
}
2323

2424
@Override void visitNumberSchema(NumberSchema numberSchema) {
25-
if (!(subject instanceof Number)) {
26-
if (numberSchema.isRequiresNumber()) {
27-
failureReporter.failure(Number.class, subject);
28-
}
29-
} else {
25+
if (owner.passesTypeCheck(Number.class, numberSchema.isRequiresNumber(), numberSchema.isNullable())) {
3026
if (!(subject instanceof Integer || subject instanceof Long) && numberSchema.requiresInteger()) {
31-
failureReporter.failure(Integer.class, subject);
27+
owner.failure(Integer.class, subject);
3228
} else {
3329
this.numberSubject = ((Number) subject).doubleValue();
3430
super.visitNumberSchema(numberSchema);
@@ -45,16 +41,16 @@ class NumberSchemaValidatingVisitor extends Visitor {
4541
return;
4642
}
4743
if (exclusiveMinimum && numberSubject <= minimum.doubleValue()) {
48-
failureReporter.failure(subject + " is not greater than " + minimum, "exclusiveMinimum");
44+
owner.failure(subject + " is not greater than " + minimum, "exclusiveMinimum");
4945
} else if (numberSubject < minimum.doubleValue()) {
50-
failureReporter.failure(subject + " is not greater or equal to " + minimum, "minimum");
46+
owner.failure(subject + " is not greater or equal to " + minimum, "minimum");
5147
}
5248
}
5349

5450
@Override void visitExclusiveMinimumLimit(Number exclusiveMinimumLimit) {
5551
if (exclusiveMinimumLimit != null) {
5652
if (numberSubject <= exclusiveMinimumLimit.doubleValue()) {
57-
failureReporter.failure(subject + " is not greater than " + exclusiveMinimumLimit, "exclusiveMinimum");
53+
owner.failure(subject + " is not greater than " + exclusiveMinimumLimit, "exclusiveMinimum");
5854
}
5955
}
6056
}
@@ -64,9 +60,9 @@ class NumberSchemaValidatingVisitor extends Visitor {
6460
return;
6561
}
6662
if (exclusiveMaximum && maximum.doubleValue() <= numberSubject) {
67-
failureReporter.failure(subject + " is not less than " + maximum, "exclusiveMaximum");
63+
owner.failure(subject + " is not less than " + maximum, "exclusiveMaximum");
6864
} else if (maximum.doubleValue() < numberSubject) {
69-
failureReporter.failure(subject + " is not less or equal to " + maximum, "maximum");
65+
owner.failure(subject + " is not less or equal to " + maximum, "maximum");
7066
}
7167
}
7268

@@ -77,7 +73,7 @@ class NumberSchemaValidatingVisitor extends Visitor {
7773
@Override void visitExclusiveMaximumLimit(Number exclusiveMaximumLimit) {
7874
if (exclusiveMaximumLimit != null) {
7975
if (numberSubject >= exclusiveMaximumLimit.doubleValue()) {
80-
failureReporter.failure(format("is not less than " + exclusiveMaximumLimit), "exclusiveMaximum");
76+
owner.failure(format("is not less than " + exclusiveMaximumLimit), "exclusiveMaximum");
8177
}
8278
}
8379
}
@@ -87,7 +83,7 @@ class NumberSchemaValidatingVisitor extends Visitor {
8783
BigDecimal remainder = BigDecimal.valueOf(numberSubject).remainder(
8884
BigDecimal.valueOf(multipleOf.doubleValue()));
8985
if (remainder.compareTo(BigDecimal.ZERO) != 0) {
90-
failureReporter.failure(subject + " is not a multiple of " + multipleOf, "multipleOf");
86+
owner.failure(subject + " is not a multiple of " + multipleOf, "multipleOf");
9187
}
9288
}
9389
}

core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,7 @@ public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
2828
}
2929

3030
@Override void visitObjectSchema(ObjectSchema objectSchema) {
31-
if (!(subject instanceof JSONObject)) {
32-
if (objectSchema.requiresObject()) {
33-
owner.failure(JSONObject.class, subject);
34-
}
35-
} else {
31+
if (owner.passesTypeCheck(JSONObject.class, objectSchema.requiresObject(), objectSchema.isNullable())) {
3632
objSubject = (JSONObject) subject;
3733
objectSize = objSubject.length();
3834
this.schema = objectSchema;

core/src/main/java/org/everit/json/schema/Schema.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public abstract static class Builder<S extends Schema> {
3131

3232
private Object defaultValue;
3333

34+
private Boolean nullable = null;
35+
3436
public Builder<S> title(String title) {
3537
this.title = title;
3638
return this;
@@ -56,10 +58,15 @@ public Builder<S> defaultValue(Object defaultValue) {
5658
return this;
5759
}
5860

61+
public Builder<S> nullable(Boolean nullable) {
62+
this.nullable = nullable;
63+
return this;
64+
}
65+
5966
public abstract S build();
6067

61-
}
6268

69+
}
6370
private final String title;
6471

6572
private final String description;
@@ -70,6 +77,8 @@ public Builder<S> defaultValue(Object defaultValue) {
7077

7178
private final Object defaultValue;
7279

80+
private final Boolean nullable;
81+
7382
/**
7483
* Constructor.
7584
*
@@ -82,6 +91,7 @@ protected Schema(Builder<?> builder) {
8291
this.id = builder.id;
8392
this.schemaLocation = builder.schemaLocation;
8493
this.defaultValue = builder.defaultValue;
94+
this.nullable = builder.nullable;
8595
}
8696

8797
/**
@@ -154,15 +164,16 @@ public boolean equals(Object o) {
154164
Objects.equals(title, schema.title) &&
155165
Objects.equals(defaultValue, schema.defaultValue) &&
156166
Objects.equals(description, schema.description) &&
157-
Objects.equals(id, schema.id);
167+
Objects.equals(id, schema.id) &&
168+
Objects.equals(nullable, schema.nullable);
158169
} else {
159170
return false;
160171
}
161172
}
162173

163174
@Override
164175
public int hashCode() {
165-
return Objects.hash(title, description, id, defaultValue);
176+
return Objects.hash(title, description, id, defaultValue, nullable);
166177
}
167178

168179
public String getTitle() {
@@ -189,6 +200,9 @@ public boolean hasDefaultValue() {
189200
return this.defaultValue != null;
190201
}
191202

203+
public Boolean isNullable() {
204+
return nullable;
205+
}
192206
/**
193207
* Describes the instance as a JSONObject to {@code writer}.
194208
* <p>
@@ -206,6 +220,7 @@ public void describeTo(JSONPrinter writer) {
206220
writer.ifPresent("description", description);
207221
writer.ifPresent("id", id);
208222
writer.ifPresent("default", defaultValue);
223+
writer.ifPresent("nullable", nullable);
209224
describePropertiesTo(writer);
210225
writer.endObject();
211226
}

core/src/main/java/org/everit/json/schema/StringSchemaValidatingVisitor.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,15 @@ public class StringSchemaValidatingVisitor extends Visitor {
1414

1515
private int stringLength;
1616

17-
private final ValidationFailureReporter failureReporter;
17+
private final ValidatingVisitor owner;
1818

19-
public StringSchemaValidatingVisitor(Object subject, ValidationFailureReporter failureReporter) {
19+
public StringSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
2020
this.subject = subject;
21-
this.failureReporter = requireNonNull(failureReporter, "failureReporter cannot be null");
21+
this.owner = requireNonNull(owner, "failureReporter cannot be null");
2222
}
2323

2424
@Override void visitStringSchema(StringSchema stringSchema) {
25-
if (!(subject instanceof String)) {
26-
if (stringSchema.requireString()) {
27-
failureReporter.failure(String.class, subject);
28-
}
29-
} else {
25+
if (owner.passesTypeCheck(String.class, stringSchema.requireString(), stringSchema.isNullable())) {
3026
stringSubject = (String) subject;
3127
stringLength = stringSubject.codePointCount(0, stringSubject.length());
3228
super.visitStringSchema(stringSchema);
@@ -35,14 +31,14 @@ public StringSchemaValidatingVisitor(Object subject, ValidationFailureReporter f
3531

3632
@Override void visitMinLength(Integer minLength) {
3733
if (minLength != null && stringLength < minLength.intValue()) {
38-
failureReporter.failure("expected minLength: " + minLength + ", actual: "
34+
owner.failure("expected minLength: " + minLength + ", actual: "
3935
+ stringLength, "minLength");
4036
}
4137
}
4238

4339
@Override void visitMaxLength(Integer maxLength) {
4440
if (maxLength != null && stringLength > maxLength.intValue()) {
45-
failureReporter.failure("expected maxLength: " + maxLength + ", actual: "
41+
owner.failure("expected maxLength: " + maxLength + ", actual: "
4642
+ stringLength, "maxLength");
4743
}
4844
}
@@ -51,14 +47,15 @@ public StringSchemaValidatingVisitor(Object subject, ValidationFailureReporter f
5147
if (pattern != null && !pattern.matcher(stringSubject).find()) {
5248
String message = format("string [%s] does not match pattern %s",
5349
subject, pattern.pattern());
54-
failureReporter.failure(message, "pattern");
50+
owner.failure(message, "pattern");
5551
}
5652
}
5753

5854
@Override void visitFormat(FormatValidator formatValidator) {
5955
Optional<String> failure = formatValidator.validate(stringSubject);
6056
if (failure.isPresent()) {
61-
failureReporter.failure(failure.get(), "format");
57+
owner.failure(failure.get(), "format");
6258
}
6359
}
60+
6461
}

0 commit comments

Comments
 (0)