16
16
17
17
package com .networknt .schema ;
18
18
19
- import com .fasterxml .jackson .databind .JsonNode ;
20
-
21
- import org .slf4j .Logger ;
22
- import org .slf4j .LoggerFactory ;
23
-
24
19
import java .util .ArrayList ;
25
20
import java .util .Collections ;
21
+ import java .util .HashMap ;
26
22
import java .util .Iterator ;
27
23
import java .util .LinkedHashSet ;
28
24
import java .util .List ;
25
+ import java .util .Map ;
29
26
import java .util .Set ;
30
27
28
+ import org .slf4j .Logger ;
29
+ import org .slf4j .LoggerFactory ;
30
+
31
+ import com .fasterxml .jackson .databind .JsonNode ;
32
+
31
33
public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
32
34
private static final Logger logger = LoggerFactory .getLogger (RequiredValidator .class );
33
35
34
- private List <JsonSchema > schemas = new ArrayList <JsonSchema >();
36
+ private List <ShortcutValidator > schemas = new ArrayList <ShortcutValidator >();
37
+
38
+ private static class ShortcutValidator {
39
+ private final JsonSchema schema ;
40
+ private final Map <String , String > constants ;
41
+
42
+ ShortcutValidator (JsonNode schemaNode , JsonSchema parentSchema ,
43
+ ValidationContext validationContext , JsonSchema schema ) {
44
+ JsonNode refNode = schemaNode .get (ValidatorTypeCode .REF .getValue ());
45
+ JsonSchema resolvedRefSchema = refNode != null && refNode .isTextual () ? RefValidator .getRefSchema (parentSchema , validationContext ,refNode .textValue ()) : null ;
46
+ this .constants = extractConstants (schemaNode , resolvedRefSchema );
47
+ this .schema = schema ;
48
+ }
49
+
50
+ private Map <String , String > extractConstants (JsonNode schemaNode , JsonSchema resolvedRefSchema ) {
51
+ Map <String , String > refMap = resolvedRefSchema != null ? extractConstants (resolvedRefSchema .getSchemaNode ()) : Collections .<String ,String >emptyMap ();
52
+ Map <String , String > schemaMap = extractConstants (schemaNode );
53
+ if (refMap .isEmpty () ) {
54
+ return schemaMap ;
55
+ }
56
+ if (schemaMap .isEmpty ()) {
57
+ return refMap ;
58
+ }
59
+ Map <String , String > joined = new HashMap <String , String >();
60
+ joined .putAll (schemaMap );
61
+ joined .putAll (refMap );
62
+ return joined ;
63
+ }
64
+
65
+ private Map <String , String > extractConstants (JsonNode schemaNode ) {
66
+ Map <String , String > result = new HashMap <String , String >();
67
+ if (!schemaNode .isObject ()) {
68
+ return result ;
69
+ }
70
+
71
+ JsonNode propertiesNode = schemaNode .get ("properties" );
72
+ if (propertiesNode == null || !propertiesNode .isObject ()) {
73
+ return result ;
74
+ }
75
+ Iterator <String > fit = propertiesNode .fieldNames ();
76
+ while (fit .hasNext ()) {
77
+ String fieldName = fit .next ();
78
+ JsonNode jsonNode = propertiesNode .get (fieldName );
79
+ String constantFieldValue = getConstantFieldValue (jsonNode );
80
+ if (constantFieldValue != null && !constantFieldValue .isEmpty ()) {
81
+ result .put (fieldName , constantFieldValue );
82
+ }
83
+ }
84
+ return result ;
85
+ }
86
+ private String getConstantFieldValue (JsonNode jsonNode ) {
87
+ if (jsonNode == null || !jsonNode .isObject () || !jsonNode .has ("enum" )) {
88
+ return null ;
89
+ }
90
+ JsonNode enumNode = jsonNode .get ("enum" );
91
+ if (enumNode == null || !enumNode .isArray () ) {
92
+ return null ;
93
+ }
94
+ if (enumNode .size () != 1 ) {
95
+ return null ;
96
+ }
97
+ JsonNode valueNode = enumNode .get (0 );
98
+ if (valueNode == null || !valueNode .isTextual ()) {
99
+ return null ;
100
+ }
101
+ return valueNode .textValue ();
102
+ }
103
+
104
+ public boolean allConstantsMatch (JsonNode node ) {
105
+ for (Map .Entry <String , String > e : constants .entrySet ()) {
106
+ JsonNode valueNode = node .get (e .getKey ());
107
+ if (valueNode != null && valueNode .isTextual ()) {
108
+ boolean match = e .getValue ().equals (valueNode .textValue ());
109
+ if (!match ) {
110
+ return false ;
111
+ }
112
+ }
113
+ }
114
+ return true ;
115
+ }
116
+
117
+ }
35
118
36
119
public OneOfValidator (String schemaPath , JsonNode schemaNode , JsonSchema parentSchema , ValidationContext validationContext ) {
37
120
super (schemaPath , schemaNode , parentSchema , ValidatorTypeCode .ONE_OF , validationContext );
38
121
int size = schemaNode .size ();
39
122
for (int i = 0 ; i < size ; i ++) {
40
- schemas .add (new JsonSchema (validationContext , getValidatorType ().getValue (), schemaNode .get (i ), parentSchema ));
123
+ JsonNode childNode = schemaNode .get (i );
124
+ JsonSchema childSchema = new JsonSchema (validationContext , getValidatorType ().getValue (), childNode , parentSchema );
125
+ schemas .add (new ShortcutValidator (childNode , parentSchema , validationContext , childSchema ));
41
126
}
42
127
43
128
parseErrorCode (getValidatorType ().getErrorCodeKey ());
@@ -49,7 +134,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
49
134
int numberOfValidSchema = 0 ;
50
135
Set <ValidationMessage > errors = new LinkedHashSet <ValidationMessage >();
51
136
52
- for (JsonSchema schema : schemas ) {
137
+ for (ShortcutValidator validator : schemas ) {
138
+ if (!validator .allConstantsMatch (node )) {
139
+ // take a shortcut: if there is any constant that does not match,
140
+ // we can bail out
141
+ continue ;
142
+ }
143
+ JsonSchema schema = validator .schema ;
53
144
Set <ValidationMessage > schemaErrors = schema .validate (node , rootNode , at );
54
145
if (schemaErrors .isEmpty ()) {
55
146
numberOfValidSchema ++;
@@ -70,6 +161,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
70
161
if (ValidatorTypeCode .ADDITIONAL_PROPERTIES .getValue ().equals (msg .getType ())) {
71
162
it .remove ();
72
163
}
164
+ if (errors .isEmpty ()) {
165
+ // ensure there is always an error reported if number of valid schemas is 0
166
+ errors .add (buildValidationMessage (at , "" ));
167
+ }
73
168
}
74
169
}
75
170
if (numberOfValidSchema > 1 ) {
0 commit comments