diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py index 3177caea..b880e8f6 100644 --- a/jsonschema/_utils.py +++ b/jsonschema/_utils.py @@ -82,12 +82,13 @@ def find_additional_properties(instance, schema): Assumes ``instance`` is dict-like already. """ properties = schema.get("properties", {}) - patterns = "|".join(schema.get("patternProperties", {})) + patterns = schema.get("patternProperties", {}) for property in instance: - if property not in properties: - if patterns and re.search(patterns, property): - continue - yield property + if property in properties: + continue + if any(re.search(pattern, property) for pattern in patterns): + continue + yield property def extras_msg(extras): diff --git a/jsonschema/tests/test_utils.py b/jsonschema/tests/test_utils.py index f716a36a..387c4dbd 100644 --- a/jsonschema/tests/test_utils.py +++ b/jsonschema/tests/test_utils.py @@ -2,7 +2,7 @@ from math import nan from unittest import TestCase -from jsonschema._utils import equal, uniq +from jsonschema._utils import equal, find_additional_properties, uniq class Unhashable: @@ -223,3 +223,21 @@ def test_unhashable_inside_mapping_falls_back(self): self.assertTrue( uniq([{"k": Unhashable(1)}, {"k": Unhashable(2)}]), ) + + +class TestFindAdditionalProperties(TestCase): + def test_empty_pattern_matches_all_properties(self): + # An empty-string pattern is a valid regex that matches every property, + # so patternProperties covers everything and nothing is additional. + schema = {"patternProperties": {"": {"type": "integer"}}} + instance = {"foo": 1, "bar": 2} + self.assertEqual( + list(find_additional_properties(instance, schema)), [], + ) + + def test_no_pattern_properties_leaves_all_additional(self): + schema = {"properties": {"foo": {}}} + instance = {"foo": 1, "bar": 2} + self.assertEqual( + list(find_additional_properties(instance, schema)), ["bar"], + )