mirror of
https://github.com/element-hq/synapse.git
synced 2026-05-14 19:05:10 +00:00
c430c16df4
Based on #19708. This is on the path to porting the entire event class to Rust, as `event.content` will then return the new Rust class `JsonObject`. This PR adds a pure Rust `JsonObject` class that is a `Mapping` representing a json-style object. It uses `serde_json::Value` as its in-memory representation and `pythonize` for conversion when a field is looked up on the object. I'm not thrilled with the name, but couldn't think of a better one. This also adds `JsonObject` handling to the JSON serialisation functions we use, as well as to the `freeze(..)` function. Reviewable commit-by-commit.
150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
|
#
|
|
# Copyright (C) 2026 Element Creations Ltd.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# See the GNU Affero General Public License for more details:
|
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
|
|
|
from synapse.synapse_rust.events import JsonObject
|
|
from synapse.util import MutableOverlayMapping
|
|
|
|
from tests import unittest
|
|
|
|
|
|
class JsonObjectMappingTestCase(unittest.TestCase):
|
|
def test_new_and_basic_mapping_behavior(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
self.assertEqual(len(obj), 2)
|
|
self.assertTrue("a" in obj)
|
|
self.assertTrue("b" in obj)
|
|
self.assertFalse("c" in obj)
|
|
self.assertFalse(123 in obj) # type: ignore[comparison-overlap]
|
|
|
|
def test_getitem_and_key_errors(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
self.assertEqual(obj["a"], 1)
|
|
|
|
with self.assertRaises(KeyError):
|
|
_ = obj["missing"]
|
|
|
|
with self.assertRaises(KeyError):
|
|
_ = obj[10] # type: ignore[index]
|
|
|
|
def test_iter_keys_values_items(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
iterator = iter(obj)
|
|
first = next(iterator)
|
|
second = next(iterator)
|
|
self.assertCountEqual((first, second), ("a", "b"))
|
|
with self.assertRaises(StopIteration):
|
|
next(iterator)
|
|
|
|
self.assertCountEqual(list(obj.keys()), ["a", "b"])
|
|
self.assertCountEqual(list(obj.values()), [1, 2])
|
|
self.assertCountEqual(list(obj.items()), [("a", 1), ("b", 2)])
|
|
|
|
def test_keys_set_like_behavior(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
# Test 'and' operator.
|
|
self.assertEqual(obj.keys() & {"a"}, {"a"})
|
|
self.assertEqual({"a"} & obj.keys(), {"a"})
|
|
self.assertEqual(obj.keys() & {"c"}, set())
|
|
self.assertEqual({"c"} & obj.keys(), set())
|
|
|
|
# Test 'or' operator.
|
|
self.assertEqual(obj.keys() | {"a"}, {"a", "b"})
|
|
self.assertEqual({"a"} | obj.keys(), {"a", "b"})
|
|
self.assertEqual(obj.keys() | {"c"}, {"a", "b", "c"})
|
|
self.assertEqual({"c"} | obj.keys(), {"a", "b", "c"})
|
|
|
|
# Test 'xor' operator.
|
|
self.assertEqual(obj.keys() ^ {"a"}, {"b"})
|
|
self.assertEqual({"a"} ^ obj.keys(), {"b"})
|
|
self.assertEqual(obj.keys() ^ {"c"}, {"a", "b", "c"})
|
|
self.assertEqual({"c"} ^ obj.keys(), {"a", "b", "c"})
|
|
|
|
# Test 'sub' operator.
|
|
self.assertEqual(obj.keys() - {"a"}, {"b"})
|
|
self.assertEqual({"a"} - obj.keys(), set())
|
|
self.assertEqual(obj.keys() - {"c"}, {"a", "b"})
|
|
self.assertEqual({"c"} - obj.keys(), {"c"})
|
|
|
|
def test_values_view(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
values = obj.values()
|
|
|
|
self.assertEqual(len(values), 2)
|
|
self.assertCountEqual(list(values), [1, 2])
|
|
|
|
self.assertIn(1, values)
|
|
self.assertIn(2, values)
|
|
self.assertNotIn(3, values)
|
|
self.assertNotIn("a", values)
|
|
self.assertNotIn(object(), values)
|
|
|
|
# Iterating twice should yield the same values.
|
|
self.assertCountEqual(list(values), [1, 2])
|
|
|
|
def test_items_view(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
items = obj.items()
|
|
|
|
self.assertEqual(len(items), 2)
|
|
self.assertCountEqual(list(items), [("a", 1), ("b", 2)])
|
|
|
|
self.assertIn(("a", 1), items)
|
|
self.assertIn(("b", 2), items)
|
|
self.assertNotIn(("a", 2), items)
|
|
self.assertNotIn(("c", 1), items)
|
|
self.assertNotIn("a", items)
|
|
self.assertNotIn(("a", 1, "extra"), items)
|
|
|
|
# Iterating twice should yield the same items.
|
|
self.assertCountEqual(list(items), [("a", 1), ("b", 2)])
|
|
|
|
def test_get(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
self.assertEqual(obj.get("a"), 1)
|
|
self.assertEqual(obj.get("missing", "fallback"), "fallback")
|
|
self.assertEqual(obj.get(5, "fallback"), "fallback") # type: ignore[call-overload]
|
|
|
|
def test_eq(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
self.assertEqual(obj, {"a": 1, "b": 2})
|
|
self.assertNotEqual(obj, {"a": 1})
|
|
self.assertNotEqual(obj, ["a", "b"])
|
|
|
|
def test_str_and_repr(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
self.assertEqual(str(obj), r'{"a":1,"b":2}')
|
|
self.assertEqual(repr(obj), r'JsonObject({"a":1,"b":2})')
|
|
|
|
def test_json_object_constructor(self) -> None:
|
|
obj = JsonObject({"a": 1, "b": 2})
|
|
|
|
# Passing in an existing JsonObject should work.
|
|
obj2 = JsonObject(obj)
|
|
self.assertEqual(obj2, {"a": 1, "b": 2})
|
|
|
|
# Other mapping types should also work.
|
|
obj3 = JsonObject(MutableOverlayMapping({"a": 1, "b": 2}))
|
|
self.assertEqual(obj3, {"a": 1, "b": 2})
|
|
|
|
# Test that passing a non-mapping raises a TypeError.
|
|
with self.assertRaises(TypeError):
|
|
JsonObject(123) # type: ignore[arg-type]
|