Skip to content

Commit

Permalink
Enhance best match to prefer errors from matching types.
Browse files Browse the repository at this point in the history
Closes: #728
  • Loading branch information
Julian committed Jul 10, 2022
1 parent 28a7598 commit e39abdb
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
@@ -1,3 +1,9 @@
v4.7.0
------

* Enhance ``best_match`` to prefer errors from branches of the schema which
match the instance's type (#728)

v4.6.2
------

Expand Down
22 changes: 20 additions & 2 deletions jsonschema/exceptions.py
Expand Up @@ -31,6 +31,7 @@ def __init__(
schema=_unset,
schema_path=(),
parent=None,
type_checker=_unset,
):
super(_Error, self).__init__(
message,
Expand All @@ -54,6 +55,7 @@ def __init__(
self.instance = instance
self.schema = schema
self.parent = parent
self._type_checker = type_checker

for error in context:
error.parent = self
Expand Down Expand Up @@ -124,7 +126,10 @@ def json_path(self):
path += "." + elem
return path

def _set(self, **kwargs):
def _set(self, type_checker=None, **kwargs):
if type_checker is not None and self._type_checker is _unset:
self._type_checker = type_checker

for k, v in kwargs.items():
if getattr(self, k) is _unset:
setattr(self, k, v)
Expand All @@ -136,6 +141,14 @@ def _contents(self):
)
return dict((attr, getattr(self, attr)) for attr in attrs)

def _matches_type(self):
try:
expected_type = self.schema["type"]
except (KeyError, TypeError):
return False
else:
return self._type_checker.is_type(self.instance, expected_type)


class ValidationError(_Error):
"""
Expand Down Expand Up @@ -307,7 +320,12 @@ def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
"""
def relevance(error):
validator = error.validator
return -len(error.path), validator not in weak, validator in strong
return (
-len(error.path),
validator not in weak,
validator in strong,
not error._matches_type(),
)
return relevance


Expand Down
32 changes: 32 additions & 0 deletions jsonschema/tests/test_exceptions.py
Expand Up @@ -131,6 +131,38 @@ def test_nested_context_for_oneOf(self):
best = self.best_match_of(instance={"foo": {"bar": 12}}, schema=schema)
self.assertEqual(best.validator_value, "array")

def test_it_prioritizes_matching_types(self):
schema = {
"properties": {
"foo": {
"anyOf": [
{"type": "array", "minItems": 2},
{"type": "string", "minLength": 10},
],
},
},
}
best = self.best_match_of(instance={"foo": "bar"}, schema=schema)
self.assertEqual(best.validator, "minLength")

reordered = {
"properties": {
"foo": {
"anyOf": [
{"type": "string", "minLength": 10},
{"type": "array", "minItems": 2},
],
},
},
}
best = self.best_match_of(instance={"foo": "bar"}, schema=reordered)
self.assertEqual(best.validator, "minLength")

def test_boolean_schemas(self):
schema = {"properties": {"foo": False}}
best = self.best_match_of(instance={"foo": "bar"}, schema=schema)
self.assertIsNone(best.validator)

def test_one_error(self):
validator = _LATEST_VERSION({"minProperties": 2})
error, = validator.iter_errors({})
Expand Down
1 change: 1 addition & 0 deletions jsonschema/validators.py
Expand Up @@ -246,6 +246,7 @@ def iter_errors(self, instance, _schema=None):
validator_value=v,
instance=instance,
schema=_schema,
type_checker=self.TYPE_CHECKER,
)
if k not in {"if", "$ref"}:
error.schema_path.appendleft(k)
Expand Down

0 comments on commit e39abdb

Please sign in to comment.