Fix traceback when a YAML validation error occurs (#480, #482).

This commit is contained in:
Dan Helfman 2022-01-19 20:39:03 -08:00
parent dcead12e86
commit bec73245e9
4 changed files with 29 additions and 26 deletions

3
NEWS
View File

@ -1,3 +1,6 @@
1.5.23.dev0
* #480, #482: Fix traceback when a YAML validation error occurs.
1.5.22
* #288: Add database dump hook for MongoDB.
* #470: Move mysqldump options to the beginning of the command due to MySQL bug 30994.

View File

@ -15,7 +15,7 @@ def schema_filename():
return pkg_resources.resource_filename('borgmatic', 'config/schema.yaml')
def format_error_path_element(path_element):
def format_json_error_path_element(path_element):
'''
Given a path element into a JSON data structure, format it for display as a string.
'''
@ -25,14 +25,14 @@ def format_error_path_element(path_element):
return str('.{}'.format(path_element))
def format_error(error):
def format_json_error(error):
'''
Given an instance of jsonschema.exceptions.ValidationError, format it for display as a string.
'''
if not error.path:
return 'At the top level: {}'.format(error.message)
formatted_path = ''.join(format_error_path_element(element) for element in error.path)
formatted_path = ''.join(format_json_error_path_element(element) for element in error.path)
return "At '{}': {}".format(formatted_path.lstrip('.'), error.message)
@ -44,8 +44,8 @@ class Validation_error(ValueError):
def __init__(self, config_filename, errors):
'''
Given a configuration filename path and a sequence of
jsonschema.exceptions.ValidationError instances, create a Validation_error.
Given a configuration filename path and a sequence of string error messages, create a
Validation_error.
'''
self.config_filename = config_filename
self.errors = errors
@ -56,7 +56,7 @@ class Validation_error(ValueError):
'''
return 'An error occurred while parsing a configuration file at {}:\n'.format(
self.config_filename
) + '\n'.join(format_error(error) for error in self.errors)
) + '\n'.join(error for error in self.errors)
def apply_logical_validation(config_filename, parsed_configuration):
@ -117,7 +117,9 @@ def parse_configuration(config_filename, schema_filename, overrides=None):
validation_errors = tuple(validator.iter_errors(config))
if validation_errors:
raise Validation_error(config_filename, validation_errors)
raise Validation_error(
config_filename, tuple(format_json_error(error) for error in validation_errors)
)
apply_logical_validation(config_filename, config)

View File

@ -1,6 +1,6 @@
from setuptools import find_packages, setup
VERSION = '1.5.22'
VERSION = '1.5.23.dev0'
setup(

View File

@ -4,33 +4,31 @@ from flexmock import flexmock
from borgmatic.config import validate as module
def test_format_error_path_element_formats_array_index():
module.format_error_path_element(3) == '[3]'
def test_format_json_error_path_element_formats_array_index():
module.format_json_error_path_element(3) == '[3]'
def test_format_error_path_element_formats_property():
module.format_error_path_element('foo') == '.foo'
def test_format_json_error_path_element_formats_property():
module.format_json_error_path_element('foo') == '.foo'
def test_format_error_formats_error_including_path():
flexmock(module).format_error_path_element = lambda element: '.{}'.format(element)
def test_format_json_error_formats_error_including_path():
flexmock(module).format_json_error_path_element = lambda element: '.{}'.format(element)
error = flexmock(message='oops', path=['foo', 'bar'])
assert module.format_error(error) == "At 'foo.bar': oops"
assert module.format_json_error(error) == "At 'foo.bar': oops"
def test_format_error_formats_error_without_path():
flexmock(module).should_receive('format_error_path_element').never()
def test_format_json_error_formats_error_without_path():
flexmock(module).should_receive('format_json_error_path_element').never()
error = flexmock(message='oops', path=[])
assert module.format_error(error) == 'At the top level: oops'
assert module.format_json_error(error) == 'At the top level: oops'
def test_validation_error_string_contains_error_messages_and_config_filename():
flexmock(module).format_error = lambda error: error.message
error = module.Validation_error(
'config.yaml', (flexmock(message='oops', path=None), flexmock(message='uh oh'))
)
def test_validation_error_string_contains_errors():
flexmock(module).format_json_error = lambda error: error.message
error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
result = str(error)
@ -40,7 +38,7 @@ def test_validation_error_string_contains_error_messages_and_config_filename():
def test_apply_logical_validation_raises_if_archive_name_format_present_without_prefix():
flexmock(module).format_error = lambda error: error.message
flexmock(module).format_json_error = lambda error: error.message
with pytest.raises(module.Validation_error):
module.apply_logical_validation(
@ -53,7 +51,7 @@ def test_apply_logical_validation_raises_if_archive_name_format_present_without_
def test_apply_logical_validation_raises_if_archive_name_format_present_without_retention_prefix():
flexmock(module).format_error = lambda error: error.message
flexmock(module).format_json_error = lambda error: error.message
with pytest.raises(module.Validation_error):
module.apply_logical_validation(
@ -67,7 +65,7 @@ def test_apply_logical_validation_raises_if_archive_name_format_present_without_
def test_apply_locical_validation_raises_if_unknown_repository_in_check_repositories():
flexmock(module).format_error = lambda error: error.message
flexmock(module).format_json_error = lambda error: error.message
with pytest.raises(module.Validation_error):
module.apply_logical_validation(