Serialize the synthetic join like other events

This commit is contained in:
Paul Chobert committed 2026-10-29 22:34:50 +01:00
1 parent 0c97b2ca0a
commit 450f4fb35f
7 files changed
+119 -4

No files matched your search

+1
View File
@@ -118,6 +118,7 @@ pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()>
child_module.add_function(wrap_pyfunction!(redact_event_py, m)?)?;
child_module.add_function(wrap_pyfunction!(redact_event_dict, m)?)?;
child_module.add_function(wrap_pyfunction!(serialize::serialize_events, m)?)?;
child_module.add_function(wrap_pyfunction!(serialize::serialize_synthetic_event, m)?)?;
child_module.add_function(wrap_pyfunction!(serialize::format_event_raw, m)?)?;
child_module.add_function(wrap_pyfunction!(serialize::format_event_for_client_v1, m)?)?;
child_module.add_function(wrap_pyfunction!(serialize::format_event_for_client_v2, m)?)?;
+28 -1
View File
@@ -36,7 +36,7 @@ use pyo3::{
types::{PyAnyMethods, PyDict, PyDictMethods},
Bound, PyAny, PyResult, Python,
};
use pythonize::pythonize;
use pythonize::{depythonize, pythonize};
use serde_json::{Map, Number, Value};
use crate::{
@@ -578,6 +578,33 @@ fn format_for_client_v2(d: &mut Map<String, Value>) {
}
}
/// Serialize an event that only exists as JSON, such as a synthetic event that
/// is never persisted.
///
/// Only the event format and the field allowlist from `config` apply: there is
/// no stored event to take the age, transaction ID, redaction or bundled
/// aggregations from.
#[pyfunction]
pub fn serialize_synthetic_event<'py>(
py: Python<'py>,
event: Bound<'py, PyAny>,
config: &SerializeEventConfig,
) -> PyResult<Bound<'py, PyAny>> {
let mut d: Map<String, Value> = depythonize(&event)?;
if config.as_client_event {
apply_event_format(config.event_format, &mut d);
}
if let Some(fields) = &config.event_field_allowlist {
if !fields.is_empty() {
d = only_fields(&d, fields)?;
}
}
Ok(pythonize(py, &Value::Object(d))?)
}
// Standalone versions of the client format transforms, re-exported from
// `synapse.events.utils` purely as a backwards compatibility hack: they have
// never been part of the module API and modules shouldn't be pulling them in,
+17
View File
@@ -50,6 +50,7 @@ from synapse.synapse_rust.events import (
format_event_raw,
redact_event,
serialize_events,
serialize_synthetic_event,
)
from synapse.synapse_rust.types import Requester
from synapse.types import JsonDict
@@ -410,6 +411,22 @@ class EventClientSerializer:
for event in events
]
def serialize_synthetic_event(
self, event: JsonDict, config: SerializeEventConfig
) -> JsonDict:
"""Serializes an event that only exists as JSON, such as a synthetic
event that is never persisted, applying the event format and field
allowlist from `config`.
Args:
event: The event's JSON, in federation format.
config: Event serialization config
Returns:
The serialized event
"""
return serialize_synthetic_event(event, config)
def register_add_extra_fields_to_unsigned_client_event_callback(
self, callback: ADD_EXTRA_FIELDS_TO_UNSIGNED_CLIENT_EVENT_CALLBACK
) -> None:
+2 -1
View File
@@ -50,9 +50,10 @@ def make_synthetic_join_event(
every time, e.g. that of the room's create event.
Returns:
The event, serialized for the client.
The event's JSON in federation format.
"""
return {
"room_id": room_id,
"type": EventTypes.Member,
"event_id": "-".join(
[
+5 -2
View File
@@ -655,8 +655,11 @@ class SyncRestServlet(RestServlet):
if is_announcement:
create_event = await self.store.get_create_event_for_room(room.room_id)
serialized_state.append(
make_synthetic_join_event(
room.room_id, user_id, create_event.origin_server_ts
self._event_serializer.serialize_synthetic_event(
make_synthetic_join_event(
room.room_id, user_id, create_event.origin_server_ts
),
serialize_options,
)
)
serialized_timeline = await self._event_serializer.serialize_events(
+18
View File
@@ -467,6 +467,24 @@ def serialize_events(
The serialized events, in the same order as `events`.
"""
def serialize_synthetic_event(
event: JsonDict, config: SerializeEventConfig
) -> JsonDict:
"""Serialize an event that only exists as JSON, such as a synthetic event
that is never persisted.
Only the event format and the field allowlist from `config` apply: there is
no stored event to take the age, transaction ID, redaction or bundled
aggregations from.
Args:
event: The event's JSON, in federation format.
config: The serialization config.
Returns:
The serialized event.
"""
# The standalone `format_event_*` transforms below are a backwards
# compatibility hack: they have never been part of the module API and modules
# shouldn't be pulling them in, but some in the wild import them (via
+48
View File
@@ -2592,3 +2592,51 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
),
self.bob,
)
def test_event_fields(self) -> None:
"""With an `event_fields` filter, the synthetic join is trimmed to the
requested fields like every other event.
"""
self._announce("hello")
event_fields = ["type", "content", "state_key"]
response = self._sync(self.bob_tok, filter={"event_fields": event_fields})
room = response["rooms"]["join"][self.room_id]
events = room["state"]["events"] + room["timeline"]["events"]
own_memberships = [
event
for event in events
if event["type"] == EventTypes.Member and event["state_key"] == self.bob
]
self.assertEqual(
own_memberships,
[
{
"type": EventTypes.Member,
"content": {"membership": Membership.JOIN},
"state_key": self.bob,
}
],
)
for event in events:
self.assertLessEqual(set(event), set(event_fields), event)
def test_federation_event_format(self) -> None:
"""With the `federation` event format, the synthetic join carries the
room ID like every other event.
"""
self._announce("hello")
response = self._sync(self.bob_tok, filter={"event_format": "federation"})
room = response["rooms"]["join"][self.room_id]
events = room["state"]["events"] + room["timeline"]["events"]
own_memberships = [
event
for event in events
if event["type"] == EventTypes.Member and event["state_key"] == self.bob
]
self.assertEqual(len(own_memberships), 1, own_memberships)
for event in events:
self.assertEqual(event.get("room_id"), self.room_id, event)