diff --git a/compose/config/validation.py b/compose/config/validation.py index 557e57683..6dc72f566 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -111,30 +111,29 @@ def validate_config_section(filename, config, section): """ if not isinstance(config, dict): raise ConfigurationError( - "In file '{filename}' {section} must be a mapping, not " - "'{type}'.".format( + "In file '{filename}', {section} must be a mapping, not " + "{type}.".format( filename=filename, section=section, - type=python_type_to_yaml_type(config))) + type=anglicize_json_type(python_type_to_yaml_type(config)))) for key, value in config.items(): if not isinstance(key, six.string_types): raise ConfigurationError( - "In file '{filename}' {section} name {name} needs to be a " - "string, eg '{name}'".format( + "In file '{filename}', the {section} name {name} must be a " + "quoted string, i.e. '{name}'.".format( filename=filename, section=section, name=key)) if not isinstance(value, (dict, type(None))): raise ConfigurationError( - "In file '{filename}' {section} '{name}' is the wrong type. " - "It should be a mapping of configuration options, it is a " - "'{type}'.".format( + "In file '{filename}', {section} '{name}' must be a mapping not " + "{type}.".format( filename=filename, section=section, name=key, - type=python_type_to_yaml_type(value))) + type=anglicize_json_type(python_type_to_yaml_type(value)))) def validate_top_level_object(config_file): @@ -203,10 +202,10 @@ def get_unsupported_config_msg(path, error_key): return msg -def anglicize_validator(validator): - if validator in ["array", "object"]: - return 'an ' + validator - return 'a ' + validator +def anglicize_json_type(json_type): + if json_type.startswith(('a', 'e', 'i', 'o', 'u')): + return 'an ' + json_type + return 'a ' + json_type def is_service_dict_schema(schema_id): @@ -314,14 +313,14 @@ def _parse_valid_types_from_validator(validator): a valid type. Parse the valid types and prefix with the correct article. """ if not isinstance(validator, list): - return anglicize_validator(validator) + return anglicize_json_type(validator) if len(validator) == 1: - return anglicize_validator(validator[0]) + return anglicize_json_type(validator[0]) return "{}, or {}".format( - ", ".join([anglicize_validator(validator[0])] + validator[1:-1]), - anglicize_validator(validator[-1])) + ", ".join([anglicize_json_type(validator[0])] + validator[1:-1]), + anglicize_json_type(validator[-1])) def _parse_oneof_validator(error): diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index f43926939..6c5b7818b 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -159,7 +159,7 @@ class CLITestCase(DockerClientTestCase): '-f', 'tests/fixtures/invalid-composefile/invalid.yml', 'config', '-q' ], returncode=1) - assert "'notaservice' is the wrong type" in result.stderr + assert "'notaservice' must be a mapping" in result.stderr # TODO: this shouldn't be v2-dependent @v2_only() diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index c58ddc607..1f5183d78 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -268,7 +268,7 @@ class ConfigTest(unittest.TestCase): }) with pytest.raises(ConfigurationError) as exc: config.load(config_details) - assert "volume must be a mapping, not 'array'" in exc.exconly() + assert "volume must be a mapping, not an array" in exc.exconly() def test_networks_invalid_type_list(self): config_details = build_config_details({ @@ -280,7 +280,7 @@ class ConfigTest(unittest.TestCase): }) with pytest.raises(ConfigurationError) as exc: config.load(config_details) - assert "network must be a mapping, not 'array'" in exc.exconly() + assert "network must be a mapping, not an array" in exc.exconly() def test_load_service_with_name_version(self): with mock.patch('compose.config.config.log') as mock_logging: @@ -392,8 +392,7 @@ class ConfigTest(unittest.TestCase): 'filename.yml') with pytest.raises(ConfigurationError) as exc: config.load(config_details) - error_msg = "service 'web' is the wrong type" - assert error_msg in exc.exconly() + assert "service 'web' must be a mapping not a string." in exc.exconly() def test_config_integer_service_name_raise_validation_error(self): with pytest.raises(ConfigurationError) as excinfo: @@ -405,8 +404,10 @@ class ConfigTest(unittest.TestCase): ) ) - assert "In file 'filename.yml' service name 1 needs to be a string, eg '1'" \ - in excinfo.exconly() + assert ( + "In file 'filename.yml', the service name 1 must be a quoted string, i.e. '1'" in + excinfo.exconly() + ) def test_config_integer_service_name_raise_validation_error_v2(self): with pytest.raises(ConfigurationError) as excinfo: @@ -421,8 +422,10 @@ class ConfigTest(unittest.TestCase): ) ) - assert "In file 'filename.yml' service name 1 needs to be a string, eg '1'" \ - in excinfo.exconly() + assert ( + "In file 'filename.yml', the service name 1 must be a quoted string, i.e. '1'." in + excinfo.exconly() + ) def test_load_with_multiple_files_v1(self): base_file = config.ConfigFile( @@ -556,7 +559,7 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as exc: config.load(details) - assert "service 'bogus' is the wrong type" in exc.exconly() + assert "service 'bogus' must be a mapping not a string." in exc.exconly() assert "In file 'override.yaml'" in exc.exconly() def test_load_sorts_in_dependency_order(self):