Skip to content

Commit e5b9f68

Browse files
authored
Merge pull request #303 from python-jsonschema/add-deep-match
Introduce 'best deep match' heuristic
2 parents 4860639 + bac38a4 commit e5b9f68

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

src/check_jsonschema/reporter.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,33 @@ def _show_validation_error(
7777
if err.context:
7878
best_match = jsonschema.exceptions.best_match(err.context)
7979
self._echo("Underlying errors caused this.", indent=2)
80+
self._echo("")
8081
self._echo("Best Match:", indent=2)
8182
self._echo(self._format_validation_error_message(best_match), indent=4)
83+
84+
best_deep_match = find_best_deep_match(err)
85+
if best_deep_match != best_match:
86+
self._echo("Best Deep Match:", indent=2)
87+
self._echo(
88+
self._format_validation_error_message(best_deep_match), indent=4
89+
)
90+
8291
if self.verbosity > 1:
8392
self._echo("All Errors:", indent=2)
8493
for e in iter_validation_error(err):
8594
self._echo(self._format_validation_error_message(e), indent=4)
95+
else:
96+
num_other_errors = len(list(iter_validation_error(err))) - 1
97+
if best_deep_match != best_match:
98+
num_other_errors -= 1
99+
if num_other_errors > 0:
100+
self._echo("")
101+
self._echo(
102+
f"{click.style(str(num_other_errors), fg='yellow')} other "
103+
"errors were produced. "
104+
"Use '--verbose' to see all errors.",
105+
indent=2,
106+
)
86107

87108
def _show_parse_error(self, filename: str, err: ParseError) -> None:
88109
if self.verbosity < 2:
@@ -139,10 +160,17 @@ def _dump_error_map(
139160
}
140161
if err.context:
141162
best_match = jsonschema.exceptions.best_match(err.context)
163+
best_deep_match = find_best_deep_match(err)
142164
item["best_match"] = {
143165
"path": best_match.json_path,
144166
"message": best_match.message,
145167
}
168+
item["best_deep_match"] = {
169+
"path": best_deep_match.json_path,
170+
"message": best_deep_match.message,
171+
}
172+
num_sub_errors = len(list(iter_validation_error(err))) - 1
173+
item["num_sub_errors"] = num_sub_errors
146174
if self.verbosity > 1:
147175
item["sub_errors"] = [
148176
{"path": suberr.json_path, "message": suberr.message}
@@ -176,3 +204,18 @@ def report_errors(self, result: CheckResult) -> None:
176204
"text": TextReporter,
177205
"json": JsonReporter,
178206
}
207+
208+
209+
def _deep_match_relevance(error: jsonschema.ValidationError) -> tuple[bool | int, ...]:
210+
validator = error.validator
211+
return (
212+
validator not in ("anyOf", "oneOf"),
213+
len(error.absolute_path),
214+
-len(error.path),
215+
)
216+
217+
218+
def find_best_deep_match(
219+
errors: jsonschema.ValidationError,
220+
) -> jsonschema.ValidationError:
221+
return max(iter_validation_error(errors), key=_deep_match_relevance)

tests/unit/test_reporters.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ def test_text_print_validation_error_nested(capsys, verbosity):
130130
assert "$.foo: {} is not of type 'string'" in captured.out
131131
assert "$.bar: {'baz': 'buzz'} is not of type 'string'" in captured.out
132132
assert "$.bar.baz: 'buzz' is not of type 'integer'" in captured.out
133+
else:
134+
assert (
135+
"4 other errors were produced. Use '--verbose' to see all errors."
136+
in captured.out
137+
)
133138

134139

135140
@pytest.mark.parametrize("pretty_json", (True, False))
@@ -187,6 +192,7 @@ def test_json_format_validation_error_nested(capsys, pretty_json, verbosity):
187192
assert len(data["errors"]) == 1
188193
assert "is not valid under any of the given schemas" in data["errors"][0]["message"]
189194
assert data["errors"][0]["has_sub_errors"]
195+
assert data["errors"][0]["num_sub_errors"] == 5
190196

191197
# stop here unless 'verbosity>=2'
192198
if verbosity < 2:

0 commit comments

Comments
 (0)