diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py
index 0568203a91c..5aafd727178 100644
--- a/homeassistant/components/rest/binary_sensor.py
+++ b/homeassistant/components/rest/binary_sensor.py
@@ -4,6 +4,7 @@ from __future__ import annotations
import logging
import ssl
+from xml.parsers.expat import ExpatError
import voluptuous as vol
@@ -149,24 +150,31 @@ class RestBinarySensor(ManualTriggerEntity, RestEntity, BinarySensorEntity):
self._attr_is_on = False
return
- response = self.rest.data
+ try:
+ response = self.rest.data_without_xml()
+ except ExpatError as err:
+ self._attr_is_on = False
+ _LOGGER.warning(
+ "REST xml result could not be parsed and converted to JSON: %s", err
+ )
+ return
raw_value = response
- if self._value_template is not None:
+ if response is not None and self._value_template is not None:
response = self._value_template.async_render_with_possible_json_value(
- self.rest.data, False
+ response, False
)
try:
- self._attr_is_on = bool(int(response))
+ self._attr_is_on = bool(int(str(response)))
except ValueError:
self._attr_is_on = {
"true": True,
"on": True,
"open": True,
"yes": True,
- }.get(response.lower(), False)
+ }.get(str(response).lower(), False)
self._process_manual_data(raw_value)
self.async_write_ha_state()
diff --git a/homeassistant/components/rest/data.py b/homeassistant/components/rest/data.py
index 4c9667e7651..e198202ae57 100644
--- a/homeassistant/components/rest/data.py
+++ b/homeassistant/components/rest/data.py
@@ -4,7 +4,6 @@ from __future__ import annotations
import logging
import ssl
-from xml.parsers.expat import ExpatError
import httpx
import xmltodict
@@ -79,14 +78,8 @@ class RestData:
and (content_type := headers.get("content-type"))
and content_type.startswith(XML_MIME_TYPES)
):
- try:
- value = json_dumps(xmltodict.parse(value))
- except ExpatError:
- _LOGGER.warning(
- "REST xml result could not be parsed and converted to JSON"
- )
- else:
- _LOGGER.debug("JSON converted from XML: %s", value)
+ value = json_dumps(xmltodict.parse(value))
+ _LOGGER.debug("JSON converted from XML: %s", value)
return value
async def async_update(self, log_errors: bool = True) -> None:
diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py
index 199ab3721c3..810d286d147 100644
--- a/homeassistant/components/rest/sensor.py
+++ b/homeassistant/components/rest/sensor.py
@@ -5,6 +5,7 @@ from __future__ import annotations
import logging
import ssl
from typing import Any
+from xml.parsers.expat import ExpatError
import voluptuous as vol
@@ -159,7 +160,13 @@ class RestSensor(ManualTriggerSensorEntity, RestEntity):
def _update_from_rest_data(self) -> None:
"""Update state from the rest data."""
- value = self.rest.data_without_xml()
+ try:
+ value = self.rest.data_without_xml()
+ except ExpatError as err:
+ _LOGGER.warning(
+ "REST xml result could not be parsed and converted to JSON: %s", err
+ )
+ value = self.rest.data
if self._json_attrs:
self._attr_extra_state_attributes = parse_json_attributes(
diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py
index 39e6a7aea0d..65ec6bf5c05 100644
--- a/tests/components/rest/test_binary_sensor.py
+++ b/tests/components/rest/test_binary_sensor.py
@@ -362,6 +362,77 @@ async def test_setup_get_on(hass: HomeAssistant) -> None:
assert state.state == STATE_ON
+@respx.mock
+async def test_setup_get_xml(hass: HomeAssistant) -> None:
+ """Test setup with valid xml configuration."""
+ respx.get("http://localhost").respond(
+ status_code=HTTPStatus.OK,
+ headers={"content-type": "text/xml"},
+ content="1",
+ )
+ assert await async_setup_component(
+ hass,
+ BINARY_SENSOR_DOMAIN,
+ {
+ BINARY_SENSOR_DOMAIN: {
+ "platform": DOMAIN,
+ "resource": "http://localhost",
+ "method": "GET",
+ "value_template": "{{ value_json.dog }}",
+ "name": "foo",
+ "verify_ssl": "true",
+ "timeout": 30,
+ }
+ },
+ )
+ await hass.async_block_till_done()
+ assert len(hass.states.async_all(BINARY_SENSOR_DOMAIN)) == 1
+
+ state = hass.states.get("binary_sensor.foo")
+ assert state.state == STATE_ON
+
+
+@respx.mock
+@pytest.mark.parametrize(
+ ("content"),
+ [
+ (""),
+ (""),
+ ],
+)
+async def test_setup_get_bad_xml(
+ hass: HomeAssistant, caplog: pytest.LogCaptureFixture, content: str
+) -> None:
+ """Test attributes get extracted from a XML result with bad xml."""
+
+ respx.get("http://localhost").respond(
+ status_code=HTTPStatus.OK,
+ headers={"content-type": "text/xml"},
+ content=content,
+ )
+ assert await async_setup_component(
+ hass,
+ BINARY_SENSOR_DOMAIN,
+ {
+ BINARY_SENSOR_DOMAIN: {
+ "platform": DOMAIN,
+ "resource": "http://localhost",
+ "method": "GET",
+ "value_template": "{{ value_json.toplevel.master_value }}",
+ "name": "foo",
+ "verify_ssl": "true",
+ "timeout": 30,
+ }
+ },
+ )
+ await hass.async_block_till_done()
+ assert len(hass.states.async_all(BINARY_SENSOR_DOMAIN)) == 1
+ state = hass.states.get("binary_sensor.foo")
+
+ assert state.state == STATE_OFF
+ assert "REST xml result could not be parsed" in caplog.text
+
+
@respx.mock
async def test_setup_with_exception(hass: HomeAssistant) -> None:
"""Test setup with exception."""
diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py
index 9af1ac9273e..2e02063b215 100644
--- a/tests/components/rest/test_sensor.py
+++ b/tests/components/rest/test_sensor.py
@@ -868,15 +868,25 @@ async def test_update_with_application_xml_convert_json_attrs_with_jsonattr_temp
@respx.mock
+@pytest.mark.parametrize(
+ ("content", "error_message"),
+ [
+ ("", "Empty reply"),
+ ("", "Erroneous JSON"),
+ ],
+)
async def test_update_with_xml_convert_bad_xml(
- hass: HomeAssistant, caplog: pytest.LogCaptureFixture
+ hass: HomeAssistant,
+ caplog: pytest.LogCaptureFixture,
+ content: str,
+ error_message: str,
) -> None:
"""Test attributes get extracted from a XML result with bad xml."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
- content="",
+ content=content,
)
assert await async_setup_component(
hass,
@@ -901,7 +911,7 @@ async def test_update_with_xml_convert_bad_xml(
assert state.state == STATE_UNKNOWN
assert "REST xml result could not be parsed" in caplog.text
- assert "Empty reply" in caplog.text
+ assert error_message in caplog.text
@respx.mock