1616
1717package com .networknt .schema ;
1818
19- import com .fasterxml .jackson .databind .JsonNode ;
20-
21- import org .slf4j .Logger ;
22- import org .slf4j .LoggerFactory ;
23-
2419import java .util .ArrayList ;
2520import java .util .Collections ;
21+ import java .util .HashMap ;
2622import java .util .Iterator ;
2723import java .util .LinkedHashSet ;
2824import java .util .List ;
25+ import java .util .Map ;
2926import java .util .Set ;
3027
28+ import org .slf4j .Logger ;
29+ import org .slf4j .LoggerFactory ;
30+
31+ import com .fasterxml .jackson .databind .JsonNode ;
32+
3133public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
3234 private static final Logger logger = LoggerFactory .getLogger (RequiredValidator .class );
3335
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+ }
35118
36119 public OneOfValidator (String schemaPath , JsonNode schemaNode , JsonSchema parentSchema , ValidationContext validationContext ) {
37120 super (schemaPath , schemaNode , parentSchema , ValidatorTypeCode .ONE_OF , validationContext );
38121 int size = schemaNode .size ();
39122 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 ));
41126 }
42127
43128 parseErrorCode (getValidatorType ().getErrorCodeKey ());
@@ -49,7 +134,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
49134 int numberOfValidSchema = 0 ;
50135 Set <ValidationMessage > errors = new LinkedHashSet <ValidationMessage >();
51136
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 ;
53144 Set <ValidationMessage > schemaErrors = schema .validate (node , rootNode , at );
54145 if (schemaErrors .isEmpty ()) {
55146 numberOfValidSchema ++;
@@ -70,6 +161,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
70161 if (ValidatorTypeCode .ADDITIONAL_PROPERTIES .getValue ().equals (msg .getType ())) {
71162 it .remove ();
72163 }
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+ }
73168 }
74169 }
75170 if (numberOfValidSchema > 1 ) {
0 commit comments