23
23
import java .util .List ;
24
24
import java .util .Map ;
25
25
import java .util .Objects ;
26
+ import java .util .Optional ;
26
27
import java .util .Set ;
27
28
import java .util .function .Consumer ;
28
29
import java .util .function .Function ;
34
35
import org .everit .json .schema .CombinedSchema ;
35
36
import org .everit .json .schema .EmptySchema ;
36
37
import org .everit .json .schema .EnumSchema ;
38
+ import org .everit .json .schema .FormatValidator ;
37
39
import org .everit .json .schema .NotSchema ;
38
40
import org .everit .json .schema .NullSchema ;
39
41
import org .everit .json .schema .NumberSchema ;
43
45
import org .everit .json .schema .Schema ;
44
46
import org .everit .json .schema .SchemaException ;
45
47
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 ;
46
54
import org .everit .json .schema .loader .internal .DefaultSchemaClient ;
47
55
import org .everit .json .schema .loader .internal .JSONPointer ;
48
56
import org .everit .json .schema .loader .internal .JSONPointer .QueryResult ;
@@ -61,7 +69,71 @@ public class SchemaLoader {
61
69
*/
62
70
@ FunctionalInterface
63
71
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
+ }
65
137
66
138
}
67
139
@@ -83,14 +155,18 @@ private interface CombinedSchemaProvider
83
155
"additionalProperties" );
84
156
85
157
private static final List <String > STRING_SCHEMA_PROPS = Arrays .asList ("minLength" , "maxLength" ,
86
- "pattern" );
158
+ "pattern" , "format" );
87
159
88
160
static {
89
161
COMB_SCHEMA_PROVIDERS .put ("allOf" , CombinedSchema ::allOf );
90
162
COMB_SCHEMA_PROVIDERS .put ("anyOf" , CombinedSchema ::anyOf );
91
163
COMB_SCHEMA_PROVIDERS .put ("oneOf" , CombinedSchema ::oneOf );
92
164
}
93
165
166
+ public static SchemaLoaderBuilder builder () {
167
+ return new SchemaLoaderBuilder ();
168
+ }
169
+
94
170
/**
95
171
* Loads a JSON schema to a schema validator using a {@link DefaultSchemaClient default HTTP
96
172
* client}.
@@ -114,8 +190,11 @@ public static Schema load(final JSONObject schemaJson) {
114
190
*/
115
191
public static Schema load (final JSONObject schemaJson , final SchemaClient httpClient ) {
116
192
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 ();
119
198
}
120
199
121
200
private final SchemaClient httpClient ;
@@ -128,44 +207,65 @@ public static Schema load(final JSONObject schemaJson, final SchemaClient httpCl
128
207
129
208
private final JSONObject schemaJson ;
130
209
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
+
131
224
/**
132
225
* Constructor.
226
+ *
227
+ * @deprecated use {@link SchemaLoader#SchemaLoader(SchemaLoaderBuilder)} instead.
133
228
*/
229
+ @ Deprecated
134
230
SchemaLoader (final String id , final JSONObject schemaJson ,
135
231
final JSONObject rootSchemaJson , final Map <String , ReferenceSchema .Builder > pointerSchemas ,
136
232
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 )) ;
142
238
}
143
239
144
240
private void addDependencies (final Builder builder , final JSONObject deps ) {
145
241
Arrays .stream (JSONObject .getNames (deps ))
146
- .forEach (ifPresent -> addDependency (builder , ifPresent , deps .get (ifPresent )));
242
+ .forEach (ifPresent -> addDependency (builder , ifPresent , deps .get (ifPresent )));
147
243
}
148
244
149
245
private void addDependency (final Builder builder , final String ifPresent , final Object deps ) {
150
246
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 );
159
259
}
160
260
161
261
private void addPropertySchemaDefinition (final String keyOfObj , final Object definition ,
162
262
final ObjectSchema .Builder builder ) {
163
263
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 ();
169
269
}
170
270
171
271
private CombinedSchema .Builder buildAnyOfSchemaForMultipleTypes () {
@@ -188,15 +288,15 @@ private ArraySchema.Builder buildArraySchema() {
188
288
ifPresent ("uniqueItems" , Boolean .class , builder ::uniqueItems );
189
289
if (schemaJson .has ("additionalItems" )) {
190
290
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 ();
194
294
}
195
295
if (schemaJson .has ("items" )) {
196
296
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 ();
200
300
}
201
301
return builder ;
202
302
}
@@ -205,8 +305,8 @@ private EnumSchema.Builder buildEnumSchema() {
205
305
Set <Object > possibleValues = new HashSet <>();
206
306
JSONArray arr = schemaJson .getJSONArray ("enum" );
207
307
IntStream .range (0 , arr .length ())
208
- .mapToObj (arr ::get )
209
- .forEach (possibleValues ::add );
308
+ .mapToObj (arr ::get )
309
+ .forEach (possibleValues ::add );
210
310
return EnumSchema .builder ().possibleValues (possibleValues );
211
311
}
212
312
@@ -231,21 +331,21 @@ private ObjectSchema.Builder buildObjectSchema() {
231
331
ifPresent ("maxProperties" , Integer .class , builder ::maxProperties );
232
332
if (schemaJson .has ("properties" )) {
233
333
typeMultiplexer (schemaJson .get ("properties" ))
234
- .ifObject ().then (propertyDefs -> {
235
- populatePropertySchemas (propertyDefs , builder );
236
- }).requireAny ();
334
+ .ifObject ().then (propertyDefs -> {
335
+ populatePropertySchemas (propertyDefs , builder );
336
+ }).requireAny ();
237
337
}
238
338
if (schemaJson .has ("additionalProperties" )) {
239
339
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 ();
243
343
}
244
344
if (schemaJson .has ("required" )) {
245
345
JSONArray requiredJson = schemaJson .getJSONArray ("required" );
246
346
IntStream .range (0 , requiredJson .length ())
247
- .mapToObj (requiredJson ::getString )
248
- .forEach (builder ::addRequiredProperty );
347
+ .mapToObj (requiredJson ::getString )
348
+ .forEach (builder ::addRequiredProperty );
249
349
}
250
350
if (schemaJson .has ("patternProperties" )) {
251
351
JSONObject patternPropsJson = schemaJson .getJSONObject ("patternProperties" );
@@ -283,14 +383,15 @@ private StringSchema.Builder buildStringSchema() {
283
383
ifPresent ("minLength" , Integer .class , builder ::minLength );
284
384
ifPresent ("maxLength" , Integer .class , builder ::maxLength );
285
385
ifPresent ("pattern" , String .class , builder ::pattern );
386
+ ifPresent ("format" , String .class , format -> addFormatValidator (builder , format ));
286
387
return builder ;
287
388
}
288
389
289
390
private void buildTupleSchema (final ArraySchema .Builder builder , final JSONArray itemSchema ) {
290
391
for (int i = 0 ; i < itemSchema .length (); ++i ) {
291
392
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 ();
294
395
}
295
396
}
296
397
@@ -315,6 +416,10 @@ JSONObject extend(final JSONObject additional, final JSONObject original) {
315
416
return rval ;
316
417
}
317
418
419
+ Optional <FormatValidator > getFormatValidator (final String format ) {
420
+ return Optional .ofNullable (formatValidators .get (format ));
421
+ }
422
+
318
423
private <E > void ifPresent (final String key , final Class <E > expectedType ,
319
424
final Consumer <E > consumer ) {
320
425
if (schemaJson .has (key )) {
@@ -335,7 +440,7 @@ private <E> void ifPresent(final String key, final Class<E> expectedType,
335
440
* {@link Schema.Builder#build()} can be immediately used to acquire the {@link Schema}
336
441
* instance to be used for validation
337
442
*/
338
- private Schema .Builder <?> load () {
443
+ public Schema .Builder <?> load () {
339
444
Schema .Builder <?> builder ;
340
445
if (schemaJson .has ("enum" )) {
341
446
builder = buildEnumSchema ();
@@ -356,8 +461,7 @@ private Schema.Builder<?> load() {
356
461
}
357
462
358
463
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 ();
361
465
}
362
466
363
467
private Schema .Builder <?> loadForExplicitType (final String typeString ) {
@@ -401,16 +505,16 @@ private Schema.Builder<?> lookupReference(final String relPointerString, final J
401
505
}
402
506
JSONPointer pointer = absPointerString .startsWith ("#" )
403
507
? 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 ;
414
518
}
415
519
416
520
private void populatePropertySchemas (final JSONObject propertyDefs ,
@@ -428,6 +532,13 @@ private boolean schemaHasAnyOf(final Collection<String> propNames) {
428
532
return propNames .stream ().filter (schemaJson ::has ).findAny ().isPresent ();
429
533
}
430
534
535
+ private SchemaLoaderBuilder selfBuilder () {
536
+ return builder ().id (id ).schemaJson (schemaJson )
537
+ .rootSchemaJson (rootSchemaJson )
538
+ .pointerSchemas (pointerSchemas )
539
+ .httpClient (httpClient );
540
+ }
541
+
431
542
private Schema .Builder <?> sniffSchemaByProps () {
432
543
if (schemaHasAnyOf (ARRAY_SCHEMA_PROPS )) {
433
544
return buildArraySchema ().requiresArray (false );
@@ -497,8 +608,8 @@ JSONObject withoutRef(final JSONObject original) {
497
608
}
498
609
JSONObject rval = new JSONObject ();
499
610
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 )));
502
613
return rval ;
503
614
}
504
615
}
0 commit comments