Skip to content

Add extra_info on ValidationError for required missing property #856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions jsonschema/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,9 @@ def find_evaluated_property_keys_by_schema(validator, instance, schema):
)

return evaluated_keys


def errors_with_property_name(errors, property_name):
for error in errors:
error.extra_info = {"property": property_name}
yield error
19 changes: 11 additions & 8 deletions jsonschema/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from jsonschema._utils import (
ensure_list,
equal,
errors_with_property_name,
extras_msg,
find_additional_properties,
find_evaluated_item_indexes_by_schema,
Expand Down Expand Up @@ -32,8 +33,7 @@ def propertyNames(validator, propertyNames, instance, schema):
return

for property in instance:
yield from validator.descend(instance=property, schema=propertyNames)

yield from errors_with_property_name(validator.descend(instance=property, schema=propertyNames), property)

def additionalProperties(validator, aP, instance, schema):
if not validator.is_type(instance, "object"):
Expand All @@ -43,8 +43,9 @@ def additionalProperties(validator, aP, instance, schema):

if validator.is_type(aP, "object"):
for extra in extras:
yield from validator.descend(instance[extra], aP, path=extra)
yield from errors_with_property_name(validator.descend(instance[extra], aP, path=extra), extra)
elif not aP and extras:
extra_info_properties = [extra for extra in sorted(extras)]
if "patternProperties" in schema:
if len(extras) == 1:
verb = "does"
Expand All @@ -56,11 +57,13 @@ def additionalProperties(validator, aP, instance, schema):
repr(each) for each in sorted(schema["patternProperties"])
)
error = f"{joined} {verb} not match any of the regexes: {patterns}"
yield ValidationError(error)
yield ValidationError(error, extra_info={"properties": extra_info_properties})
else:
error = "Additional properties are not allowed (%s %s unexpected)"
yield ValidationError(error % extras_msg(extras))

yield ValidationError(
error % extras_msg(extras),
extra_info={"properties": extra_info_properties}
)

def items(validator, items, instance, schema):
if not validator.is_type(instance, "array"):
Expand Down Expand Up @@ -261,7 +264,7 @@ def dependentRequired(validator, dependentRequired, instance, schema):
for each in dependency:
if each not in instance:
message = f"{each!r} is a dependency of {property!r}"
yield ValidationError(message)
yield ValidationError(message, extra_info={"property": property})


def dependentSchemas(validator, dependentSchemas, instance, schema):
Expand Down Expand Up @@ -339,7 +342,7 @@ def required(validator, required, instance, schema):
return
for property in required:
if property not in instance:
yield ValidationError(f"{property!r} is a required property")
yield ValidationError(f"{property!r} is a required property", extra_info={"property": property})


def minProperties(validator, mP, instance, schema):
Expand Down
5 changes: 4 additions & 1 deletion jsonschema/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
schema=_unset,
schema_path=(),
parent=None,
extra_info=None,
):
super(_Error, self).__init__(
message,
Expand All @@ -41,6 +42,7 @@ def __init__(
schema,
schema_path,
parent,
extra_info,
)
self.message = message
self.path = self.relative_path = deque(path)
Expand All @@ -52,6 +54,7 @@ def __init__(
self.instance = instance
self.schema = schema
self.parent = parent
self.extra_info = extra_info

for error in context:
error.parent = self
Expand Down Expand Up @@ -130,7 +133,7 @@ def _set(self, **kwargs):
def _contents(self):
attrs = (
"message", "cause", "context", "validator", "validator_value",
"path", "schema_path", "instance", "schema", "parent",
"path", "schema_path", "instance", "schema", "parent", "extra_info",
)
return dict((attr, getattr(self, attr)) for attr in attrs)

Expand Down
38 changes: 38 additions & 0 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,40 @@ def test_unevaluated_properties(self):
class TestValidationErrorDetails(TestCase):
# TODO: These really need unit tests for each individual validator, rather
# than just these higher level tests.
def test_extra_info_required(self):
instance = {"a": 1}
schema = {
"required": ["b", "c"],
}

validator = validators.Draft4Validator(schema)
e1, e2 = validator.iter_errors(instance)
self.assertEqual(e1.extra_info, {"property": "b"})
self.assertEqual(e2.extra_info, {"property": "c"})

def test_extra_info_additionalProperties_single(self):
instance = {"a": 1}
schema = {"additionalProperties": False}

validator = validators.Draft4Validator(schema)
e1, = validator.iter_errors(instance)
self.assertEqual(e1.extra_info, {"properties": ["a"]})

def test_extra_info_additionalProperties_multiple(self):
instance = {"a": 1,"b": 2}
schema = {"additionalProperties": False}

validator = validators.Draft4Validator(schema)
e1, = validator.iter_errors(instance)
self.assertEqual(e1.extra_info, {"properties": ["a", "b"]})

def test_extra_info_dependentRequired(self):
instance = {"a": {}}
schema = {"dependentRequired": {"a": ["bar"]}}
validator = validators.Draft202012Validator(schema)
e1, = validator.iter_errors(instance)
self.assertEqual(e1.extra_info, {"property": "a"})

def test_anyOf(self):
instance = 5
schema = {
Expand Down Expand Up @@ -972,6 +1006,8 @@ def test_additionalProperties(self):
self.assertEqual(e1.validator, "type")
self.assertEqual(e2.validator, "minimum")

self.assertEqual(e1.extra_info, {"property" : "bar"})

def test_patternProperties(self):
instance = {"bar": 1, "foo": 2}
schema = {
Expand Down Expand Up @@ -1050,6 +1086,8 @@ def test_propertyNames(self):
self.assertEqual(error.json_path, "$")
self.assertEqual(error.schema_path, deque(["propertyNames", "not"]))

self.assertEqual(error.extra_info, {"property": "foo"})

def test_if_then(self):
schema = {
"if": {"const": 12},
Expand Down