mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
174 lines
6.4 KiB
Python
174 lines
6.4 KiB
Python
"""
|
|
Courtesy https://github.com/ccpgames/jsonschema-errorprinter with minor
|
|
updates to support Python 3 (changed cStringIO to io), to print out
|
|
multiple errors, the ability to place default values, and a few
|
|
other small changes.
|
|
|
|
Licensed under the MIT License, reproduced below:
|
|
Copyright © 2015 CCP hf.
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
THERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
"""
|
|
|
|
"""
|
|
Json Schema Validation Error Pretty-printer.
|
|
---------------------------------------------------
|
|
|
|
Makes a user friendly error message from a ValidationError.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import io
|
|
import json
|
|
import jsonschema
|
|
import jsonschema.validators
|
|
|
|
# The 'default' field is really just for documentation in the
|
|
# json schema. We would like to use it to actually fill in
|
|
# values when they aren't supplied. This code is provided
|
|
# by the jsonschema project itself because this behavior
|
|
# is not part of the default jsonschema definition
|
|
# https://python-jsonschema.readthedocs.io/en/latest/faq/
|
|
def extend_with_default(validator_class):
|
|
validate_properties = validator_class.VALIDATORS["properties"]
|
|
|
|
def set_defaults(validator, properties, instance, schema):
|
|
for property, subschema in properties.items():
|
|
if "default" in subschema:
|
|
instance.setdefault(property, subschema["default"])
|
|
|
|
for error in validate_properties(
|
|
validator, properties, instance, schema,
|
|
):
|
|
yield error
|
|
|
|
return jsonschema.validators.extend(
|
|
validator_class, {"properties": set_defaults},
|
|
)
|
|
|
|
|
|
def check_json(json_object, schema, context=None) -> tuple[list[str], dict]:
|
|
try:
|
|
DefaultValidatingDraft7Validator = extend_with_default(
|
|
jsonschema.Draft7Validator)
|
|
|
|
|
|
validator = DefaultValidatingDraft7Validator(schema, jsonschema.FormatChecker())
|
|
#validator = jsonschema.Draft7Validator(schema, jsonschema.FormatChecker())
|
|
errors_formatted = []
|
|
|
|
for error in sorted(validator.iter_errors(json_object), key=str):
|
|
|
|
#validate(json_object, schema, format_checker=FormatChecker())
|
|
# except jsonschema.ValidationError as e:
|
|
report = generate_validation_error_report(error, json_object)
|
|
|
|
#note = "\n*** Note - If there is more than one error, only the first error is shown ***\n\n"
|
|
if context:
|
|
errors_formatted.append(
|
|
"Schema check failed for '{}'\n{}".format(context, report))
|
|
# return note + "Schema check failed for '{}'\n{}".format(context, report)
|
|
else:
|
|
errors_formatted.append(
|
|
"Schema check failed.\n{}".format(report))
|
|
# return note + "Schema check failed.\n{}".format(report)
|
|
if len(errors_formatted) == 0:
|
|
#DefaultValidatingDraft7Validator = extend_with_default(
|
|
# jsonschema.Draft7Validator)
|
|
#DefaultValidatingDraft7Validator(schema).validate(json_object)
|
|
return (errors_formatted, json_object)
|
|
else:
|
|
return (errors_formatted, {})
|
|
except Exception as e:
|
|
# Some error occurred, probably related to the schema itself
|
|
raise(Exception("Error validating the JSON Schema: %s" % (str(e))))
|
|
|
|
|
|
def generate_validation_error_report(
|
|
e,
|
|
json_object,
|
|
lines_before=7,
|
|
lines_after=7
|
|
):
|
|
"""
|
|
Generate a detailed report of a schema validation error.
|
|
|
|
'e' is a jsonschema.ValidationError exception that errored on
|
|
'json_object'.
|
|
|
|
Steps to discover the location of the validation error:
|
|
1. Traverse the json object using the 'path' in the validation exception
|
|
and replace the offending value with a special marker.
|
|
2. Pretty-print the json object indendented json text.
|
|
3. Search for the special marker in the json text to find the actual
|
|
line number of the error.
|
|
4. Make a report by showing the error line with a context of
|
|
'lines_before' and 'lines_after' number of lines on each side.
|
|
"""
|
|
|
|
if json_object is None:
|
|
return "'json_object' cannot be None."
|
|
if not e.path:
|
|
return str(e)
|
|
marker = "3fb539deef7c4e2991f265c0a982f5ea"
|
|
|
|
# Find the object that is erroring, and replace it with the marker.
|
|
ob_tmp = json_object
|
|
for entry in list(e.path)[:-1]:
|
|
ob_tmp = ob_tmp[entry]
|
|
|
|
orig, ob_tmp[e.path[-1]] = ob_tmp[e.path[-1]], marker
|
|
|
|
# Pretty print the object and search for the marker.
|
|
json_error = json.dumps(json_object, indent=4)
|
|
string_io_instance = io.StringIO(json_error)
|
|
errline = None
|
|
|
|
for lineno, text in enumerate(string_io_instance):
|
|
if marker in text:
|
|
errline = lineno
|
|
break
|
|
|
|
if errline is not None:
|
|
# Re-create report.
|
|
report = []
|
|
ob_tmp[e.path[-1]] = orig
|
|
json_error = json.dumps(json_object, indent=4)
|
|
string_io_instance = io.StringIO(json_error)
|
|
|
|
for lineno, text in enumerate(string_io_instance):
|
|
if lineno == errline:
|
|
line_text = "{:4}: >>>".format(lineno+1)
|
|
else:
|
|
line_text = "{:4}: ".format(lineno+1)
|
|
report.append(line_text + text.rstrip("\n"))
|
|
|
|
report = report[max(0, errline-lines_before):errline+1+lines_after]
|
|
|
|
s = "Error in line {}:\n".format(errline+1)
|
|
s += "\n".join(report)
|
|
s += '\n\tREASON:' + str(e).split('\n')[0]
|
|
#s += "\n\n" + str(e).replace("u'", "'")
|
|
else:
|
|
s = str(e)
|
|
return s
|