Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Fix serialisation errors when using third-party event rules. #8678

Merged
merged 2 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8678.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `Object of type frozendict is not JSON serializable` exceptions when using third-party event rules.
5 changes: 2 additions & 3 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
from synapse.storage.state import StateFilter
from synapse.types import Requester, RoomAlias, StreamToken, UserID, create_requester
from synapse.util import json_decoder
from synapse.util import json_decoder, json_encoder
from synapse.util.async_helpers import Linearizer
from synapse.util.frozenutils import frozendict_json_encoder
from synapse.util.metrics import measure_func
from synapse.visibility import filter_events_for_client

Expand Down Expand Up @@ -928,7 +927,7 @@ async def handle_new_client_event(

# Ensure that we can round trip before trying to persist in db
try:
dump = frozendict_json_encoder.encode(event.content)
dump = json_encoder.encode(event.content)
json_decoder.decode(dump)
except Exception:
logger.exception("Failed to encode content: %r", event.content)
Expand Down
4 changes: 1 addition & 3 deletions synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
from twisted.web.static import File, NoRangeStaticProducer
from twisted.web.util import redirectTo

import synapse.events
import synapse.metrics
from synapse.api.errors import (
CodeMessageException,
Codes,
Expand Down Expand Up @@ -620,7 +618,7 @@ def respond_with_json(
if pretty_print:
encoder = iterencode_pretty_printed_json
else:
if canonical_json or synapse.events.USE_FROZEN_DICTS:
if canonical_json:
encoder = iterencode_canonical_json
else:
encoder = _encode_json_bytes
Expand Down
6 changes: 3 additions & 3 deletions synapse/storage/databases/main/censor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from synapse.storage.database import DatabasePool
from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
from synapse.storage.databases.main.events_worker import EventsWorkerStore
from synapse.util.frozenutils import frozendict_json_encoder
from synapse.util import json_encoder

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand Down Expand Up @@ -104,7 +104,7 @@ async def _censor_redactions(self):
and original_event.internal_metadata.is_redacted()
):
# Redaction was allowed
pruned_json = frozendict_json_encoder.encode(
pruned_json = json_encoder.encode(
prune_event_dict(
original_event.room_version, original_event.get_dict()
)
Expand Down Expand Up @@ -170,7 +170,7 @@ def delete_expired_event_txn(txn):
return

# Prune the event's dict then convert it to JSON.
pruned_json = frozendict_json_encoder.encode(
pruned_json = json_encoder.encode(
prune_event_dict(event.room_version, event.get_dict())
)

Expand Down
10 changes: 4 additions & 6 deletions synapse/storage/databases/main/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from synapse.storage.databases.main.search import SearchEntry
from synapse.storage.util.id_generators import MultiWriterIdGenerator
from synapse.types import StateMap, get_domain_from_id
from synapse.util.frozenutils import frozendict_json_encoder
from synapse.util import json_encoder
from synapse.util.iterutils import batch_iter

if TYPE_CHECKING:
Expand Down Expand Up @@ -769,9 +769,7 @@ def _update_outliers_txn(self, txn, events_and_contexts):
logger.exception("")
raise

metadata_json = frozendict_json_encoder.encode(
event.internal_metadata.get_dict()
)
metadata_json = json_encoder.encode(event.internal_metadata.get_dict())

sql = "UPDATE event_json SET internal_metadata = ? WHERE event_id = ?"
txn.execute(sql, (metadata_json, event.event_id))
Expand Down Expand Up @@ -826,10 +824,10 @@ def event_dict(event):
{
"event_id": event.event_id,
"room_id": event.room_id,
"internal_metadata": frozendict_json_encoder.encode(
"internal_metadata": json_encoder.encode(
event.internal_metadata.get_dict()
),
"json": frozendict_json_encoder.encode(event_dict(event)),
"json": json_encoder.encode(event_dict(event)),
"format_version": event.format_version,
}
for event, _ in events_and_contexts
Expand Down
24 changes: 21 additions & 3 deletions synapse/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import re

import attr
from frozendict import frozendict

from twisted.internet import defer, task

Expand All @@ -31,9 +32,26 @@ def _reject_invalid_json(val):
raise ValueError("Invalid JSON value: '%s'" % val)


# Create a custom encoder to reduce the whitespace produced by JSON encoding and
# ensure that valid JSON is produced.
json_encoder = json.JSONEncoder(allow_nan=False, separators=(",", ":"))
def _handle_frozendict(obj):
"""Helper for json_encoder. Makes frozendicts serializable by returning
the underlying dict
"""
if type(obj) is frozendict:
# fishing the protected dict out of the object is a bit nasty,
# but we don't really want the overhead of copying the dict.
return obj._dict
raise TypeError(
"Object of type %s is not JSON serializable" % obj.__class__.__name__
)


# A custom JSON encoder which:
# * handles frozendicts
# * produces valid JSON (no NaNs etc)
# * reduces redundant whitespace
json_encoder = json.JSONEncoder(
allow_nan=False, separators=(",", ":"), default=_handle_frozendict
)

# Create a custom decoder to reject Python extensions to JSON.
json_decoder = json.JSONDecoder(parse_constant=_reject_invalid_json)
Expand Down
22 changes: 0 additions & 22 deletions synapse/util/frozenutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json

from frozendict import frozendict


Expand Down Expand Up @@ -49,23 +47,3 @@ def unfreeze(o):
pass

return o


def _handle_frozendict(obj):
"""Helper for EventEncoder. Makes frozendicts serializable by returning
the underlying dict
"""
if type(obj) is frozendict:
# fishing the protected dict out of the object is a bit nasty,
# but we don't really want the overhead of copying the dict.
return obj._dict
raise TypeError(
"Object of type %s is not JSON serializable" % obj.__class__.__name__
)


# A JSONEncoder which is capable of encoding frozendicts without barfing.
# Additionally reduce the whitespace produced by JSON encoding.
frozendict_json_encoder = json.JSONEncoder(
allow_nan=False, separators=(",", ":"), default=_handle_frozendict,
)