Improve formatting of config validation errors (#103957)
* Improve formatting of config validation errors * Address review comments
This commit is contained in:
parent
85eac5a1b1
commit
94a2087ba0
4 changed files with 65 additions and 25 deletions
|
@ -17,7 +17,7 @@ from urllib.parse import urlparse
|
|||
|
||||
from awesomeversion import AwesomeVersion
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
from voluptuous.humanize import MAX_VALIDATION_ERROR_ITEM_LENGTH
|
||||
|
||||
from . import auth
|
||||
from .auth import mfa_modules as auth_mfa_modules, providers as auth_providers
|
||||
|
@ -578,6 +578,47 @@ def find_annotation(
|
|||
return find_annotation_rec(config, list(path), None)
|
||||
|
||||
|
||||
def stringify_invalid(ex: vol.Invalid) -> str:
|
||||
"""Stringify voluptuous.Invalid.
|
||||
|
||||
Based on voluptuous.error.Invalid.__str__, the main modification
|
||||
is to format the path delimited by -> instead of @data[].
|
||||
"""
|
||||
path = "->".join(str(m) for m in ex.path)
|
||||
# This function is an alternative to the stringification done by
|
||||
# vol.Invalid.__str__, so we need to call Exception.__str__ here
|
||||
# instead of str(ex)
|
||||
output = Exception.__str__(ex)
|
||||
if error_type := ex.error_type:
|
||||
output += " for " + error_type
|
||||
return f"{output} '{path}'"
|
||||
|
||||
|
||||
def humanize_error(
|
||||
data: Any,
|
||||
validation_error: vol.Invalid,
|
||||
max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH,
|
||||
) -> str:
|
||||
"""Provide a more helpful + complete validation error message.
|
||||
|
||||
This is a modified version of voluptuous.error.Invalid.__str__,
|
||||
the modifications make some minor changes to the formatting.
|
||||
"""
|
||||
if isinstance(validation_error, vol.MultipleInvalid):
|
||||
return "\n".join(
|
||||
sorted(
|
||||
humanize_error(data, sub_error, max_sub_error_length)
|
||||
for sub_error in validation_error.errors
|
||||
)
|
||||
)
|
||||
offending_item_summary = repr(_get_by_path(data, validation_error.path))
|
||||
if len(offending_item_summary) > max_sub_error_length:
|
||||
offending_item_summary = (
|
||||
f"{offending_item_summary[: max_sub_error_length - 3]}..."
|
||||
)
|
||||
return f"{stringify_invalid(validation_error)}, got {offending_item_summary}"
|
||||
|
||||
|
||||
@callback
|
||||
def _format_config_error(
|
||||
ex: Exception, domain: str, config: dict, link: str | None = None
|
||||
|
|
|
@ -204,7 +204,7 @@ async def test_optimistic_states(hass: HomeAssistant, start_ha) -> None:
|
|||
{
|
||||
"alarm_control_panel": {"platform": "template"},
|
||||
},
|
||||
"required key not provided @ data['panels']",
|
||||
"required key not provided 'panels'",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
|
|
@ -83,8 +83,7 @@ async def test_bad_core_config(hass: HomeAssistant) -> None:
|
|||
(
|
||||
"Invalid config for [homeassistant] at "
|
||||
f"{hass.config.path(YAML_CONFIG_FILE)}, line 2: "
|
||||
"not a valid value for dictionary value @ data['unit_system']. Got "
|
||||
"'bad'."
|
||||
"not a valid value for dictionary value 'unit_system', got 'bad'."
|
||||
),
|
||||
"homeassistant",
|
||||
{"unit_system": "bad"},
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
# serializer version: 1
|
||||
# name: test_component_config_validation_error[basic]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 6: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 9: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 6: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 9: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 12: 'no_such_option' is an invalid option for [iot_domain.non_adr_0007], check: no_such_option",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 19: required key not provided @ data['adr_0007_2']['host']. Got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 24: expected int for dictionary value @ data['adr_0007_3']['port']. Got 'foo'.",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 19: required key not provided 'adr_0007_2->host', got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 24: expected int for dictionary value 'adr_0007_3->port', got 'foo'.",
|
||||
"Invalid config for [adr_0007_4] at <BASE_PATH>/fixtures/core/config/component_validation/basic/configuration.yaml, line 29: 'no_such_option' is an invalid option for [adr_0007_4], check: adr_0007_4->no_such_option",
|
||||
])
|
||||
# ---
|
||||
# name: test_component_config_validation_error[basic_include]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/iot_domain.yaml, line 5: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/iot_domain.yaml, line 8: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/iot_domain.yaml, line 5: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/iot_domain.yaml, line 8: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/iot_domain.yaml, line 11: 'no_such_option' is an invalid option for [iot_domain.non_adr_0007], check: no_such_option",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/configuration.yaml, line 3: required key not provided @ data['adr_0007_2']['host']. Got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/adr_0007_3.yaml, line 3: expected int for dictionary value @ data['adr_0007_3']['port']. Got 'foo'.",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/configuration.yaml, line 3: required key not provided 'adr_0007_2->host', got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/basic_include/integrations/adr_0007_3.yaml, line 3: expected int for dictionary value 'adr_0007_3->port', got 'foo'.",
|
||||
])
|
||||
# ---
|
||||
# name: test_component_config_validation_error[include_dir_list]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_list/iot_domain/iot_domain_2.yaml, line 2: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_list/iot_domain/iot_domain_3.yaml, line 3: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_list/iot_domain/iot_domain_2.yaml, line 2: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_list/iot_domain/iot_domain_3.yaml, line 3: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_list/iot_domain/iot_domain_4.yaml, line 3: 'no_such_option' is an invalid option for [iot_domain.non_adr_0007], check: no_such_option",
|
||||
])
|
||||
# ---
|
||||
# name: test_component_config_validation_error[include_dir_merge_list]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_merge_list/iot_domain/iot_domain_1.yaml, line 5: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_merge_list/iot_domain/iot_domain_2.yaml, line 3: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_merge_list/iot_domain/iot_domain_1.yaml, line 5: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_merge_list/iot_domain/iot_domain_2.yaml, line 3: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/include_dir_merge_list/iot_domain/iot_domain_2.yaml, line 6: 'no_such_option' is an invalid option for [iot_domain.non_adr_0007], check: no_such_option",
|
||||
])
|
||||
# ---
|
||||
# name: test_component_config_validation_error[packages]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 11: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 16: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 11: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 16: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 21: 'no_such_option' is an invalid option for [iot_domain.non_adr_0007], check: no_such_option",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 28: required key not provided @ data['adr_0007_2']['host']. Got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 33: expected int for dictionary value @ data['adr_0007_3']['port']. Got 'foo'.",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 28: required key not provided 'adr_0007_2->host', got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 33: expected int for dictionary value 'adr_0007_3->port', got 'foo'.",
|
||||
"Invalid config for [adr_0007_4] at <BASE_PATH>/fixtures/core/config/component_validation/packages/configuration.yaml, line 38: 'no_such_option' is an invalid option for [adr_0007_4], check: adr_0007_4->no_such_option",
|
||||
])
|
||||
# ---
|
||||
# name: test_component_config_validation_error[packages_include_dir_named]
|
||||
list([
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 6: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 9: expected str for dictionary value @ data['option1']. Got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 12: required key not provided @ data['platform']. Got None.",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/adr_0007_2.yaml, line 2: required key not provided @ data['adr_0007_2']['host']. Got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/adr_0007_3.yaml, line 4: expected int for dictionary value @ data['adr_0007_3']['port']. Got 'foo'.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 6: required key not provided 'platform', got None.",
|
||||
"Invalid config for [iot_domain.non_adr_0007] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 9: expected str for dictionary value 'option1', got 123.",
|
||||
"Invalid config for [iot_domain] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/iot_domain.yaml, line 12: required key not provided 'platform', got None.",
|
||||
"Invalid config for [adr_0007_2] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/adr_0007_2.yaml, line 2: required key not provided 'adr_0007_2->host', got None.",
|
||||
"Invalid config for [adr_0007_3] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/adr_0007_3.yaml, line 4: expected int for dictionary value 'adr_0007_3->port', got 'foo'.",
|
||||
"Invalid config for [adr_0007_4] at <BASE_PATH>/fixtures/core/config/component_validation/packages_include_dir_named/integrations/adr_0007_4.yaml, line 4: 'no_such_option' is an invalid option for [adr_0007_4], check: adr_0007_4->no_such_option",
|
||||
])
|
||||
# ---
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue