Skip to content

Commit 340435c

Browse files
committed
wiring up format validators in SchemaLoader, introducing SchemaLoaderBuilder class (mostly for adding custom schema validators)
1 parent 685e567 commit 340435c

File tree

4 files changed

+230
-67
lines changed

4 files changed

+230
-67
lines changed

core/src/main/java/org/everit/json/schema/loader/SchemaLoader.java

Lines changed: 167 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Objects;
26+
import java.util.Optional;
2627
import java.util.Set;
2728
import java.util.function.Consumer;
2829
import java.util.function.Function;
@@ -34,6 +35,7 @@
3435
import org.everit.json.schema.CombinedSchema;
3536
import org.everit.json.schema.EmptySchema;
3637
import org.everit.json.schema.EnumSchema;
38+
import org.everit.json.schema.FormatValidator;
3739
import org.everit.json.schema.NotSchema;
3840
import org.everit.json.schema.NullSchema;
3941
import org.everit.json.schema.NumberSchema;
@@ -43,6 +45,12 @@
4345
import org.everit.json.schema.Schema;
4446
import org.everit.json.schema.SchemaException;
4547
import org.everit.json.schema.StringSchema;
48+
import org.everit.json.schema.internal.DateTimeFormatValidator;
49+
import org.everit.json.schema.internal.EmailFormatValidator;
50+
import org.everit.json.schema.internal.HostnameFormatValidator;
51+
import org.everit.json.schema.internal.IPV4Validator;
52+
import org.everit.json.schema.internal.IPV6Validator;
53+
import org.everit.json.schema.internal.URIFormatValidator;
4654
import org.everit.json.schema.loader.internal.DefaultSchemaClient;
4755
import org.everit.json.schema.loader.internal.JSONPointer;
4856
import org.everit.json.schema.loader.internal.JSONPointer.QueryResult;
@@ -61,7 +69,71 @@ public class SchemaLoader {
6169
*/
6270
@FunctionalInterface
6371
private interface CombinedSchemaProvider
64-
extends Function<Collection<Schema>, CombinedSchema.Builder> {
72+
extends Function<Collection<Schema>, CombinedSchema.Builder> {
73+
74+
}
75+
76+
public static class SchemaLoaderBuilder {
77+
78+
SchemaClient httpClient = new DefaultSchemaClient();
79+
80+
JSONObject schemaJson;
81+
82+
JSONObject rootSchemaJson;
83+
84+
Map<String, ReferenceSchema.Builder> pointerSchemas = new HashMap<>();
85+
86+
String id;
87+
88+
Map<String, FormatValidator> formatValidators = new HashMap<>();
89+
90+
{
91+
formatValidators.put("date-time", new DateTimeFormatValidator());
92+
formatValidators.put("uri", new URIFormatValidator());
93+
formatValidators.put("email", new EmailFormatValidator());
94+
formatValidators.put("ipv4", new IPV4Validator());
95+
formatValidators.put("ipv6", new IPV6Validator());
96+
formatValidators.put("hostname", new HostnameFormatValidator());
97+
}
98+
99+
public SchemaLoaderBuilder addFormatValidator(final String formatName,
100+
final FormatValidator formatValidator) {
101+
formatValidators.put(formatName, formatValidator);
102+
return this;
103+
}
104+
105+
public SchemaLoader build() {
106+
return new SchemaLoader(this);
107+
}
108+
109+
public JSONObject getRootSchemaJson() {
110+
return rootSchemaJson == null ? schemaJson : rootSchemaJson;
111+
}
112+
113+
public SchemaLoaderBuilder httpClient(final SchemaClient httpClient) {
114+
this.httpClient = httpClient;
115+
return this;
116+
}
117+
118+
SchemaLoaderBuilder id(final String id) {
119+
this.id = id;
120+
return this;
121+
}
122+
123+
SchemaLoaderBuilder pointerSchemas(final Map<String, ReferenceSchema.Builder> pointerSchemas) {
124+
this.pointerSchemas = pointerSchemas;
125+
return this;
126+
}
127+
128+
SchemaLoaderBuilder rootSchemaJson(final JSONObject rootSchemaJson) {
129+
this.rootSchemaJson = rootSchemaJson;
130+
return this;
131+
}
132+
133+
public SchemaLoaderBuilder schemaJson(final JSONObject schemaJson) {
134+
this.schemaJson = schemaJson;
135+
return this;
136+
}
65137

66138
}
67139

@@ -83,14 +155,18 @@ private interface CombinedSchemaProvider
83155
"additionalProperties");
84156

85157
private static final List<String> STRING_SCHEMA_PROPS = Arrays.asList("minLength", "maxLength",
86-
"pattern");
158+
"pattern", "format");
87159

88160
static {
89161
COMB_SCHEMA_PROVIDERS.put("allOf", CombinedSchema::allOf);
90162
COMB_SCHEMA_PROVIDERS.put("anyOf", CombinedSchema::anyOf);
91163
COMB_SCHEMA_PROVIDERS.put("oneOf", CombinedSchema::oneOf);
92164
}
93165

166+
public static SchemaLoaderBuilder builder() {
167+
return new SchemaLoaderBuilder();
168+
}
169+
94170
/**
95171
* Loads a JSON schema to a schema validator using a {@link DefaultSchemaClient default HTTP
96172
* client}.
@@ -114,8 +190,11 @@ public static Schema load(final JSONObject schemaJson) {
114190
*/
115191
public static Schema load(final JSONObject schemaJson, final SchemaClient httpClient) {
116192
String schemaId = schemaJson.optString("id");
117-
return new SchemaLoader(schemaId, schemaJson, schemaJson, new HashMap<>(), httpClient)
118-
.load().build();
193+
SchemaLoader loader = builder().id(schemaId)
194+
.schemaJson(schemaJson)
195+
.httpClient(httpClient)
196+
.build();
197+
return loader.load().build();
119198
}
120199

121200
private final SchemaClient httpClient;
@@ -128,44 +207,65 @@ public static Schema load(final JSONObject schemaJson, final SchemaClient httpCl
128207

129208
private final JSONObject schemaJson;
130209

210+
private final Map<String, FormatValidator> formatValidators;
211+
212+
public SchemaLoader(final SchemaLoaderBuilder builder) {
213+
this.schemaJson = Objects.requireNonNull(builder.schemaJson, "schemaJson cannot be null");
214+
this.rootSchemaJson = Objects.requireNonNull(builder.getRootSchemaJson(),
215+
"rootSchemaJson cannot be null");
216+
this.id = builder.id;
217+
this.httpClient = Objects.requireNonNull(builder.httpClient, "httpClient cannot be null");
218+
this.pointerSchemas = Objects.requireNonNull(builder.pointerSchemas,
219+
"pointerSchemas cannot be null");
220+
this.formatValidators = Objects.requireNonNull(builder.formatValidators,
221+
"formatValidators cannot be null");
222+
}
223+
131224
/**
132225
* Constructor.
226+
*
227+
* @deprecated use {@link SchemaLoader#SchemaLoader(SchemaLoaderBuilder)} instead.
133228
*/
229+
@Deprecated
134230
SchemaLoader(final String id, final JSONObject schemaJson,
135231
final JSONObject rootSchemaJson, final Map<String, ReferenceSchema.Builder> pointerSchemas,
136232
final SchemaClient httpClient) {
137-
this.schemaJson = Objects.requireNonNull(schemaJson, "schemaJson cannot be null");
138-
this.rootSchemaJson = Objects.requireNonNull(rootSchemaJson, "rootSchemaJson cannot be null");
139-
this.id = id;
140-
this.httpClient = Objects.requireNonNull(httpClient, "httpClient cannot be null");
141-
this.pointerSchemas = pointerSchemas;
233+
this(builder().schemaJson(schemaJson)
234+
.rootSchemaJson(rootSchemaJson)
235+
.id(id)
236+
.httpClient(httpClient)
237+
.pointerSchemas(pointerSchemas));
142238
}
143239

144240
private void addDependencies(final Builder builder, final JSONObject deps) {
145241
Arrays.stream(JSONObject.getNames(deps))
146-
.forEach(ifPresent -> addDependency(builder, ifPresent, deps.get(ifPresent)));
242+
.forEach(ifPresent -> addDependency(builder, ifPresent, deps.get(ifPresent)));
147243
}
148244

149245
private void addDependency(final Builder builder, final String ifPresent, final Object deps) {
150246
typeMultiplexer(deps)
151-
.ifObject().then(obj -> {
152-
builder.schemaDependency(ifPresent, loadChild(obj).build());
153-
})
154-
.ifIs(JSONArray.class).then(propNames -> {
155-
IntStream.range(0, propNames.length())
156-
.mapToObj(i -> propNames.getString(i))
157-
.forEach(dependency -> builder.propertyDependency(ifPresent, dependency));
158-
}).requireAny();
247+
.ifObject().then(obj -> {
248+
builder.schemaDependency(ifPresent, loadChild(obj).build());
249+
})
250+
.ifIs(JSONArray.class).then(propNames -> {
251+
IntStream.range(0, propNames.length())
252+
.mapToObj(i -> propNames.getString(i))
253+
.forEach(dependency -> builder.propertyDependency(ifPresent, dependency));
254+
}).requireAny();
255+
}
256+
257+
private void addFormatValidator(final StringSchema.Builder builder, final String formatName) {
258+
getFormatValidator(formatName).ifPresent(builder::formatValidator);
159259
}
160260

161261
private void addPropertySchemaDefinition(final String keyOfObj, final Object definition,
162262
final ObjectSchema.Builder builder) {
163263
typeMultiplexer(definition)
164-
.ifObject()
165-
.then(obj -> {
166-
builder.addPropertySchema(keyOfObj, loadChild(obj).build());
167-
})
168-
.requireAny();
264+
.ifObject()
265+
.then(obj -> {
266+
builder.addPropertySchema(keyOfObj, loadChild(obj).build());
267+
})
268+
.requireAny();
169269
}
170270

171271
private CombinedSchema.Builder buildAnyOfSchemaForMultipleTypes() {
@@ -188,15 +288,15 @@ private ArraySchema.Builder buildArraySchema() {
188288
ifPresent("uniqueItems", Boolean.class, builder::uniqueItems);
189289
if (schemaJson.has("additionalItems")) {
190290
typeMultiplexer("additionalItems", schemaJson.get("additionalItems"))
191-
.ifIs(Boolean.class).then(builder::additionalItems)
192-
.ifObject().then(jsonObj -> builder.schemaOfAdditionalItems(loadChild(jsonObj).build()))
193-
.requireAny();
291+
.ifIs(Boolean.class).then(builder::additionalItems)
292+
.ifObject().then(jsonObj -> builder.schemaOfAdditionalItems(loadChild(jsonObj).build()))
293+
.requireAny();
194294
}
195295
if (schemaJson.has("items")) {
196296
typeMultiplexer("items", schemaJson.get("items"))
197-
.ifObject().then(itemSchema -> builder.allItemSchema(loadChild(itemSchema).build()))
198-
.ifIs(JSONArray.class).then(arr -> buildTupleSchema(builder, arr))
199-
.requireAny();
297+
.ifObject().then(itemSchema -> builder.allItemSchema(loadChild(itemSchema).build()))
298+
.ifIs(JSONArray.class).then(arr -> buildTupleSchema(builder, arr))
299+
.requireAny();
200300
}
201301
return builder;
202302
}
@@ -205,8 +305,8 @@ private EnumSchema.Builder buildEnumSchema() {
205305
Set<Object> possibleValues = new HashSet<>();
206306
JSONArray arr = schemaJson.getJSONArray("enum");
207307
IntStream.range(0, arr.length())
208-
.mapToObj(arr::get)
209-
.forEach(possibleValues::add);
308+
.mapToObj(arr::get)
309+
.forEach(possibleValues::add);
210310
return EnumSchema.builder().possibleValues(possibleValues);
211311
}
212312

@@ -231,21 +331,21 @@ private ObjectSchema.Builder buildObjectSchema() {
231331
ifPresent("maxProperties", Integer.class, builder::maxProperties);
232332
if (schemaJson.has("properties")) {
233333
typeMultiplexer(schemaJson.get("properties"))
234-
.ifObject().then(propertyDefs -> {
235-
populatePropertySchemas(propertyDefs, builder);
236-
}).requireAny();
334+
.ifObject().then(propertyDefs -> {
335+
populatePropertySchemas(propertyDefs, builder);
336+
}).requireAny();
237337
}
238338
if (schemaJson.has("additionalProperties")) {
239339
typeMultiplexer("additionalProperties", schemaJson.get("additionalProperties"))
240-
.ifIs(Boolean.class).then(builder::additionalProperties)
241-
.ifObject().then(def -> builder.schemaOfAdditionalProperties(loadChild(def).build()))
242-
.requireAny();
340+
.ifIs(Boolean.class).then(builder::additionalProperties)
341+
.ifObject().then(def -> builder.schemaOfAdditionalProperties(loadChild(def).build()))
342+
.requireAny();
243343
}
244344
if (schemaJson.has("required")) {
245345
JSONArray requiredJson = schemaJson.getJSONArray("required");
246346
IntStream.range(0, requiredJson.length())
247-
.mapToObj(requiredJson::getString)
248-
.forEach(builder::addRequiredProperty);
347+
.mapToObj(requiredJson::getString)
348+
.forEach(builder::addRequiredProperty);
249349
}
250350
if (schemaJson.has("patternProperties")) {
251351
JSONObject patternPropsJson = schemaJson.getJSONObject("patternProperties");
@@ -283,14 +383,15 @@ private StringSchema.Builder buildStringSchema() {
283383
ifPresent("minLength", Integer.class, builder::minLength);
284384
ifPresent("maxLength", Integer.class, builder::maxLength);
285385
ifPresent("pattern", String.class, builder::pattern);
386+
ifPresent("format", String.class, format -> addFormatValidator(builder, format));
286387
return builder;
287388
}
288389

289390
private void buildTupleSchema(final ArraySchema.Builder builder, final JSONArray itemSchema) {
290391
for (int i = 0; i < itemSchema.length(); ++i) {
291392
typeMultiplexer(itemSchema.get(i))
292-
.ifObject().then(schema -> builder.addItemSchema(loadChild(schema).build()))
293-
.requireAny();
393+
.ifObject().then(schema -> builder.addItemSchema(loadChild(schema).build()))
394+
.requireAny();
294395
}
295396
}
296397

@@ -315,6 +416,10 @@ JSONObject extend(final JSONObject additional, final JSONObject original) {
315416
return rval;
316417
}
317418

419+
Optional<FormatValidator> getFormatValidator(final String format) {
420+
return Optional.ofNullable(formatValidators.get(format));
421+
}
422+
318423
private <E> void ifPresent(final String key, final Class<E> expectedType,
319424
final Consumer<E> consumer) {
320425
if (schemaJson.has(key)) {
@@ -335,7 +440,7 @@ private <E> void ifPresent(final String key, final Class<E> expectedType,
335440
* {@link Schema.Builder#build()} can be immediately used to acquire the {@link Schema}
336441
* instance to be used for validation
337442
*/
338-
private Schema.Builder<?> load() {
443+
public Schema.Builder<?> load() {
339444
Schema.Builder<?> builder;
340445
if (schemaJson.has("enum")) {
341446
builder = buildEnumSchema();
@@ -356,8 +461,7 @@ private Schema.Builder<?> load() {
356461
}
357462

358463
private Schema.Builder<?> loadChild(final JSONObject childJson) {
359-
return new SchemaLoader(id, childJson, rootSchemaJson, pointerSchemas,
360-
httpClient).load();
464+
return selfBuilder().schemaJson(childJson).build().load();
361465
}
362466

363467
private Schema.Builder<?> loadForExplicitType(final String typeString) {
@@ -401,16 +505,16 @@ private Schema.Builder<?> lookupReference(final String relPointerString, final J
401505
}
402506
JSONPointer pointer = absPointerString.startsWith("#")
403507
? JSONPointer.forDocument(rootSchemaJson, absPointerString)
404-
: JSONPointer.forURL(httpClient, absPointerString);
405-
ReferenceSchema.Builder refBuilder = ReferenceSchema.builder();
406-
pointerSchemas.put(absPointerString, refBuilder);
407-
QueryResult result = pointer.query();
408-
JSONObject resultObject = extend(withoutRef(ctx), result.getQueryResult());
409-
SchemaLoader childLoader = new SchemaLoader(id, resultObject,
410-
result.getContainingDocument(), pointerSchemas, httpClient);
411-
Schema referredSchema = childLoader.load().build();
412-
refBuilder.build().setReferredSchema(referredSchema);
413-
return refBuilder;
508+
: JSONPointer.forURL(httpClient, absPointerString);
509+
ReferenceSchema.Builder refBuilder = ReferenceSchema.builder();
510+
pointerSchemas.put(absPointerString, refBuilder);
511+
QueryResult result = pointer.query();
512+
JSONObject resultObject = extend(withoutRef(ctx), result.getQueryResult());
513+
SchemaLoader childLoader = selfBuilder().schemaJson(resultObject)
514+
.rootSchemaJson(result.getContainingDocument()).build();
515+
Schema referredSchema = childLoader.load().build();
516+
refBuilder.build().setReferredSchema(referredSchema);
517+
return refBuilder;
414518
}
415519

416520
private void populatePropertySchemas(final JSONObject propertyDefs,
@@ -428,6 +532,13 @@ private boolean schemaHasAnyOf(final Collection<String> propNames) {
428532
return propNames.stream().filter(schemaJson::has).findAny().isPresent();
429533
}
430534

535+
private SchemaLoaderBuilder selfBuilder() {
536+
return builder().id(id).schemaJson(schemaJson)
537+
.rootSchemaJson(rootSchemaJson)
538+
.pointerSchemas(pointerSchemas)
539+
.httpClient(httpClient);
540+
}
541+
431542
private Schema.Builder<?> sniffSchemaByProps() {
432543
if (schemaHasAnyOf(ARRAY_SCHEMA_PROPS)) {
433544
return buildArraySchema().requiresArray(false);
@@ -497,8 +608,8 @@ JSONObject withoutRef(final JSONObject original) {
497608
}
498609
JSONObject rval = new JSONObject();
499610
Arrays.stream(names)
500-
.filter(name -> !"$ref".equals(name))
501-
.forEach(name -> rval.put(name, original.get(name)));
611+
.filter(name -> !"$ref".equals(name))
612+
.forEach(name -> rval.put(name, original.get(name)));
502613
return rval;
503614
}
504615
}

0 commit comments

Comments
 (0)