Skip to content

Commit 8cff13d

Browse files
committed
Fix items/prefixItems' message when used with heterogeneous arrays.
Closes: #1157
1 parent d9be1a4 commit 8cff13d

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

CHANGELOG.rst

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v4.19.2
2+
=======
3+
4+
* Fix the error message for additional items when used with heterogeneous arrays.
5+
16
v4.19.1
27
=======
38

jsonschema/_keywords.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def additionalProperties(validator, aP, instance, schema):
5454
yield ValidationError(error)
5555
else:
5656
error = "Additional properties are not allowed (%s %s unexpected)"
57-
yield ValidationError(error % extras_msg(extras))
57+
yield ValidationError(error % extras_msg(sorted(extras, key=str)))
5858

5959

6060
def items(validator, items, instance, schema):
@@ -63,9 +63,17 @@ def items(validator, items, instance, schema):
6363

6464
prefix = len(schema.get("prefixItems", []))
6565
total = len(instance)
66-
if items is False and total > prefix:
67-
message = f"Expected at most {prefix} items, but found {total}"
68-
yield ValidationError(message)
66+
extra = total - prefix
67+
if extra <= 0:
68+
return
69+
70+
if items is False:
71+
rest = instance[prefix:] if extra != 1 else instance[prefix]
72+
item = "items" if prefix != 1 else "item"
73+
yield ValidationError(
74+
f"Expected at most {prefix} {item} but found {extra} "
75+
f"extra: {rest!r}",
76+
)
6977
else:
7078
for index in range(prefix, total):
7179
yield from validator.descend(
@@ -427,7 +435,8 @@ def unevaluatedProperties(validator, unevaluatedProperties, instance, schema):
427435
if unevaluated_keys:
428436
if unevaluatedProperties is False:
429437
error = "Unevaluated properties are not allowed (%s %s unexpected)"
430-
yield ValidationError(error % extras_msg(unevaluated_keys))
438+
extras = sorted(unevaluated_keys, key=str)
439+
yield ValidationError(error % extras_msg(extras))
431440
else:
432441
error = (
433442
"Unevaluated properties are not valid under "

jsonschema/_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def extras_msg(extras):
9191
"""
9292

9393
verb = "was" if len(extras) == 1 else "were"
94-
return ", ".join(repr(extra) for extra in sorted(extras)), verb
94+
return ", ".join(repr(extra) for extra in extras), verb
9595

9696

9797
def ensure_list(thing):

jsonschema/tests/test_validators.py

+88-2
Original file line numberDiff line numberDiff line change
@@ -510,11 +510,24 @@ def test_maxItems(self):
510510
self.assertEqual(message, "[1, 2, 3] is too long")
511511

512512
def test_prefixItems_with_items(self):
513+
message = self.message_for(
514+
instance=[1, 2, "foo"],
515+
schema={"items": False, "prefixItems": [{}, {}]},
516+
)
517+
self.assertEqual(
518+
message,
519+
"Expected at most 2 items but found 1 extra: 'foo'"
520+
)
521+
522+
def test_prefixItems_with_multiple_extra_items(self):
513523
message = self.message_for(
514524
instance=[1, 2, "foo", 5],
515525
schema={"items": False, "prefixItems": [{}, {}]},
516526
)
517-
self.assertEqual(message, "Expected at most 2 items, but found 4")
527+
self.assertEqual(
528+
message,
529+
"Expected at most 2 items but found 2 extra: ['foo', 5]"
530+
)
518531

519532
def test_minLength(self):
520533
message = self.message_for(
@@ -655,7 +668,7 @@ def test_unevaluated_items(self):
655668
message = self.message_for(instance=["foo", "bar"], schema=schema)
656669
self.assertIn(
657670
message,
658-
"Unevaluated items are not allowed ('bar', 'foo' were unexpected)",
671+
"Unevaluated items are not allowed ('foo', 'bar' were unexpected)",
659672
)
660673

661674
def test_unevaluated_items_on_invalid_type(self):
@@ -702,6 +715,79 @@ def test_unevaluated_properties_on_invalid_type(self):
702715
message = self.message_for(instance="foo", schema=schema)
703716
self.assertEqual(message, "'foo' is not of type 'object'")
704717

718+
def test_single_item(self):
719+
schema = {"prefixItems": [{}], "items": False}
720+
message = self.message_for(
721+
instance=["foo", "bar", "baz"],
722+
schema=schema,
723+
)
724+
self.assertEqual(
725+
message,
726+
"Expected at most 1 item but found 2 extra: ['bar', 'baz']",
727+
)
728+
729+
def test_heterogeneous_additionalItems_with_Items(self):
730+
schema = {"items": [{}], "additionalItems": False}
731+
message = self.message_for(
732+
instance=["foo", "bar", 37],
733+
schema=schema,
734+
cls=validators.Draft7Validator,
735+
)
736+
self.assertEqual(
737+
message,
738+
"Additional items are not allowed ('bar', 37 were unexpected)"
739+
)
740+
741+
def test_heterogeneous_items_prefixItems(self):
742+
schema = {"prefixItems": [{}], "items": False}
743+
message = self.message_for(
744+
instance=["foo", "bar", 37],
745+
schema=schema,
746+
)
747+
self.assertEqual(
748+
message,
749+
"Expected at most 1 item but found 2 extra: ['bar', 37]",
750+
)
751+
752+
def test_heterogeneous_unevaluatedItems_prefixItems(self):
753+
schema = {"prefixItems": [{}], "unevaluatedItems": False}
754+
message = self.message_for(
755+
instance=["foo", "bar", 37],
756+
schema=schema,
757+
)
758+
self.assertEqual(
759+
message,
760+
"Unevaluated items are not allowed ('bar', 37 were unexpected)",
761+
)
762+
763+
def test_heterogeneous_properties_additionalProperties(self):
764+
"""
765+
Not valid deserialized JSON, but this should not blow up.
766+
"""
767+
schema = {"properties": {"foo": {}}, "additionalProperties": False}
768+
message = self.message_for(
769+
instance={"foo": {}, "a": "baz", 37: 12},
770+
schema=schema,
771+
)
772+
self.assertEqual(
773+
message,
774+
"Additional properties are not allowed (37, 'a' were unexpected)",
775+
)
776+
777+
def test_heterogeneous_properties_unevaluatedProperties(self):
778+
"""
779+
Not valid deserialized JSON, but this should not blow up.
780+
"""
781+
schema = {"properties": {"foo": {}}, "unevaluatedProperties": False}
782+
message = self.message_for(
783+
instance={"foo": {}, "a": "baz", 37: 12},
784+
schema=schema,
785+
)
786+
self.assertEqual(
787+
message,
788+
"Unevaluated properties are not allowed (37, 'a' were unexpected)",
789+
)
790+
705791

706792
class TestValidationErrorDetails(TestCase):
707793
# TODO: These really need unit tests for each individual keyword, rather

0 commit comments

Comments
 (0)