mirror of https://github.com/docker/compose.git
Improve error messages from oneOf schema errors
oneOf schema ValidationError takes a little more work to parse and pull out more detail so we can give a better error message back to the user. Signed-off-by: Mazz Mosley <mazz@houseofmnowster.com>
This commit is contained in:
parent
418ec5336b
commit
cf7b595385
|
@ -107,16 +107,6 @@ def process_errors(errors, service_name=None):
|
||||||
def _clean_error_message(message):
|
def _clean_error_message(message):
|
||||||
return message.replace("u'", "'")
|
return message.replace("u'", "'")
|
||||||
|
|
||||||
def _parse_valid_types_from_schema(schema):
|
|
||||||
"""
|
|
||||||
Our defined types using $ref in the schema require some extra parsing
|
|
||||||
retrieve a helpful type for error message display.
|
|
||||||
"""
|
|
||||||
if '$ref' in schema:
|
|
||||||
return schema['$ref'].replace("#/definitions/", "").replace("_", " ")
|
|
||||||
else:
|
|
||||||
return str(schema['type'])
|
|
||||||
|
|
||||||
def _parse_valid_types_from_validator(validator):
|
def _parse_valid_types_from_validator(validator):
|
||||||
"""
|
"""
|
||||||
A validator value can be either an array of valid types or a string of
|
A validator value can be either an array of valid types or a string of
|
||||||
|
@ -149,6 +139,39 @@ def process_errors(errors, service_name=None):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _parse_oneof_validator(error):
|
||||||
|
"""
|
||||||
|
oneOf has multiple schemas, so we need to reason about which schema, sub
|
||||||
|
schema or constraint the validation is failing on.
|
||||||
|
Inspecting the context value of a ValidationError gives us information about
|
||||||
|
which sub schema failed and which kind of error it is.
|
||||||
|
"""
|
||||||
|
constraint = [context for context in error.context if len(context.path) > 0]
|
||||||
|
if constraint:
|
||||||
|
valid_types = _parse_valid_types_from_validator(constraint[0].validator_value)
|
||||||
|
msg = "contains {}, which is an invalid type, it should be {}".format(
|
||||||
|
constraint[0].instance,
|
||||||
|
valid_types
|
||||||
|
)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
uniqueness = [context for context in error.context if context.validator == 'uniqueItems']
|
||||||
|
if uniqueness:
|
||||||
|
msg = "contains non unique items, please remove duplicates from {}".format(
|
||||||
|
uniqueness[0].instance
|
||||||
|
)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
types = [context.validator_value for context in error.context if context.validator == 'type']
|
||||||
|
if len(types) == 1:
|
||||||
|
valid_types = _parse_valid_types_from_validator(types[0])
|
||||||
|
else:
|
||||||
|
valid_types = _parse_valid_types_from_validator(types)
|
||||||
|
|
||||||
|
msg = "contains an invalid type, it should be {}".format(valid_types)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
root_msgs = []
|
root_msgs = []
|
||||||
invalid_keys = []
|
invalid_keys = []
|
||||||
required = []
|
required = []
|
||||||
|
@ -200,12 +223,10 @@ def process_errors(errors, service_name=None):
|
||||||
required.append(_clean_error_message(error.message))
|
required.append(_clean_error_message(error.message))
|
||||||
elif error.validator == 'oneOf':
|
elif error.validator == 'oneOf':
|
||||||
config_key = error.path[0]
|
config_key = error.path[0]
|
||||||
|
msg = _parse_oneof_validator(error)
|
||||||
|
|
||||||
valid_types = [_parse_valid_types_from_schema(schema) for schema in error.schema['oneOf']]
|
type_errors.append("Service '{}' configuration key '{}' {}".format(
|
||||||
valid_type_msg = " or ".join(valid_types)
|
service_name, config_key, msg)
|
||||||
|
|
||||||
type_errors.append("Service '{}' configuration key '{}' contains an invalid type, valid types are {}".format(
|
|
||||||
service_name, config_key, valid_type_msg)
|
|
||||||
)
|
)
|
||||||
elif error.validator == 'type':
|
elif error.validator == 'type':
|
||||||
msg = _parse_valid_types_from_validator(error.validator_value)
|
msg = _parse_valid_types_from_validator(error.validator_value)
|
||||||
|
|
|
@ -183,7 +183,8 @@ class ConfigTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_list_of_strings_format(self):
|
def test_invalid_list_of_strings_format(self):
|
||||||
expected_error_msg = "'command' contains an invalid type, valid types are string or array"
|
expected_error_msg = "Service 'web' configuration key 'command' contains 1"
|
||||||
|
expected_error_msg += ", which is an invalid type, it should be a string"
|
||||||
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
|
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
|
||||||
config.load(
|
config.load(
|
||||||
config.ConfigDetails(
|
config.ConfigDetails(
|
||||||
|
@ -222,7 +223,7 @@ class ConfigTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_config_extra_hosts_list_of_dicts_validation_error(self):
|
def test_config_extra_hosts_list_of_dicts_validation_error(self):
|
||||||
expected_error_msg = "Service 'web' configuration key 'extra_hosts' contains an invalid type"
|
expected_error_msg = "key 'extra_hosts' contains {'somehost': '162.242.195.82'}, which is an invalid type, it should be a string"
|
||||||
|
|
||||||
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
|
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
|
||||||
config.load(
|
config.load(
|
||||||
|
|
Loading…
Reference in New Issue