From 1f91a7927056f0c37da0157c1ac88c24f13cfb2f Mon Sep 17 00:00:00 2001 From: Nicolas Werner <nicolas.werner@hotmail.de> Date: Sun, 13 Jun 2021 20:25:26 +0200 Subject: [PATCH] Add space types --- CMakeLists.txt | 1 + include/mtx/events/collections.hpp | 15 ++ include/mtx/events/create.hpp | 9 + include/mtx/events/event_type.hpp | 6 + include/mtx/events/spaces.hpp | 81 +++++++ include/mtxclient/http/client.hpp | 2 + lib/http/client.cpp | 2 + lib/structs/events.cpp | 12 + lib/structs/events/collections.cpp | 12 + lib/structs/events/create.cpp | 5 + lib/structs/events/spaces.cpp | 73 ++++++ lib/structs/responses/common.cpp | 56 +++++ tests/events.cpp | 363 +++++++++++++++++++++++++++++ 13 files changed, 637 insertions(+) create mode 100644 include/mtx/events/spaces.hpp create mode 100644 lib/structs/events/spaces.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 55ebac0d9..87579e451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ target_sources(matrix_client lib/structs/events/presence.cpp lib/structs/events/reaction.cpp lib/structs/events/redaction.cpp + lib/structs/events/spaces.cpp lib/structs/events/tag.cpp lib/structs/events/tombstone.cpp lib/structs/events/topic.cpp diff --git a/include/mtx/events/collections.hpp b/include/mtx/events/collections.hpp index df4a17ee5..efa112f22 100644 --- a/include/mtx/events/collections.hpp +++ b/include/mtx/events/collections.hpp @@ -27,6 +27,7 @@ #include "mtx/events/presence.hpp" #include "mtx/events/reaction.hpp" #include "mtx/events/redaction.hpp" +#include "mtx/events/spaces.hpp" #include "mtx/events/tag.hpp" #include "mtx/events/tombstone.hpp" #include "mtx/events/topic.hpp" @@ -94,6 +95,8 @@ using StateEvents = std::variant<events::StateEvent<states::Aliases>, events::StateEvent<states::Name>, events::StateEvent<states::PinnedEvents>, events::StateEvent<states::PowerLevels>, + events::StateEvent<states::space::Child>, + events::StateEvent<states::space::Parent>, events::StateEvent<states::Tombstone>, events::StateEvent<states::Topic>, events::StateEvent<msgs::Redacted>, @@ -112,6 +115,8 @@ using StrippedEvents = std::variant<events::StrippedEvent<states::Aliases>, events::StrippedEvent<states::Name>, events::StrippedEvent<states::PinnedEvents>, events::StrippedEvent<states::PowerLevels>, + events::StrippedEvent<states::space::Child>, + events::StrippedEvent<states::space::Parent>, events::StrippedEvent<states::Tombstone>, events::StrippedEvent<states::Topic>, events::StrippedEvent<Unknown>>; @@ -130,6 +135,8 @@ using TimelineEvents = std::variant<events::StateEvent<states::Aliases>, events::StateEvent<states::Name>, events::StateEvent<states::PinnedEvents>, events::StateEvent<states::PowerLevels>, + events::StateEvent<states::space::Child>, + events::StateEvent<states::space::Parent>, events::StateEvent<states::Tombstone>, events::StateEvent<states::Topic>, events::StateEvent<msc2545::ImagePack>, @@ -262,6 +269,14 @@ constexpr inline EventType state_content_to_type<mtx::events::state::PowerLevels template<> constexpr inline EventType state_content_to_type<mtx::events::state::Tombstone> = EventType::RoomTombstone; + +template<> +constexpr inline EventType state_content_to_type<mtx::events::state::space::Child> = + EventType::SpaceChild; +template<> +constexpr inline EventType state_content_to_type<mtx::events::state::space::Parent> = + EventType::SpaceParent; + template<> constexpr inline EventType state_content_to_type<mtx::events::state::Topic> = EventType::RoomTopic; template<> diff --git a/include/mtx/events/create.hpp b/include/mtx/events/create.hpp index ff0422bfc..f69ad8106 100644 --- a/include/mtx/events/create.hpp +++ b/include/mtx/events/create.hpp @@ -23,6 +23,12 @@ struct PreviousRoom std::string event_id; }; +//! Definitions of different room types. +namespace room_type { +//! The room type for a space. +constexpr std::string_view space = "m.space"; +} + //! Content of the `m.room.create` event. // //! This is the first event in a room and cannot be changed. @@ -32,6 +38,9 @@ struct Create //! The `user_id` of the room creator. This is set by the homeserver. std::string creator; + //! The room type, for example `m.space` for spaces. + std::optional<std::string> type; + //! Whether users on other servers can join this room. //! Defaults to **true** if key does not exist. bool federate = true; diff --git a/include/mtx/events/event_type.hpp b/include/mtx/events/event_type.hpp index e602a18cb..0dd5b614d 100644 --- a/include/mtx/events/event_type.hpp +++ b/include/mtx/events/event_type.hpp @@ -82,6 +82,12 @@ enum class EventType Presence, // m.push_rules PushRules, + + // m.space.child + SpaceChild, + // m.space.parent + SpaceParent, + // m.call.invite CallInvite, // m.call.candidates diff --git a/include/mtx/events/spaces.hpp b/include/mtx/events/spaces.hpp new file mode 100644 index 000000000..e80f00b5b --- /dev/null +++ b/include/mtx/events/spaces.hpp @@ -0,0 +1,81 @@ +#pragma once + +/// @file +/// @brief Space related events to make child and parent relations + +#include <optional> + +#if __has_include(<nlohmann/json_fwd.hpp>) +#include <nlohmann/json_fwd.hpp> +#else +#include <nlohmann/json.hpp> +#endif + +namespace mtx { +namespace events { +namespace state { +//! Namespace for space related state events. +namespace space { +/// @brief Event to point at a parent space from a room or space +/// +/// To avoid abuse where a room admin falsely claims that a room is part of a space that it +/// should not be, clients could ignore such m.space.parent events unless either (a) there +/// is a corresponding m.space.child event in the claimed parent, or (b) the sender of the +/// m.space.child event has a sufficient power-level to send such an m.space.child event in +/// the parent. (It is not necessarily required that that user currently be a member of the +/// parent room - only the m.room.power_levels event is inspected.) +struct Parent +{ + /// @brief Servers to join the parent space via. + /// + /// Needs to contain at least one server. + std::optional<std::vector<std::string>> via; + /// @brief Determines whether this is the main parent for the space. + /// + /// When a user joins a room with a canonical parent, clients may switch to view the room in + /// the context of that space, peeking into it in order to find other rooms and group them + /// together. In practice, well behaved rooms should only have one canonical parent, but + /// given this is not enforced: if multiple are present the client should select the one + /// with the lowest room ID, as determined via a lexicographic ordering of the Unicode + /// code-points. + bool canonical = false; +}; + +void +from_json(const nlohmann::json &obj, Parent &child); + +void +to_json(nlohmann::json &obj, const Parent &child); + +/// @brief Event to point at a child room or space from a parent space +/// +/// The admins of a space can advertise rooms and subspaces for their space by setting m.space.child +/// state events. The state_key is the ID of a child room or space, and the content must contain a +/// via key which gives a list of candidate servers that can be used to join the room. +struct Child +{ + /// @brief Servers to join the child room/space via. + /// + /// Needs to contain at least one server. + std::optional<std::vector<std::string>> via; + /// @brief A string which is used to provide a default ordering of siblings in the room + /// list. + /// + /// Rooms are sorted based on a lexicographic ordering of the Unicode codepoints of the + /// characters in order values. Rooms with no order come last, in ascending numeric order of + /// the origin_server_ts of their m.room.create events, or ascending lexicographic order of + /// their room_ids in case of equal origin_server_ts. orders which are not strings, or do + /// not consist solely of ascii characters in the range \x20 (space) to \x7E (~), or consist + /// of more than 50 characters, are forbidden and the field should be ignored if received. + std::optional<std::string> order; +}; + +void +from_json(const nlohmann::json &obj, Child &child); + +void +to_json(nlohmann::json &obj, const Child &child); +} +} +} +} diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp index 5bb888ea4..0b1e8a4d2 100644 --- a/include/mtxclient/http/client.hpp +++ b/include/mtxclient/http/client.hpp @@ -698,6 +698,8 @@ MTXCLIENT_SEND_STATE_EVENT_FWD(state::PinnedEvents) MTXCLIENT_SEND_STATE_EVENT_FWD(state::PowerLevels) MTXCLIENT_SEND_STATE_EVENT_FWD(state::Tombstone) MTXCLIENT_SEND_STATE_EVENT_FWD(state::Topic) +MTXCLIENT_SEND_STATE_EVENT_FWD(state::space::Child) +MTXCLIENT_SEND_STATE_EVENT_FWD(state::space::Parent) MTXCLIENT_SEND_STATE_EVENT_FWD(msc2545::ImagePack) #define MTXCLIENT_SEND_ROOM_MESSAGE_FWD(Content) \ diff --git a/lib/http/client.cpp b/lib/http/client.cpp index 0d7d187e0..adc10519b 100644 --- a/lib/http/client.cpp +++ b/lib/http/client.cpp @@ -1449,6 +1449,8 @@ MTXCLIENT_SEND_STATE_EVENT(state::PinnedEvents) MTXCLIENT_SEND_STATE_EVENT(state::PowerLevels) MTXCLIENT_SEND_STATE_EVENT(state::Tombstone) MTXCLIENT_SEND_STATE_EVENT(state::Topic) +MTXCLIENT_SEND_STATE_EVENT(state::space::Child) +MTXCLIENT_SEND_STATE_EVENT(state::space::Parent) MTXCLIENT_SEND_STATE_EVENT(msc2545::ImagePack) #define MTXCLIENT_SEND_ROOM_MESSAGE(Content) \ diff --git a/lib/structs/events.cpp b/lib/structs/events.cpp index 090f50d84..e1aa5240f 100644 --- a/lib/structs/events.cpp +++ b/lib/structs/events.cpp @@ -72,6 +72,12 @@ getEventType(const std::string &type) return EventType::RoomTombstone; else if (type == "m.sticker") return EventType::Sticker; + + else if (type == "m.space.child") + return EventType::SpaceChild; + else if (type == "m.space.parent") + return EventType::SpaceParent; + else if (type == "m.tag") return EventType::Tag; else if (type == "m.presence") @@ -174,6 +180,12 @@ to_string(EventType type) return "m.room.tombstone"; case EventType::Sticker: return "m.sticker"; + + case EventType::SpaceChild: + return "m.space.child"; + case EventType::SpaceParent: + return "m.space.parent"; + case EventType::Tag: return "m.tag"; case EventType::Presence: diff --git a/lib/structs/events/collections.cpp b/lib/structs/events/collections.cpp index bf22a3792..2f8c211f7 100644 --- a/lib/structs/events/collections.cpp +++ b/lib/structs/events/collections.cpp @@ -25,6 +25,8 @@ MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::PinnedEvents) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::PowerLevels) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::Tombstone) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::Topic) +MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::space::Child) +MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, states::space::Parent) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, msgs::Redacted) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, msc2545::ImagePack) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StateEvent, Unknown) @@ -68,6 +70,8 @@ MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::PinnedEvents MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::PowerLevels) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::Tombstone) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::Topic) +MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::space::Child) +MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, states::space::Parent) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::StrippedEvent, Unknown) MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::Encrypted) @@ -185,6 +189,14 @@ from_json(const json &obj, TimelineEvent &e) e.data = events::StateEvent<Topic>(obj); break; } + case events::EventType::SpaceChild: { + e.data = events::StateEvent<space::Child>(obj); + break; + } + case events::EventType::SpaceParent: { + e.data = events::StateEvent<space::Parent>(obj); + break; + } case events::EventType::ImagePackInRoom: { e.data = events::StateEvent<msc2545::ImagePack>(obj); break; diff --git a/lib/structs/events/create.cpp b/lib/structs/events/create.cpp index 6cf7861d3..32237acf4 100644 --- a/lib/structs/events/create.cpp +++ b/lib/structs/events/create.cpp @@ -28,6 +28,9 @@ from_json(const json &obj, Create &create) { create.creator = obj.at("creator"); + if (obj.contains("type") && obj.at("type").is_string()) + create.type = obj.at("type"); + if (obj.find("m.federate") != obj.end()) create.federate = obj.at("m.federate").get<bool>(); @@ -51,6 +54,8 @@ to_json(json &obj, const Create &create) else obj["room_version"] = create.room_version; + if (create.type) + obj["type"] = create.type.value(); if (create.predecessor) obj["predecessor"] = *create.predecessor; } diff --git a/lib/structs/events/spaces.cpp b/lib/structs/events/spaces.cpp new file mode 100644 index 000000000..ad4331e14 --- /dev/null +++ b/lib/structs/events/spaces.cpp @@ -0,0 +1,73 @@ +#include "mtx/events/spaces.hpp" + +#include <string> + +#include <nlohmann/json.hpp> + +namespace mtx { +namespace events { +namespace state { +namespace space { + +void +from_json(const nlohmann::json &obj, Parent &parent) +{ + if (obj.contains("canonical") && obj.at("canonical").is_boolean()) + parent.canonical = obj.at("canonical").get<bool>(); + if (obj.contains("via") && obj.at("via").is_array() && !obj.at("via").empty()) + parent.via = obj.at("via").get<std::vector<std::string>>(); +} + +void +to_json(nlohmann::json &obj, const Parent &parent) +{ + // event without via is invalid. + if (!parent.via.has_value() || parent.via.value().empty()) + return; + + obj["via"] = parent.via.value(); + + if (parent.canonical) + obj["canonical"] = true; +} + +static bool +is_valid_order_str(std::string_view order) +{ + if (order.size() > 50) + return false; + + for (auto c : order) + if (c < '\x20' || c > '\x7E') + return false; + + return true; +} + +void +from_json(const nlohmann::json &obj, Child &child) +{ + if (obj.contains("via") && obj.at("via").is_array() && !obj.at("via").empty()) + child.via = obj.at("via").get<std::vector<std::string>>(); + + if (obj.contains("order") && obj.at("order").is_string() && + is_valid_order_str(obj.at("order").get<std::string>())) + child.order = obj.at("order").get<std::string>(); +} + +void +to_json(nlohmann::json &obj, const Child &child) +{ + // event without via is invalid. + if (!child.via.has_value() || child.via.value().empty()) + return; + + obj["via"] = child.via.value(); + + if (child.order && is_valid_order_str(child.order.value())) + obj["order"] = child.order.value(); +} +} +} // namespace state +} // namespace events +} // namespace mtx diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp index 8a9804618..29b7e629d 100644 --- a/lib/structs/responses/common.cpp +++ b/lib/structs/responses/common.cpp @@ -176,6 +176,8 @@ parse_room_account_data_events( case events::EventType::RoomRedaction: case events::EventType::RoomTombstone: case events::EventType::RoomTopic: + case events::EventType::SpaceChild: + case events::EventType::SpaceParent: case events::EventType::Sticker: case events::EventType::CallInvite: case events::EventType::CallCandidates: @@ -354,6 +356,24 @@ parse_timeline_events(const json &events, break; } + case events::EventType::SpaceChild: { + try { + container.emplace_back(events::StateEvent<space::Child>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } + case events::EventType::SpaceParent: { + try { + container.emplace_back(events::StateEvent<space::Parent>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } case events::EventType::ImagePackInRoom: { try { container.emplace_back( @@ -930,6 +950,24 @@ parse_state_events(const json &events, break; } + case events::EventType::SpaceChild: { + try { + container.emplace_back(events::StateEvent<space::Child>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } + case events::EventType::SpaceParent: { + try { + container.emplace_back(events::StateEvent<space::Parent>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } case events::EventType::ImagePackInRoom: { try { container.emplace_back( @@ -1106,6 +1144,24 @@ parse_stripped_events(const json &events, break; } + case events::EventType::SpaceChild: { + try { + container.emplace_back(events::StrippedEvent<space::Child>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } + case events::EventType::SpaceParent: { + try { + container.emplace_back(events::StrippedEvent<space::Parent>(e)); + } catch (json::exception &err) { + log_error(err, e); + } + + break; + } case events::EventType::Unsupported: { try { container.emplace_back(events::StrippedEvent<events::Unknown>(e)); diff --git a/tests/events.cpp b/tests/events.cpp index 09c25ea70..2db2e7a00 100644 --- a/tests/events.cpp +++ b/tests/events.cpp @@ -108,6 +108,8 @@ TEST(Events, Conversions) EXPECT_EQ("m.room.tombstone", ns::to_string(ns::EventType::RoomTombstone)); EXPECT_EQ("m.room.redaction", ns::to_string(ns::EventType::RoomRedaction)); EXPECT_EQ("m.room.pinned_events", ns::to_string(ns::EventType::RoomPinnedEvents)); + EXPECT_EQ("m.space.child", ns::to_string(ns::EventType::SpaceChild)); + EXPECT_EQ("m.space.parent", ns::to_string(ns::EventType::SpaceParent)); EXPECT_EQ("m.tag", ns::to_string(ns::EventType::Tag)); } @@ -276,6 +278,71 @@ TEST(StateEvents, Create) EXPECT_EQ(event.content.predecessor->event_id, "$something:example.org"); } +TEST(StateEvents, CreateWithType) +{ + json data = R"({ + "origin_server_ts": 1506761923948, + "sender": "@mujx:matrix.org", + "event_id": "$15067619231414398jhvQC:matrix.org", + "unsigned": { + "age": 3715756343 + }, + "state_key": "", + "content": { + "creator": "@mujx:matrix.org", + "type": "m.space" + }, + "type": "m.room.create" + })"_json; + + ns::StateEvent<ns::state::Create> event = data; + + EXPECT_EQ(event.type, ns::EventType::RoomCreate); + EXPECT_EQ(event.event_id, "$15067619231414398jhvQC:matrix.org"); + EXPECT_EQ(event.sender, "@mujx:matrix.org"); + EXPECT_EQ(event.unsigned_data.age, 3715756343L); + EXPECT_EQ(event.origin_server_ts, 1506761923948L); + EXPECT_EQ(event.state_key, ""); + EXPECT_EQ(event.content.creator, "@mujx:matrix.org"); + EXPECT_TRUE(event.content.type.has_value()); + EXPECT_EQ(event.content.type.value(), ns::state::room_type::space); + + json example_from_spec = R"({ + "content": { + "creator": "@example:example.org", + "m.federate": true, + "predecessor": { + "event_id": "$something:example.org", + "room_id": "!oldroom:example.org" + }, + "room_version": "1" + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 1432735824653, + "room_id": "!jEsUZKDJdhlrceRyVU:example.org", + "sender": "@example:example.org", + "state_key": "", + "type": "m.room.create", + "unsigned": { + "age": 1234 + } + })"_json; + + event = example_from_spec; + + EXPECT_EQ(event.type, ns::EventType::RoomCreate); + EXPECT_EQ(event.event_id, "$143273582443PhrSn:example.org"); + EXPECT_EQ(event.sender, "@example:example.org"); + EXPECT_EQ(event.unsigned_data.age, 1234); + EXPECT_EQ(event.origin_server_ts, 1432735824653L); + EXPECT_EQ(event.state_key, ""); + EXPECT_EQ(event.content.creator, "@example:example.org"); + EXPECT_EQ(event.content.federate, true); + EXPECT_EQ(event.content.room_version, "1"); + EXPECT_EQ(event.content.predecessor->room_id, "!oldroom:example.org"); + EXPECT_EQ(event.content.predecessor->event_id, "$something:example.org"); +} + TEST(StateEvents, GuestAccess) { json data = R"({ @@ -717,6 +784,302 @@ TEST(StateEvents, Topic) EXPECT_EQ(event.content.topic, "Test topic"); } +TEST(StateEvents, SpaceChild) +{ + json data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!abcd:example.com", + "content": { + "via": ["example.com", "test.org"] + } +} + )"_json; + + ns::StateEvent<ns::state::space::Child> event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceChild); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!abcd:example.com"); + ASSERT_TRUE(event.content.via.has_value()); + std::vector<std::string> via{"example.com", "test.org"}; + EXPECT_EQ(event.content.via, via); + EXPECT_FALSE(event.content.order.has_value()); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "via": ["example.com"], + "order": "abcd" + } +} + )"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceChild); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!efgh:example.com"); + ASSERT_TRUE(event.content.via.has_value()); + std::vector<std::string> via2{"example.com"}; + EXPECT_EQ(event.content.via, via2); + ASSERT_TRUE(event.content.order.has_value()); + ASSERT_EQ(event.content.order, "abcd"); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!jklm:example.com", + "content": {} +} + )"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceChild); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!jklm:example.com"); + ASSERT_FALSE(event.content.via.has_value()); + ASSERT_FALSE(event.content.order.has_value()); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "via": ["example.com"], + "order": "01234567890123456789012345678901234567890123456789_" + } +} + )"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceChild); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!efgh:example.com"); + EXPECT_TRUE(event.content.via.has_value()); + ASSERT_FALSE(event.content.order.has_value()); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "via": [], + "order": "01234567890123456789012345678901234567890123456789_" + } +} + )"_json; + + event = data; + + EXPECT_FALSE(event.content.via.has_value()); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "via": 5, + "order": "01234567890123456789012345678901234567890123456789_" + } +} + )"_json; + + event = data; + + EXPECT_FALSE(event.content.via.has_value()); + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "via": null, + "order": "01234567890123456789012345678901234567890123456789_" + } +} + )"_json; + + event = data; + + EXPECT_FALSE(event.content.via.has_value()); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.child", + "state_key": "!efgh:example.com", + "content": { + "order": "01234567890123456789012345678901234567890123456789_" + } +} + )"_json; + + event = data; + + EXPECT_FALSE(event.content.via.has_value()); +} +TEST(StateEvents, SpaceParent) +{ + json data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": ["example.com"], + "canonical": true + } + })"_json; + + ns::StateEvent<ns::state::space::Parent> event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + ASSERT_TRUE(event.content.via.has_value()); + std::vector<std::string> via{"example.com"}; + EXPECT_EQ(event.content.via, via); + EXPECT_TRUE(event.content.canonical); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": ["example.org"] + } + })"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + EXPECT_TRUE(event.content.via.has_value()); + EXPECT_FALSE(event.content.canonical); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": [], + "canonical": true + } + })"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + EXPECT_FALSE(event.content.via.has_value()); + EXPECT_TRUE(event.content.canonical); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": null, + "canonical": true + } + })"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + EXPECT_FALSE(event.content.via.has_value()); + EXPECT_TRUE(event.content.canonical); + + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": 5, + "canonical": true + } + })"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + EXPECT_FALSE(event.content.via.has_value()); + EXPECT_TRUE(event.content.canonical); + data = R"({ + "origin_server_ts": 1510476064445, + "sender": "@nheko_test:matrix.org", + "event_id": "$15104760642668662QICBu:matrix.org", + "type": "m.space.parent", + "state_key": "!space:example.com", + "content": { + "via": "adjsa", + "canonical": true + } + })"_json; + + event = data; + + EXPECT_EQ(event.type, ns::EventType::SpaceParent); + EXPECT_EQ(event.event_id, "$15104760642668662QICBu:matrix.org"); + EXPECT_EQ(event.sender, "@nheko_test:matrix.org"); + EXPECT_EQ(event.origin_server_ts, 1510476064445); + EXPECT_EQ(event.state_key, "!space:example.com"); + EXPECT_FALSE(event.content.via.has_value()); + EXPECT_TRUE(event.content.canonical); +} + TEST(StateEvents, ImagePack) { json data = R"({ -- GitLab