@@ -45,6 +45,35 @@ def test_earlier_sibling_errors_are_better_matches(self):
4545 best = self .best_match_of (instance = [12 , 12 , 12 ], schema = schema )
4646 self .assertEqual (list (best .path ), [0 ])
4747
48+ def test_earlier_sibling_errors_within_a_subschema_are_better_matches (
49+ self ,
50+ ):
51+ """
52+ The same holds within an anyOf subschema, even though its errors are
53+ always provided in the order they were produced in.
54+ """
55+
56+ schema = {"anyOf" : [{"items" : {"const" : 37 }}]}
57+ error = next (_LATEST_VERSION (schema ).iter_errors ([12 , 12 , 12 ]))
58+ error .context .reverse ()
59+ best = exceptions .best_match ([error ])
60+ self .assertEqual (list (best .path ), [0 ])
61+
62+ def test_hand_constructed_errors_with_context (self ):
63+ """
64+ Errors constructed by hand need not have any schema path at all.
65+ """
66+
67+ error = exceptions .ValidationError (
68+ "parent" ,
69+ context = [
70+ exceptions .ValidationError ("child" , path = ["foo" ]),
71+ exceptions .ValidationError ("grandchild" , path = ["foo" , "bar" ]),
72+ ],
73+ )
74+ best = exceptions .best_match ([error ])
75+ self .assertEqual (best .message , "child" )
76+
4877 def test_oneOf_and_anyOf_are_weak_matches (self ):
4978 """
5079 A property you *must* match is probably better than one you have to
@@ -160,10 +189,13 @@ def test_no_anyOf_traversal_for_two_properties_sibling_errors(self):
160189 best = self .best_match_of (instance = {"foo" : 1 , "bar" : 1 }, schema = schema )
161190 self .assertEqual (best .validator , "anyOf" )
162191
163- def test_no_anyOf_traversal_for_two_items_sibling_errors (self ):
192+ def test_anyOf_traversal_prefers_subschemas_with_fewer_errors (self ):
164193 """
165- We don't traverse into an anyOf if all of its context errors
166- seem to be equally "wrong" against the instance.
194+ When subschemas fail at the same depth, we traverse into the one
195+ which produced fewer errors, since it was closer to being valid.
196+
197+ Here the first subschema rejects every item, whereas the second
198+ rejects only the non-integer one.
167199 """
168200
169201 schema = {
@@ -188,9 +220,40 @@ def test_no_anyOf_traversal_for_two_items_sibling_errors(self):
188220 },
189221 }
190222 best = self .best_match_of (instance = ["not an int" , 0 ], schema = schema )
191- self .assertEqual (best .validator , "anyOf" )
223+ self .assertEqual (best .validator , "type" )
224+ self .assertEqual (best .instance , "not an int" )
192225 best = self .best_match_of (instance = [0 , "not an int" ], schema = schema )
193- self .assertEqual (best .validator , "anyOf" )
226+ self .assertEqual (best .validator , "type" )
227+ self .assertEqual (best .instance , "not an int" )
228+
229+ def test_anyOf_traversal_for_partially_matching_subschemas (self ):
230+ """
231+ When one subschema matches more of the instance than another,
232+ we traverse into it, and report its remaining error.
233+
234+ See #1002.
235+ """
236+
237+ schema = {
238+ "anyOf" : [
239+ {
240+ "properties" : {
241+ "version" : {"const" : 1 },
242+ "description" : {"type" : "string" },
243+ },
244+ },
245+ {
246+ "properties" : {
247+ "version" : {"const" : 2 },
248+ "description" : {"type" : "string" },
249+ },
250+ },
251+ ],
252+ }
253+ instance = {"version" : 1 , "description" : 0 }
254+ best = self .best_match_of (instance = instance , schema = schema )
255+ self .assertEqual (best .validator , "type" )
256+ self .assertEqual (list (best .path ), ["description" ])
194257
195258 def test_anyOf_traversal_for_single_equally_relevant_error (self ):
196259 """
@@ -337,10 +400,13 @@ def test_no_oneOf_traversal_for_two_properties_sibling_errors(self):
337400 best = self .best_match_of (instance = {"foo" : 1 , "bar" : 1 }, schema = schema )
338401 self .assertEqual (best .validator , "oneOf" )
339402
340- def test_no_oneOf_traversal_for_two_items_sibling_errors (self ):
403+ def test_oneOf_traversal_prefers_subschemas_with_fewer_errors (self ):
341404 """
342- We don't traverse into an anyOf if all of its context errors
343- seem to be equally "wrong" against the instance.
405+ When subschemas fail at the same depth, we traverse into the one
406+ which produced fewer errors, since it was closer to being valid.
407+
408+ Here the first subschema rejects every item, whereas the second
409+ rejects only the non-integer one.
344410 """
345411
346412 schema = {
@@ -365,9 +431,40 @@ def test_no_oneOf_traversal_for_two_items_sibling_errors(self):
365431 },
366432 }
367433 best = self .best_match_of (instance = ["not an int" , 0 ], schema = schema )
368- self .assertEqual (best .validator , "oneOf" )
434+ self .assertEqual (best .validator , "type" )
435+ self .assertEqual (best .instance , "not an int" )
369436 best = self .best_match_of (instance = [0 , "not an int" ], schema = schema )
370- self .assertEqual (best .validator , "oneOf" )
437+ self .assertEqual (best .validator , "type" )
438+ self .assertEqual (best .instance , "not an int" )
439+
440+ def test_oneOf_traversal_for_partially_matching_subschemas (self ):
441+ """
442+ When one subschema matches more of the instance than another,
443+ we traverse into it, and report its remaining error.
444+
445+ See #1002.
446+ """
447+
448+ schema = {
449+ "oneOf" : [
450+ {
451+ "properties" : {
452+ "version" : {"const" : 1 },
453+ "description" : {"type" : "string" },
454+ },
455+ },
456+ {
457+ "properties" : {
458+ "version" : {"const" : 2 },
459+ "description" : {"type" : "string" },
460+ },
461+ },
462+ ],
463+ }
464+ instance = {"version" : 1 , "description" : 0 }
465+ best = self .best_match_of (instance = instance , schema = schema )
466+ self .assertEqual (best .validator , "type" )
467+ self .assertEqual (list (best .path ), ["description" ])
371468
372469 def test_oneOf_traversal_for_single_equally_relevant_error (self ):
373470 """
0 commit comments