16
16
17
17
package com .networknt .schema ;
18
18
19
+ import java .util .ArrayList ;
20
+ import java .util .Collections ;
21
+ import java .util .HashMap ;
22
+ import java .util .HashSet ;
23
+ import java .util .Iterator ;
24
+ import java .util .LinkedHashSet ;
25
+ import java .util .List ;
26
+ import java .util .Map ;
27
+ import java .util .Set ;
28
+ import java .util .regex .Matcher ;
29
+ import java .util .regex .Pattern ;
30
+
19
31
import com .fasterxml .jackson .databind .JsonNode ;
20
32
import org .slf4j .Logger ;
21
33
import org .slf4j .LoggerFactory ;
22
34
23
- import java .util .*;
24
-
25
35
public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
26
36
private static final Logger logger = LoggerFactory .getLogger (RequiredValidator .class );
37
+ public static final Pattern TYPE_PATTERN = Pattern .compile ("^#(/[a-zA-Z0-9-_/]*)/([a-zA-Z0-9_]+)" );
27
38
28
- private List <ShortcutValidator > schemas = new ArrayList <ShortcutValidator >();
39
+ private final List <ShortcutValidator > schemas = new ArrayList <ShortcutValidator >();
29
40
30
41
private static class ShortcutValidator {
31
42
private final JsonSchema schema ;
32
43
private final Map <String , String > constants ;
33
-
34
- ShortcutValidator (JsonNode schemaNode , JsonSchema parentSchema ,
35
- ValidationContext validationContext , JsonSchema schema ) {
44
+ private final JsonNode discriminator ;
45
+
46
+ ShortcutValidator (JsonNode schemaNode ,
47
+ JsonSchema parentSchema ,
48
+ ValidationContext validationContext ,
49
+ JsonSchema schema ,
50
+ JsonNode discriminator ) {
51
+ this .discriminator = discriminator ;
36
52
JsonNode refNode = schemaNode .get (ValidatorTypeCode .REF .getValue ());
37
- JsonSchema resolvedRefSchema = refNode != null && refNode .isTextual () ? RefValidator .getRefSchema (parentSchema , validationContext , refNode .textValue ()).getSchema () : null ;
53
+ JsonSchema resolvedRefSchema = refNode != null && refNode .isTextual () ? RefValidator
54
+ .getRefSchema (parentSchema , validationContext , refNode .textValue ()).getSchema () : null ;
38
55
this .constants = extractConstants (schemaNode , resolvedRefSchema );
39
56
this .schema = schema ;
40
57
}
41
58
42
59
private Map <String , String > extractConstants (JsonNode schemaNode , JsonSchema resolvedRefSchema ) {
43
- Map <String , String > refMap = resolvedRefSchema != null ? extractConstants (resolvedRefSchema .getSchemaNode ()) : Collections .<String , String >emptyMap ();
60
+ Map <String , String > refMap = resolvedRefSchema != null
61
+ ? extractConstants (resolvedRefSchema .getSchemaNode ())
62
+ : Collections .<String , String >emptyMap ();
44
63
Map <String , String > schemaMap = extractConstants (schemaNode );
45
64
if (refMap .isEmpty ()) {
46
65
return schemaMap ;
@@ -115,11 +134,16 @@ private JsonSchema getSchema() {
115
134
116
135
public OneOfValidator (String schemaPath , JsonNode schemaNode , JsonSchema parentSchema , ValidationContext validationContext ) {
117
136
super (schemaPath , schemaNode , parentSchema , ValidatorTypeCode .ONE_OF , validationContext );
137
+ JsonNode discriminator = validationContext .getDiscriminatorForCurrentSchemaNode (schemaNode );
118
138
int size = schemaNode .size ();
119
139
for (int i = 0 ; i < size ; i ++) {
120
140
JsonNode childNode = schemaNode .get (i );
121
- JsonSchema childSchema = new JsonSchema (validationContext , getValidatorType ().getValue (), parentSchema .getCurrentUri (), childNode , parentSchema );
122
- schemas .add (new ShortcutValidator (childNode , parentSchema , validationContext , childSchema ));
141
+ JsonSchema childSchema = new JsonSchema (validationContext ,
142
+ getValidatorType ().getValue (),
143
+ parentSchema .getCurrentUri (),
144
+ childNode ,
145
+ parentSchema );
146
+ schemas .add (new ShortcutValidator (childNode , parentSchema , validationContext , childSchema , discriminator ));
123
147
}
124
148
125
149
parseErrorCode (getValidatorType ().getErrorCodeKey ());
@@ -133,14 +157,15 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
133
157
state .setComplexValidator (true );
134
158
135
159
int numberOfValidSchema = 0 ;
160
+ int skippedSchemas = 0 ;
136
161
Set <ValidationMessage > errors = new LinkedHashSet <ValidationMessage >();
137
162
Set <ValidationMessage > childErrors = new LinkedHashSet <ValidationMessage >();
138
163
// validate that only a single element has been received in the oneOf node
139
164
// validation should not continue, as it contradicts the oneOf requirement of only one
140
- // if(node.isObject() && node.size()>1) {
141
- // errors = Collections.singleton(buildValidationMessage(at, ""));
142
- // return Collections.unmodifiableSet(errors);
143
- // }
165
+ // if(node.isObject() && node.size()>1) {
166
+ // errors = Collections.singleton(buildValidationMessage(at, ""));
167
+ // return Collections.unmodifiableSet(errors);
168
+ // }
144
169
145
170
for (ShortcutValidator validator : schemas ) {
146
171
Set <ValidationMessage > schemaErrors = null ;
@@ -153,9 +178,38 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
153
178
// we can bail out of the validation
154
179
continue;
155
180
}*/
181
+ JsonNode discriminator = validator .discriminator ;
182
+ if (null != discriminator ) {
183
+ final String propertyName = discriminator .get ("propertyName" ).textValue ();
184
+ final String identifiedSchemaKey = node .get (propertyName ).textValue ();
185
+ final JsonNode discriminatorMapping = discriminator .get ("mapping" );
186
+ final String discriminatorDeterminedSchemaURL ;
187
+ final String currentValidatorSchemaLocation = validator .getSchema ().schemaNode .get ("$ref" ).textValue ();
188
+
189
+ if (null == discriminatorMapping || discriminatorMapping .get (identifiedSchemaKey ) == null ) {
190
+ if (currentValidatorSchemaLocation .startsWith ("#" )) {
191
+ final Matcher matcher = TYPE_PATTERN .matcher (currentValidatorSchemaLocation );
192
+ if (matcher .matches () && matcher .group (2 ).equals (identifiedSchemaKey )) {
193
+ discriminatorDeterminedSchemaURL = currentValidatorSchemaLocation ;
194
+ } else {
195
+ discriminatorDeterminedSchemaURL = null ;
196
+ }
197
+ } else {
198
+ throw new UnsupportedOperationException ("Remote schema support for discriminators not yet implemented" ); // TODO
199
+ }
200
+ } else {
201
+ discriminatorDeterminedSchemaURL = discriminatorMapping .get (identifiedSchemaKey ).textValue ();
202
+ }
203
+
204
+ if (!currentValidatorSchemaLocation .equals (discriminatorDeterminedSchemaURL )) {
205
+ skippedSchemas ++;
206
+ continue ; // next validator
207
+ }
208
+ }
156
209
157
210
// get the current validator
158
211
JsonSchema schema = validator .schema ;
212
+
159
213
if (!state .isWalkEnabled ()) {
160
214
schemaErrors = schema .validate (node , rootNode , at );
161
215
} else {
@@ -165,19 +219,28 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
165
219
// check if any validation errors have occurred
166
220
if (schemaErrors .isEmpty ()) {
167
221
// check whether there are no errors HOWEVER we have validated the exact validator
168
- if (!state .hasMatchedNode ())
222
+ if (!state .hasMatchedNode ()) {
169
223
continue ;
224
+ }
170
225
171
226
numberOfValidSchema ++;
172
227
}
173
228
childErrors .addAll (schemaErrors );
174
229
}
175
230
231
+ if (skippedSchemas == schemas .size ()) {
232
+ // this is when no discriminator value matched, so we have not a single candidate to check
233
+ final ValidationMessage message = getNoMatchingDiscriminatorErrorMsg (at );
234
+ if (failFast ) {
235
+ throw new JsonSchemaException (message );
236
+ }
237
+ errors .add (message );
238
+ }
176
239
177
240
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
178
- if (numberOfValidSchema > 1 ){
241
+ else if (numberOfValidSchema > 1 ) {
179
242
final ValidationMessage message = getMultiSchemasValidErrorMsg (at );
180
- if ( failFast ) {
243
+ if ( failFast ) {
181
244
throw new JsonSchemaException (message );
182
245
}
183
246
errors .add (message );
@@ -187,14 +250,15 @@ else if (numberOfValidSchema < 1) {
187
250
if (!childErrors .isEmpty ()) {
188
251
errors .addAll (childErrors );
189
252
}
190
- if ( failFast ) {
253
+ if ( failFast ) {
191
254
throw new JsonSchemaException (errors .toString ());
192
255
}
193
256
}
194
257
195
258
// Make sure to signal parent handlers we matched
196
- if (errors .isEmpty ())
259
+ if (errors .isEmpty ()) {
197
260
state .setMatchedNode (true );
261
+ }
198
262
199
263
// reset the ValidatorState object in the ThreadLocal
200
264
resetValidatorState ();
@@ -210,7 +274,7 @@ private void resetValidatorState() {
210
274
211
275
public List <JsonSchema > getChildSchemas () {
212
276
List <JsonSchema > childJsonSchemas = new ArrayList <JsonSchema >();
213
- for (ShortcutValidator shortcutValidator : schemas ) {
277
+ for (ShortcutValidator shortcutValidator : schemas ) {
214
278
childJsonSchemas .add (shortcutValidator .getSchema ());
215
279
}
216
280
return childJsonSchemas ;
@@ -223,24 +287,29 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
223
287
validationMessages .addAll (validate (node , rootNode , at ));
224
288
} else {
225
289
for (ShortcutValidator validator : schemas ) {
226
- validator .schema .walk (node , rootNode , at , shouldValidateSchema );
290
+ validator .schema .walk (node , rootNode , at , shouldValidateSchema );
227
291
}
228
292
}
229
293
return validationMessages ;
230
294
}
231
295
232
- private ValidationMessage getMultiSchemasValidErrorMsg (String at ){
233
- String msg = "" ;
234
- for (ShortcutValidator schema : schemas ){
296
+ private ValidationMessage getMultiSchemasValidErrorMsg (String at ) {
297
+ String msg = "" ;
298
+ for (ShortcutValidator schema : schemas ) {
235
299
String schemaValue = schema .getSchema ().getSchemaNode ().toString ();
236
300
msg = msg .concat (schemaValue );
237
301
}
238
302
239
- ValidationMessage message = ValidationMessage .of (getValidatorType ().getValue (),ValidatorTypeCode .ONE_OF ,
240
- at , String .format ("but more than one schemas {%s} are valid " ,msg ));
303
+ ValidationMessage message = ValidationMessage .of (getValidatorType ().getValue (), ValidatorTypeCode .ONE_OF ,
304
+ at , String .format ("but more than one schemas {%s} are valid " , msg ));
241
305
242
306
return message ;
243
307
}
244
308
245
-
309
+ private ValidationMessage getNoMatchingDiscriminatorErrorMsg (String at ) {
310
+
311
+ return ValidationMessage .of (getValidatorType ().getValue (), ValidatorTypeCode .ONE_OF ,
312
+ at , "but no candidate schema can be identified via the discriminator configuration" );
313
+ }
314
+
246
315
}
0 commit comments