From d5c208672e4ac2e0f83fcc5772e95a2a7c446480 Mon Sep 17 00:00:00 2001 From: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:40:39 +0200 Subject: [PATCH] Add TARGET_FIELDS to config validation (#150238) --- homeassistant/helpers/config_validation.py | 20 +++++++++++ homeassistant/helpers/llm.py | 2 +- tests/helpers/test_llm.py | 39 +++------------------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index c2ebddf8012..e3dda5d32f3 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -373,6 +373,17 @@ def entity_id(value: Any) -> str: raise vol.Invalid(f"Entity ID {value} is an invalid entity ID") +def strict_entity_id(value: Any) -> str: + """Validate Entity ID, strictly.""" + if not isinstance(value, str): + raise vol.Invalid(f"Entity ID {value} is not a string") + + if valid_entity_id(value): + return value + + raise vol.Invalid(f"Entity ID {value} is not a valid entity ID") + + def entity_id_or_uuid(value: Any) -> str: """Validate Entity specified by entity_id or uuid.""" with contextlib.suppress(vol.Invalid): @@ -1292,6 +1303,15 @@ PLATFORM_SCHEMA = vol.Schema( PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) + +TARGET_FIELDS: VolDictType = { + vol.Optional(ATTR_ENTITY_ID): vol.All(ensure_list, [strict_entity_id]), + vol.Optional(ATTR_DEVICE_ID): vol.All(ensure_list, [str]), + vol.Optional(ATTR_AREA_ID): vol.All(ensure_list, [str]), + vol.Optional(ATTR_FLOOR_ID): vol.All(ensure_list, [str]), + vol.Optional(ATTR_LABEL_ID): vol.All(ensure_list, [str]), +} + ENTITY_SERVICE_FIELDS: VolDictType = { # Either accept static entity IDs, a single dynamic template or a mixed list # of static and dynamic templates. While this could be solved with a single diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index dc69916a728..5427c220c02 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -828,7 +828,7 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 return {"type": "string", "enum": options} if isinstance(schema, selector.TargetSelector): - return convert(cv.TARGET_SERVICE_FIELDS) + return convert(cv.TARGET_FIELDS) if isinstance(schema, selector.TemplateSelector): return {"type": "string", "format": "jinja2"} diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index 9ba93cef4ca..6eef62a2c54 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -1216,43 +1216,14 @@ async def test_selector_serializer( selector.StateSelector({"entity_id": "sensor.test"}) ) == {"type": "string"} target_schema = selector_serializer(selector.TargetSelector()) - target_schema["properties"]["entity_id"]["anyOf"][0][ - "enum" - ].sort() # Order is not deterministic assert target_schema == { "type": "object", "properties": { - "area_id": { - "anyOf": [ - {"type": "string", "enum": ["none"]}, - {"type": "array", "items": {"type": "string", "nullable": True}}, - ] - }, - "device_id": { - "anyOf": [ - {"type": "string", "enum": ["none"]}, - {"type": "array", "items": {"type": "string", "nullable": True}}, - ] - }, - "entity_id": { - "anyOf": [ - {"type": "string", "enum": ["all", "none"], "format": "lower"}, - {"type": "string", "nullable": True}, - {"type": "array", "items": {"type": "string"}}, - ] - }, - "floor_id": { - "anyOf": [ - {"type": "string", "enum": ["none"]}, - {"type": "array", "items": {"type": "string", "nullable": True}}, - ] - }, - "label_id": { - "anyOf": [ - {"type": "string", "enum": ["none"]}, - {"type": "array", "items": {"type": "string", "nullable": True}}, - ] - }, + "area_id": {"items": {"type": "string"}, "type": "array"}, + "device_id": {"items": {"type": "string"}, "type": "array"}, + "entity_id": {"items": {"type": "string"}, "type": "array"}, + "floor_id": {"items": {"type": "string"}, "type": "array"}, + "label_id": {"items": {"type": "string"}, "type": "array"}, }, "required": [], }