From e468a454261014e133082f6b562f8e17e37b0669 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 17 Aug 2022 12:55:10 +0200 Subject: [PATCH] events: Add RoomMemberUnsigned with invite_room_state field --- crates/ruma-common/CHANGELOG.md | 3 + crates/ruma-common/src/events/room/member.rs | 90 ++++++++++++++++++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index 4f7f25ac..09e77390 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -28,6 +28,9 @@ Breaking changes: * Move `receipt::ReceiptType` to `events::receipt` * Make `Clone` as supertrait of `api::OutgoingRequest` * Rename `Any[Sync]RoomEvent` to `Any[Sync]TimelineEvent` +* `RoomMemberEvent` and related types now have a custom unsigned type including the + `invite_room_state` field, instead of the `StateUnsigned` type used by other state + events [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/3669 diff --git a/crates/ruma-common/src/events/room/member.rs b/crates/ruma-common/src/events/room/member.rs index f7b384f6..dc07250e 100644 --- a/crates/ruma-common/src/events/room/member.rs +++ b/crates/ruma-common/src/events/room/member.rs @@ -4,18 +4,20 @@ use std::collections::BTreeMap; +use js_int::Int; use ruma_macros::EventContent; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue as RawJsonValue; +use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue}; use crate::{ events::{ - EventContent, HasDeserializeFields, RedactContent, RedactedEventContent, - RedactedStateEventContent, StateEventContent, StateEventType, StateUnsigned, + AnyStrippedStateEvent, EventContent, HasDeserializeFields, RedactContent, + RedactedEventContent, RedactedStateEventContent, Relations, StateEventContent, + StateEventType, StateUnsigned, StateUnsignedFromParts, StaticEventContent, }, - serde::StringEnum, - OwnedMxcUri, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, PrivOwnedStr, - RoomVersionId, + serde::{CanBeEmpty, Raw, StringEnum}, + OwnedMxcUri, OwnedServerName, OwnedServerSigningKeyId, OwnedTransactionId, OwnedUserId, + PrivOwnedStr, RoomVersionId, }; mod change; @@ -45,7 +47,13 @@ pub use self::change::{Change, MembershipChange, MembershipDetails}; /// must be assumed as leave. #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[ruma_event(type = "m.room.member", kind = State, state_key_type = OwnedUserId, custom_redacted)] +#[ruma_event( + type = "m.room.member", + kind = State, + state_key_type = OwnedUserId, + unsigned_type = RoomMemberUnsigned, + custom_redacted, +)] pub struct RoomMemberEventContent { /// The avatar URL for this user, if any. /// @@ -453,6 +461,74 @@ impl StrippedRoomMemberEvent { } } +/// Extra information about a message event that is not incorporated into the event's hash. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct RoomMemberUnsigned { + /// The time in milliseconds that has elapsed since the event was sent. + /// + /// This field is generated by the local homeserver, and may be incorrect if the local time on + /// at least one of the two servers is out of sync, which can cause the age to either be + /// negative or greater than it actually is. + #[serde(skip_serializing_if = "Option::is_none")] + pub age: Option, + + /// The client-supplied transaction ID, if the client being given the event is the same one + /// which sent it. + #[serde(skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + + /// Optional previous content of the event. + #[serde(skip_serializing_if = "Option::is_none")] + pub prev_content: Option, + + /// State events to assist the receiver in identifying the room. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub invite_room_state: Vec>, + + /// [Bundled aggregations] of related child events. + /// + /// [Bundled aggregations]: https://spec.matrix.org/v1.3/client-server-api/#aggregations + #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")] + pub relations: Option, +} + +impl RoomMemberUnsigned { + /// Create a new `Unsigned` with fields set to `None`. + pub fn new() -> Self { + Self::default() + } +} + +impl CanBeEmpty for RoomMemberUnsigned { + /// Whether this unsigned data is empty (all fields are `None`). + /// + /// This method is used to determine whether to skip serializing the `unsigned` field in room + /// events. Do not use it to determine whether an incoming `unsigned` field was present - it + /// could still have been present but contained none of the known fields. + fn is_empty(&self) -> bool { + self.age.is_none() + && self.transaction_id.is_none() + && self.prev_content.is_none() + && self.invite_room_state.is_empty() + && self.relations.is_none() + } +} + +impl StateUnsignedFromParts for RoomMemberUnsigned { + fn _from_parts(event_type: &str, object: &RawJsonValue) -> serde_json::Result { + const EVENT_TYPE: &str = ::TYPE; + + if event_type != EVENT_TYPE { + return Err(serde::de::Error::custom(format!( + "expected event type of `{EVENT_TYPE}`, found `{event_type}`", + ))); + } + + from_json_str(object.get()) + } +} + #[cfg(test)] mod tests { use assert_matches::assert_matches;