@@ -85,10 +85,33 @@ def allOf(
8585 validator = validator .evolve (
8686 function_filter = validator .function_filter .evolve (
8787 add_cfn_lint_keyword = False ,
88- )
88+ ),
89+ context = validator .context .evolve (
90+ unresolvable_function_mode = True ,
91+ ),
8992 )
93+ has_unknown = False
94+ known_errors = []
95+
9096 for index , subschema in enumerate (allOf ):
91- yield from validator .descend (instance , subschema , schema_path = index )
97+ errs = list (validator .descend (instance , subschema , schema_path = index ))
98+
99+ if any (getattr (err , "unknown" , False ) for err in errs ):
100+ has_unknown = True
101+ else :
102+ known_errors .extend (errs )
103+
104+ # If we have unknown branches, we can't determine if allOf is satisfied
105+ if has_unknown :
106+ yield ValidationError (
107+ f"Cannot determine allOf for { instance !r} " ,
108+ unknown = True ,
109+ )
110+ return
111+
112+ # Yield all known errors
113+ for err in known_errors :
114+ yield err
92115
93116
94117def anyOf (
@@ -97,10 +120,16 @@ def anyOf(
97120 validator = validator .evolve (
98121 function_filter = validator .function_filter .evolve (
99122 add_cfn_lint_keyword = False ,
100- )
123+ ),
124+ context = validator .context .evolve (
125+ unresolvable_function_mode = True ,
126+ ),
101127 )
102128 all_errors = []
103129 other_errors = []
130+ has_valid = False
131+ has_unknown = False
132+
104133 for index , subschema in enumerate (anyOf ):
105134 errs = []
106135 # warning and informational shouldn't count towards if anyOf is
@@ -110,14 +139,32 @@ def anyOf(
110139 other_errors .append (err )
111140 continue
112141 errs .append (err )
113- if not errs :
142+
143+ if any (getattr (err , "unknown" , False ) for err in errs ):
144+ has_unknown = True
145+ elif not errs :
146+ has_valid = True
114147 break
115- all_errors .extend (errs )
116- else :
148+ else :
149+ all_errors .extend (errs )
150+
151+ # If we found a valid branch, we're done
152+ if has_valid :
153+ return
154+
155+ # If we have unknown branches, we can't determine
156+ if has_unknown :
117157 yield ValidationError (
118- f"{ instance !r} is not valid under any of the given schemas " ,
119- context = all_errors + other_errors ,
158+ f"Cannot determine anyOf for { instance !r} " ,
159+ unknown = True ,
120160 )
161+ return
162+
163+ # All known branches failed
164+ yield ValidationError (
165+ f"{ instance !r} is not valid under any of the given schemas" ,
166+ context = all_errors + other_errors ,
167+ )
121168
122169
123170def const (
@@ -134,22 +181,39 @@ def contains(
134181 return
135182
136183 matches = 0
184+ unknown_count = 0
137185 min_contains = schema .get ("minContains" , 1 )
138186 max_contains = schema .get ("maxContains" , len (instance ))
139187
140188 contains_validator = validator .evolve (schema = contains )
141189
142190 for each in instance :
143- if contains_validator .is_valid (each ):
144- matches += 1
145- if matches > max_contains :
146- yield ValidationError (
147- "Too many items match the given schema "
148- f"(expected at most { max_contains } )" ,
149- validator = "maxContains" ,
150- validator_value = max_contains ,
151- )
152- return
191+ errs = list (contains_validator .iter_errors (each ))
192+ # Filter unknown errors
193+ non_unknown_errs = [err for err in errs if not err .unknown ]
194+
195+ if not non_unknown_errs :
196+ # If no non-unknown errors, it's either valid or unknown
197+ if any (err .unknown for err in errs ):
198+ unknown_count += 1
199+ else :
200+ matches += 1
201+ if matches > max_contains :
202+ yield ValidationError (
203+ "Too many items match the given schema "
204+ f"(expected at most { max_contains } )" ,
205+ validator = "maxContains" ,
206+ validator_value = max_contains ,
207+ )
208+ return
209+
210+ # If we have unknown items, we can't determine if contains is satisfied
211+ if unknown_count > 0 and matches < min_contains :
212+ yield ValidationError (
213+ "Cannot determine if contains constraint is satisfied" ,
214+ unknown = True ,
215+ )
216+ return
153217
154218 if matches < min_contains :
155219 if not matches :
@@ -314,10 +378,22 @@ def if_(
314378 if_validator = validator .evolve (
315379 context = validator .context .evolve (
316380 allow_exceptions = False ,
381+ unresolvable_function_mode = True ,
317382 )
318383 )
319384
320- if if_validator .evolve (schema = if_schema ).is_valid (instance ):
385+ if_errors = list (if_validator .evolve (schema = if_schema ).iter_errors (instance ))
386+
387+ # If any error is unknown, we can't determine the condition
388+ if any (getattr (err , "unknown" , False ) for err in if_errors ):
389+ yield ValidationError (
390+ f"Cannot determine if condition for { instance !r} " ,
391+ unknown = True ,
392+ )
393+ return
394+
395+ # Original logic
396+ if not if_errors :
321397 if "then" in schema :
322398 then = schema ["then" ]
323399 yield from validator .descend (instance , then , schema_path = "then" )
@@ -477,9 +553,24 @@ def not_(
477553 validator = validator .evolve (
478554 function_filter = validator .function_filter .evolve (
479555 add_cfn_lint_keyword = False ,
480- )
556+ ),
557+ context = validator .context .evolve (
558+ unresolvable_function_mode = True ,
559+ ),
481560 )
482- if validator .evolve (schema = not_schema ).is_valid (instance ):
561+
562+ errs = list (validator .evolve (schema = not_schema ).iter_errors (instance ))
563+
564+ # If there are unknown errors, we can't determine if not is satisfied
565+ if any (getattr (err , "unknown" , False ) for err in errs ):
566+ yield ValidationError (
567+ f"Cannot determine not for { instance !r} " ,
568+ unknown = True ,
569+ )
570+ return
571+
572+ # If no errors, the schema is valid, so 'not' fails
573+ if not errs :
483574 message = f"{ instance !r} should not be valid under { not_schema !r} "
484575 yield ValidationError (message )
485576
@@ -490,30 +581,48 @@ def oneOf(
490581 validator = validator .evolve (
491582 function_filter = validator .function_filter .evolve (
492583 add_cfn_lint_keyword = False ,
493- )
584+ ),
585+ context = validator .context .evolve (
586+ unresolvable_function_mode = True ,
587+ ),
494588 )
495589 subschemas = enumerate (oneOf )
496590 all_errors = []
591+ valid_count = 0
592+ has_unknown = False
593+ valid_schemas = []
594+
497595 for index , subschema in subschemas :
498596 errs = list (validator .descend (instance , subschema , schema_path = index ))
499- if not errs :
500- first_valid = subschema
501- break
502- all_errors .extend (errs )
503- else :
597+
598+ if any (getattr (err , "unknown" , False ) for err in errs ):
599+ has_unknown = True
600+ elif not errs :
601+ valid_count += 1
602+ valid_schemas .append (subschema )
603+ else :
604+ all_errors .extend (errs )
605+
606+ # If we can't determine, yield unknown error
607+ if has_unknown and valid_count < 2 :
608+ yield ValidationError (
609+ f"Cannot determine oneOf for { instance !r} " ,
610+ unknown = True ,
611+ )
612+ return
613+
614+ # Definitive results
615+ if valid_count == 1 :
616+ return
617+
618+ if valid_count == 0 :
504619 yield ValidationError (
505620 f"{ instance !r} is not valid under any of the given schemas" ,
506621 context = all_errors ,
507622 )
508-
509- more_valid = [
510- each
511- for _ , each in subschemas
512- if validator .evolve (schema = each ).is_valid (instance )
513- ]
514- if more_valid :
515- more_valid .append (first_valid )
516- reprs = ", " .join (repr (schema ) for schema in more_valid )
623+ else :
624+ # Multiple valid schemas
625+ reprs = ", " .join (repr (schema ) for schema in valid_schemas )
517626 yield ValidationError (f"{ instance !r} is valid under each of { reprs } " )
518627
519628
0 commit comments