1
1
package org .everit .json .schema .loader ;
2
2
3
+ import static java .lang .String .format ;
4
+ import static java .util .Arrays .asList ;
3
5
import static java .util .Collections .emptyList ;
4
6
import static java .util .Collections .singleton ;
5
7
import static java .util .Collections .singletonList ;
6
8
import static java .util .Objects .requireNonNull ;
9
+ import static org .everit .json .schema .loader .SpecificationVersion .DRAFT_4 ;
10
+ import static org .everit .json .schema .loader .SpecificationVersion .DRAFT_7 ;
7
11
12
+ import java .util .ArrayList ;
8
13
import java .util .Collection ;
9
14
import java .util .HashSet ;
10
15
import java .util .List ;
11
16
import java .util .Optional ;
12
17
import java .util .Set ;
13
18
19
+ import org .everit .json .schema .ArraySchema ;
20
+ import org .everit .json .schema .BooleanSchema ;
21
+ import org .everit .json .schema .CombinedSchema ;
22
+ import org .everit .json .schema .ConditionalSchema ;
23
+ import org .everit .json .schema .ConstSchema ;
14
24
import org .everit .json .schema .EnumSchema ;
25
+ import org .everit .json .schema .NotSchema ;
26
+ import org .everit .json .schema .NullSchema ;
27
+ import org .everit .json .schema .NumberSchema ;
28
+ import org .everit .json .schema .ObjectSchema ;
15
29
import org .everit .json .schema .Schema ;
30
+ import org .everit .json .schema .SchemaException ;
16
31
17
32
class ExtractionResult {
18
33
@@ -39,43 +54,91 @@ interface SchemaExtractor {
39
54
40
55
abstract class AbstractSchemaExtractor implements SchemaExtractor {
41
56
57
+ static final List <String > NUMBER_SCHEMA_PROPS = asList ("minimum" , "maximum" ,
58
+ "exclusiveMinimum" , "exclusiveMaximum" , "multipleOf" );
59
+
60
+ static final List <String > STRING_SCHEMA_PROPS = asList ("minLength" , "maxLength" ,
61
+ "pattern" , "format" );
62
+
42
63
protected JsonObject schemaJson ;
43
64
44
65
private Set <String > consumedKeys ;
45
66
67
+ final SchemaLoader defaultLoader ;
68
+
69
+ private ExclusiveLimitHandler exclusiveLimitHandler ;
70
+
71
+ AbstractSchemaExtractor (SchemaLoader defaultLoader ) {
72
+ this .defaultLoader = requireNonNull (defaultLoader , "defaultLoader cannot be null" );
73
+ }
74
+
46
75
@ Override
47
76
public final ExtractionResult extract (JsonObject schemaJson ) {
48
77
this .schemaJson = requireNonNull (schemaJson , "schemaJson cannot be null" );
78
+ this .exclusiveLimitHandler = ExclusiveLimitHandler .ofSpecVersion (config ().specVersion );
49
79
consumedKeys = new HashSet <>(schemaJson .keySet ().size ());
50
80
return new ExtractionResult (consumedKeys , extract ());
51
81
}
52
82
53
- private void keyConsumed (String key ) {
83
+ void keyConsumed (String key ) {
54
84
if (schemaJson .keySet ().contains (key )) {
55
85
consumedKeys .add (key );
56
86
}
57
87
}
58
88
59
- protected JsonValue require (String key ) {
89
+ JsonValue require (String key ) {
60
90
keyConsumed (key );
61
91
return schemaJson .require (key );
62
92
}
63
93
64
- protected Optional <JsonValue > maybe (String key ) {
94
+ Optional <JsonValue > maybe (String key ) {
65
95
keyConsumed (key );
66
96
return schemaJson .maybe (key );
67
97
}
68
98
69
- protected boolean containsKey (String key ) {
99
+ boolean containsKey (String key ) {
70
100
return schemaJson .containsKey (key );
71
101
}
72
102
73
- protected abstract List <Schema .Builder <?>> extract ();
103
+ boolean schemaHasAnyOf (Collection <String > propNames ) {
104
+ return propNames .stream ().anyMatch (schemaJson ::containsKey );
105
+ }
106
+
107
+ LoaderConfig config () {
108
+ return schemaJson .ls .config ;
109
+ }
110
+
111
+ ObjectSchema .Builder buildObjectSchema () {
112
+ config ().specVersion .objectKeywords ().forEach (this ::keyConsumed );
113
+ return new ObjectSchemaLoader (schemaJson .ls , config (), defaultLoader ).load ();
114
+ }
115
+
116
+ ArraySchema .Builder buildArraySchema () {
117
+ config ().specVersion .arrayKeywords ().forEach (this ::keyConsumed );
118
+ return new ArraySchemaLoader (schemaJson .ls , config (), defaultLoader ).load ();
119
+ }
120
+
121
+ NumberSchema .Builder buildNumberSchema () {
122
+ PropertySnifferSchemaExtractor .NUMBER_SCHEMA_PROPS .forEach (this ::keyConsumed );
123
+ NumberSchema .Builder builder = NumberSchema .builder ();
124
+ maybe ("minimum" ).map (JsonValue ::requireNumber ).ifPresent (builder ::minimum );
125
+ maybe ("maximum" ).map (JsonValue ::requireNumber ).ifPresent (builder ::maximum );
126
+ maybe ("multipleOf" ).map (JsonValue ::requireNumber ).ifPresent (builder ::multipleOf );
127
+ maybe ("exclusiveMinimum" ).ifPresent (exclMin -> exclusiveLimitHandler .handleExclusiveMinimum (exclMin , builder ));
128
+ maybe ("exclusiveMaximum" ).ifPresent (exclMax -> exclusiveLimitHandler .handleExclusiveMaximum (exclMax , builder ));
129
+ return builder ;
130
+ }
131
+
132
+ abstract List <Schema .Builder <?>> extract ();
74
133
}
75
134
76
135
class EnumSchemaExtractor extends AbstractSchemaExtractor {
77
136
78
- @ Override protected List <Schema .Builder <?>> extract () {
137
+ EnumSchemaExtractor (SchemaLoader defaultLoader ) {
138
+ super (defaultLoader );
139
+ }
140
+
141
+ @ Override List <Schema .Builder <?>> extract () {
79
142
if (!containsKey ("enum" )) {
80
143
return emptyList ();
81
144
}
@@ -90,11 +153,132 @@ class EnumSchemaExtractor extends AbstractSchemaExtractor {
90
153
91
154
class ReferenceSchemaExtractor extends AbstractSchemaExtractor {
92
155
93
- @ Override protected List <Schema .Builder <?>> extract () {
156
+ ReferenceSchemaExtractor (SchemaLoader defaultLoader ) {
157
+ super (defaultLoader );
158
+ }
159
+
160
+ @ Override List <Schema .Builder <?>> extract () {
94
161
if (containsKey ("$ref" )) {
95
162
String ref = require ("$ref" ).requireString ();
96
163
return singletonList (new ReferenceLookup (schemaJson .ls ).lookup (ref , schemaJson ));
97
164
}
98
165
return emptyList ();
99
166
}
100
167
}
168
+
169
+ class PropertySnifferSchemaExtractor extends AbstractSchemaExtractor {
170
+
171
+ static final List <String > CONDITIONAL_SCHEMA_KEYWORDS = asList ("if" , "then" , "else" );
172
+
173
+ PropertySnifferSchemaExtractor (SchemaLoader defaultLoader ) {
174
+ super (defaultLoader );
175
+ }
176
+
177
+ @ Override List <Schema .Builder <?>> extract () {
178
+ List <Schema .Builder <?>> builders = new ArrayList <>(1 );
179
+ if (schemaHasAnyOf (config ().specVersion .arrayKeywords ())) {
180
+ builders .add (new ArraySchemaLoader (schemaJson .ls , config (), defaultLoader ).load ().requiresArray (false ));
181
+ }
182
+ if (schemaHasAnyOf (config ().specVersion .objectKeywords ())) {
183
+ builders .add (new ObjectSchemaLoader (schemaJson .ls , config (), defaultLoader ).load ().requiresObject (false ));
184
+ }
185
+ if (schemaHasAnyOf (NUMBER_SCHEMA_PROPS )) {
186
+ builders .add (buildNumberSchema ().requiresNumber (false ));
187
+ }
188
+ if (schemaHasAnyOf (STRING_SCHEMA_PROPS )) {
189
+ builders .add (new StringSchemaLoader (schemaJson .ls , config ().formatValidators ).load ().requiresString (false ));
190
+ }
191
+ if (config ().specVersion .isAtLeast (DRAFT_7 ) && schemaHasAnyOf (CONDITIONAL_SCHEMA_KEYWORDS )) {
192
+ builders .add (buildConditionalSchema ());
193
+ }
194
+ return builders ;
195
+ }
196
+
197
+ private ConditionalSchema .Builder buildConditionalSchema () {
198
+ ConditionalSchema .Builder builder = ConditionalSchema .builder ();
199
+ maybe ("if" ).map (defaultLoader ::loadChild ).map (Schema .Builder ::build ).ifPresent (builder ::ifSchema );
200
+ maybe ("then" ).map (defaultLoader ::loadChild ).map (Schema .Builder ::build ).ifPresent (builder ::thenSchema );
201
+ maybe ("else" ).map (defaultLoader ::loadChild ).map (Schema .Builder ::build ).ifPresent (builder ::elseSchema );
202
+ return builder ;
203
+ }
204
+
205
+ }
206
+
207
+ class TypeBasedSchemaExtractor extends AbstractSchemaExtractor {
208
+
209
+ TypeBasedSchemaExtractor (SchemaLoader defaultLoader ) {
210
+ super (defaultLoader );
211
+ }
212
+
213
+ @ Override List <Schema .Builder <?>> extract () {
214
+ if (containsKey ("type" )) {
215
+ return singletonList (require ("type" ).canBeMappedTo (JsonArray .class , arr -> (Schema .Builder ) buildAnyOfSchemaForMultipleTypes ())
216
+ .orMappedTo (String .class , this ::loadForExplicitType )
217
+ .requireAny ());
218
+ } else {
219
+ return emptyList ();
220
+ }
221
+ }
222
+
223
+ private CombinedSchema .Builder buildAnyOfSchemaForMultipleTypes () {
224
+ JsonArray subtypeJsons = require ("type" ).requireArray ();
225
+ Collection <Schema > subschemas = new ArrayList <>(subtypeJsons .length ());
226
+ subtypeJsons .forEach ((j , raw ) -> {
227
+ subschemas .add (loadForExplicitType (raw .requireString ()).build ());
228
+ });
229
+ return CombinedSchema .anyOf (subschemas );
230
+ }
231
+
232
+ private Schema .Builder <?> loadForExplicitType (String typeString ) {
233
+ switch (typeString ) {
234
+ case "string" :
235
+ PropertySnifferSchemaExtractor .STRING_SCHEMA_PROPS .forEach (this ::keyConsumed );
236
+ return new StringSchemaLoader (schemaJson .ls , config ().formatValidators ).load ();
237
+ case "integer" :
238
+ return buildNumberSchema ().requiresInteger (true );
239
+ case "number" :
240
+ return buildNumberSchema ();
241
+ case "boolean" :
242
+ return BooleanSchema .builder ();
243
+ case "null" :
244
+ return NullSchema .builder ();
245
+ case "array" :
246
+ return buildArraySchema ();
247
+ case "object" :
248
+ return buildObjectSchema ();
249
+ default :
250
+ throw new SchemaException (schemaJson .ls .locationOfCurrentObj (), format ("unknown type: [%s]" , typeString ));
251
+ }
252
+ }
253
+
254
+ }
255
+
256
+ class NotSchemaExtractor extends AbstractSchemaExtractor {
257
+
258
+ NotSchemaExtractor (SchemaLoader defaultLoader ) {
259
+ super (defaultLoader );
260
+ }
261
+
262
+ @ Override List <Schema .Builder <?>> extract () {
263
+ if (containsKey ("not" )) {
264
+ Schema mustNotMatch = defaultLoader .loadChild (require ("not" )).build ();
265
+ return singletonList (NotSchema .builder ().mustNotMatch (mustNotMatch ));
266
+ }
267
+ return emptyList ();
268
+ }
269
+ }
270
+
271
+ class ConstSchemaExtractor extends AbstractSchemaExtractor {
272
+
273
+ ConstSchemaExtractor (SchemaLoader defaultLoader ) {
274
+ super (defaultLoader );
275
+ }
276
+
277
+ @ Override List <Schema .Builder <?>> extract () {
278
+ if (config ().specVersion != DRAFT_4 && containsKey ("const" )) {
279
+ return singletonList (ConstSchema .builder ().permittedValue (require ("const" ).unwrap ()));
280
+ } else {
281
+ return emptyList ();
282
+ }
283
+ }
284
+ }
0 commit comments