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 1.5.22
* #288: Add database dump hook for MongoDB. * #288: Add database dump hook for MongoDB.
* #470: Move mysqldump options to the beginning of the command due to MySQL bug 30994. * #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') 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. 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)) 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. Given an instance of jsonschema.exceptions.ValidationError, format it for display as a string.
''' '''
if not error.path: if not error.path:
return 'At the top level: {}'.format(error.message) 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) return "At '{}': {}".format(formatted_path.lstrip('.'), error.message)
@ -44,8 +44,8 @@ class Validation_error(ValueError):
def __init__(self, config_filename, errors): def __init__(self, config_filename, errors):
''' '''
Given a configuration filename path and a sequence of Given a configuration filename path and a sequence of string error messages, create a
jsonschema.exceptions.ValidationError instances, create a Validation_error. Validation_error.
''' '''
self.config_filename = config_filename self.config_filename = config_filename
self.errors = errors self.errors = errors
@ -56,7 +56,7 @@ class Validation_error(ValueError):
''' '''
return 'An error occurred while parsing a configuration file at {}:\n'.format( return 'An error occurred while parsing a configuration file at {}:\n'.format(
self.config_filename 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): 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)) validation_errors = tuple(validator.iter_errors(config))
if validation_errors: 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) apply_logical_validation(config_filename, config)

View file

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

View file

@ -4,33 +4,31 @@ from flexmock import flexmock
from borgmatic.config import validate as module from borgmatic.config import validate as module
def test_format_error_path_element_formats_array_index(): def test_format_json_error_path_element_formats_array_index():
module.format_error_path_element(3) == '[3]' module.format_json_error_path_element(3) == '[3]'
def test_format_error_path_element_formats_property(): def test_format_json_error_path_element_formats_property():
module.format_error_path_element('foo') == '.foo' module.format_json_error_path_element('foo') == '.foo'
def test_format_error_formats_error_including_path(): def test_format_json_error_formats_error_including_path():
flexmock(module).format_error_path_element = lambda element: '.{}'.format(element) flexmock(module).format_json_error_path_element = lambda element: '.{}'.format(element)
error = flexmock(message='oops', path=['foo', 'bar']) 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(): def test_format_json_error_formats_error_without_path():
flexmock(module).should_receive('format_error_path_element').never() flexmock(module).should_receive('format_json_error_path_element').never()
error = flexmock(message='oops', path=[]) 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(): def test_validation_error_string_contains_errors():
flexmock(module).format_error = lambda error: error.message flexmock(module).format_json_error = lambda error: error.message
error = module.Validation_error( error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
'config.yaml', (flexmock(message='oops', path=None), flexmock(message='uh oh'))
)
result = str(error) 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(): 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): with pytest.raises(module.Validation_error):
module.apply_logical_validation( 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(): 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): with pytest.raises(module.Validation_error):
module.apply_logical_validation( 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(): 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): with pytest.raises(module.Validation_error):
module.apply_logical_validation( module.apply_logical_validation(