Return an error response when a REST API service call fails (#181590)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-09-08 01:29:46 +02:00
committed by GitHub
co-authored by Claude
parent e85b8a256e
commit 384c153186
2 changed files with 64 additions and 0 deletions
+10
View File
@@ -39,9 +39,11 @@ from homeassistant.const import (
)
from homeassistant.core import Event, EventStateChangedData, HomeAssistant
from homeassistant.exceptions import (
HomeAssistantError,
InvalidEntityFormatError,
InvalidStateError,
ServiceNotFound,
ServiceValidationError,
TemplateError,
Unauthorized,
)
@@ -455,6 +457,14 @@ class APIDomainServicesView(HomeAssistantView):
)
except (vol.Invalid, ServiceNotFound) as ex:
raise HTTPBadRequest from ex
except ServiceValidationError as ex:
return self.json_message(str(ex), HTTPStatus.BAD_REQUEST)
except Unauthorized:
# Handled by the view wrapper, which maps it to 401
raise
except HomeAssistantError as ex:
_LOGGER.error("Error during service call to %s.%s: %s", domain, service, ex)
return self.json_message(str(ex), HTTPStatus.INTERNAL_SERVER_ERROR)
finally:
cancel_listen()
+54
View File
@@ -20,6 +20,11 @@ from homeassistant.components.group import DOMAIN as GROUP_DOMAIN
from homeassistant.components.logger import DOMAIN as LOGGER_DOMAIN
from homeassistant.components.system_health import DOMAIN as SYSTEM_HEALTH_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
HomeAssistantError,
ServiceValidationError,
Unauthorized,
)
from homeassistant.loader import Integration
from homeassistant.setup import async_setup_component
from homeassistant.util.yaml.loader import JSON_TYPE
@@ -943,6 +948,55 @@ async def test_api_call_service_not_found(
assert resp.status == HTTPStatus.BAD_REQUEST
@pytest.mark.parametrize(
("error", "status"),
[
pytest.param(
ServiceValidationError("Bad input"),
HTTPStatus.BAD_REQUEST,
id="service_validation_error",
),
pytest.param(
HomeAssistantError("Something failed"),
HTTPStatus.INTERNAL_SERVER_ERROR,
id="home_assistant_error",
),
],
)
async def test_api_call_service_raises(
hass: HomeAssistant,
mock_api_client: TestClient,
error: HomeAssistantError,
status: HTTPStatus,
) -> None:
"""Test the API returns a JSON error if the service raises."""
async def handler(service_call: ha.ServiceCall) -> None:
"""Raise the configured error."""
raise error
hass.services.async_register("test_domain", "test_service", handler)
resp = await mock_api_client.post("/api/services/test_domain/test_service")
assert resp.status == status
assert await resp.json() == {"message": str(error)}
async def test_api_call_service_unauthorized(
hass: HomeAssistant, mock_api_client: TestClient
) -> None:
"""Test the API returns 401 if the service denies permission."""
async def handler(service_call: ha.ServiceCall) -> None:
"""Deny the call."""
raise Unauthorized
hass.services.async_register("test_domain", "test_service", handler)
resp = await mock_api_client.post("/api/services/test_domain/test_service")
assert resp.status == HTTPStatus.UNAUTHORIZED
async def test_api_call_service_bad_data(
hass: HomeAssistant, mock_api_client: TestClient
) -> None: