diff --git a/compose/config/validation.py b/compose/config/validation.py index 44763fda3..971cfe371 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -107,16 +107,6 @@ def process_errors(errors, service_name=None): def _clean_error_message(message): 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): """ 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 + 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 = [] invalid_keys = [] required = [] @@ -200,12 +223,10 @@ def process_errors(errors, service_name=None): required.append(_clean_error_message(error.message)) elif error.validator == 'oneOf': config_key = error.path[0] + msg = _parse_oneof_validator(error) - valid_types = [_parse_valid_types_from_schema(schema) for schema in error.schema['oneOf']] - valid_type_msg = " or ".join(valid_types) - - type_errors.append("Service '{}' configuration key '{}' contains an invalid type, valid types are {}".format( - service_name, config_key, valid_type_msg) + type_errors.append("Service '{}' configuration key '{}' {}".format( + service_name, config_key, msg) ) elif error.validator == 'type': msg = _parse_valid_types_from_validator(error.validator_value) diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 90d7a6a26..f55789207 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -183,7 +183,8 @@ class ConfigTest(unittest.TestCase): ) 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): config.load( config.ConfigDetails( @@ -222,7 +223,7 @@ class ConfigTest(unittest.TestCase): ) 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): config.load(