Report unserializable data in websocket (#34072)

* Report unserializable data in websocket

* Fix tests

* log types too
This commit is contained in:
Paulus Schoutsen 2020-04-13 23:46:41 -07:00 committed by GitHub
parent 45beb3c6e4
commit d93c09327a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 28 deletions

View file

@ -4,8 +4,9 @@ import json
import logging
import os
import tempfile
from typing import Any, Dict, List, Optional, Type, Union
from typing import Any, Callable, Dict, List, Optional, Type, Union
from homeassistant.core import Event, State
from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
@ -56,7 +57,7 @@ def save_json(
json_data = json.dumps(data, sort_keys=True, indent=4, cls=encoder)
except TypeError:
# pylint: disable=no-member
msg = f"Failed to serialize to JSON: {filename}. Bad data found at {', '.join(find_paths_unserializable_data(data))}"
msg = f"Failed to serialize to JSON: {filename}. Bad data at {format_unserializable_data(find_paths_unserializable_data(data))}"
_LOGGER.error(msg)
raise SerializationError(msg)
@ -85,30 +86,48 @@ def save_json(
_LOGGER.error("JSON replacement cleanup failed: %s", err)
def find_paths_unserializable_data(bad_data: Any) -> List[str]:
def format_unserializable_data(data: Dict[str, Any]) -> str:
"""Format output of find_paths in a friendly way.
Format is comma separated: <path>=<value>(<type>)
"""
return ", ".join(f"{path}={value}({type(value)}" for path, value in data.items())
def find_paths_unserializable_data(
bad_data: Any, *, dump: Callable[[Any], str] = json.dumps
) -> Dict[str, Any]:
"""Find the paths to unserializable data.
This method is slow! Only use for error handling.
"""
to_process = deque([(bad_data, "$")])
invalid = []
invalid = {}
while to_process:
obj, obj_path = to_process.popleft()
try:
json.dumps(obj)
dump(obj)
continue
except TypeError:
except (ValueError, TypeError):
pass
# We convert states and events to dict so we can find bad data inside it
if isinstance(obj, State):
obj_path += f"(state: {obj.entity_id})"
obj = obj.as_dict()
elif isinstance(obj, Event):
obj_path += f"(event: {obj.event_type})"
obj = obj.as_dict()
if isinstance(obj, dict):
for key, value in obj.items():
try:
# Is key valid?
json.dumps({key: None})
dump({key: None})
except TypeError:
invalid.append(f"{obj_path}<key: {key}>")
invalid[f"{obj_path}<key: {key}>"] = key
else:
# Process value
to_process.append((value, f"{obj_path}.{key}"))
@ -116,6 +135,6 @@ def find_paths_unserializable_data(bad_data: Any) -> List[str]:
for idx, value in enumerate(obj):
to_process.append((value, f"{obj_path}[{idx}]"))
else:
invalid.append(obj_path)
invalid[obj_path] = obj
return invalid