Răsfoiți Sursa

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 <[email protected]>
Mazz Mosley 10 ani în urmă
părinte
comite
cf7b595385
2 a modificat fișierele cu 39 adăugiri și 17 ștergeri
  1. 36 15
      compose/config/validation.py
  2. 3 2
      tests/unit/config_test.py

+ 36 - 15
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)

+ 3 - 2
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(