From 84829e23dc6134e81b310ad7f0aa8e83821b22f4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 20 Jun 2021 18:53:30 +0200 Subject: [PATCH] events: Remove shared Relation type in favor of more per-event-type ones --- crates/ruma-events/CHANGELOG.md | 2 + crates/ruma-events/src/room.rs | 1 - crates/ruma-events/src/room/encrypted.rs | 82 +++++++++- .../relation_serde.rs | 0 crates/ruma-events/src/room/message.rs | 65 +++++++- .../src/room/message/relation_serde.rs | 142 ++++++++++++++++++ crates/ruma-events/src/room/relationships.rs | 114 -------------- crates/ruma-events/tests/room_message.rs | 11 +- 8 files changed, 282 insertions(+), 135 deletions(-) rename crates/ruma-events/src/room/{relationships => encrypted}/relation_serde.rs (100%) create mode 100644 crates/ruma-events/src/room/message/relation_serde.rs delete mode 100644 crates/ruma-events/src/room/relationships.rs diff --git a/crates/ruma-events/CHANGELOG.md b/crates/ruma-events/CHANGELOG.md index 1df8aba0..9c053d48 100644 --- a/crates/ruma-events/CHANGELOG.md +++ b/crates/ruma-events/CHANGELOG.md @@ -11,6 +11,8 @@ Breaking changes: * Remove `Custom` variant from `key::verification::accept::AcceptMethod` and `key::verification::start::StartMethod`. * Rename `relation` field in some events to `relates_to` +* All events that support relations now have their own `Relation` types (the `room::relationships` + module has been removed) Improvements: diff --git a/crates/ruma-events/src/room.rs b/crates/ruma-events/src/room.rs index c35c22d6..b6f9f47d 100644 --- a/crates/ruma-events/src/room.rs +++ b/crates/ruma-events/src/room.rs @@ -23,7 +23,6 @@ pub mod name; pub mod pinned_events; pub mod power_levels; pub mod redaction; -pub mod relationships; pub mod server_acl; pub mod third_party_invite; pub mod tombstone; diff --git a/crates/ruma-events/src/room/encrypted.rs b/crates/ruma-events/src/room/encrypted.rs index 73bad635..f2675373 100644 --- a/crates/ruma-events/src/room/encrypted.rs +++ b/crates/ruma-events/src/room/encrypted.rs @@ -5,12 +5,15 @@ use std::collections::BTreeMap; use js_int::UInt; use ruma_events_macros::EventContent; use ruma_identifiers::DeviceIdBox; +#[cfg(feature = "unstable-pre-spec")] +use ruma_identifiers::EventId; use serde::{Deserialize, Serialize}; -use crate::{ - room::relationships::{relation_serde, Relation}, - MessageEvent, -}; +#[cfg(feature = "unstable-pre-spec")] +use crate::room::message::Replacement; +use crate::{room::message::InReplyTo, MessageEvent}; + +mod relation_serde; /// An event that has been encrypted. pub type EncryptedEvent = MessageEvent; @@ -61,6 +64,73 @@ pub enum EncryptedEventScheme { MegolmV1AesSha2(MegolmV1AesSha2Content), } +/// Relationship information about an encrypted event. +/// +/// Outside of the encrypted payload to support server aggregation. +#[derive(Clone, Debug)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub enum Relation { + /// An `m.in_reply_to` relation indicating that the event is a reply to another event. + Reply { + /// Information about another message being replied to. + in_reply_to: InReplyTo, + }, + + /// An event that replaces another event. + #[cfg(feature = "unstable-pre-spec")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] + Replacement(Replacement), + + /// A reference to another event. + #[cfg(feature = "unstable-pre-spec")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] + Reference(Reference), + + /// An annotation to an event. + #[cfg(feature = "unstable-pre-spec")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] + Annotation(Annotation), +} + +/// A reference to another event. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg(feature = "unstable-pre-spec")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Reference { + /// The event we are referencing. + pub event_id: EventId, +} + +#[cfg(feature = "unstable-pre-spec")] +impl Reference { + /// Creates a new `Reference` with the given event ID. + pub fn new(event_id: EventId) -> Self { + Self { event_id } + } +} + +/// An annotation for an event. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg(feature = "unstable-pre-spec")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Annotation { + /// The event that is being annotated. + pub event_id: EventId, + + /// The annotation. + pub key: String, +} + +#[cfg(feature = "unstable-pre-spec")] +impl Annotation { + /// Creates a new `Annotation` with the given event ID and key. + pub fn new(event_id: EventId, key: String) -> Self { + Self { event_id, key } + } +} + /// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] @@ -154,8 +224,8 @@ mod tests { use ruma_serde::Raw; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; - use super::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2Content}; - use crate::room::relationships::{InReplyTo, Relation}; + use super::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2Content, Relation}; + use crate::room::message::InReplyTo; use ruma_identifiers::event_id; #[test] diff --git a/crates/ruma-events/src/room/relationships/relation_serde.rs b/crates/ruma-events/src/room/encrypted/relation_serde.rs similarity index 100% rename from crates/ruma-events/src/room/relationships/relation_serde.rs rename to crates/ruma-events/src/room/encrypted/relation_serde.rs diff --git a/crates/ruma-events/src/room/message.rs b/crates/ruma-events/src/room/message.rs index 97f30d95..fb0852ca 100644 --- a/crates/ruma-events/src/room/message.rs +++ b/crates/ruma-events/src/room/message.rs @@ -5,22 +5,20 @@ use std::borrow::Cow; use indoc::formatdoc; use js_int::UInt; use ruma_events_macros::EventContent; -use ruma_identifiers::MxcUri; #[cfg(feature = "unstable-pre-spec")] use ruma_identifiers::{DeviceIdBox, UserId}; +use ruma_identifiers::{EventId, MxcUri}; use ruma_serde::StringEnum; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value as JsonValue; -use super::{ - relationships::{relation_serde, InReplyTo, Relation}, - EncryptedFile, ImageInfo, ThumbnailInfo, -}; +use super::{EncryptedFile, ImageInfo, ThumbnailInfo}; #[cfg(feature = "unstable-pre-spec")] use crate::key::verification::VerificationMethod; mod content_serde; pub mod feedback; +mod relation_serde; type JsonObject = serde_json::Map; @@ -262,6 +260,60 @@ impl From for MessageEventContent { } } +/// Message vent relationship. +/// +/// Currently used for replies and editing (message replacement). +#[derive(Clone, Debug)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub enum Relation { + /// An `m.in_reply_to` relation indicating that the event is a reply to another event. + Reply { + /// Information about another message being replied to. + in_reply_to: InReplyTo, + }, + + /// An event that replaces another event. + #[cfg(feature = "unstable-pre-spec")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] + Replacement(Replacement), +} + +/// Information about the event a "rich reply" is replying to. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct InReplyTo { + /// The event being replied to. + pub event_id: EventId, +} + +impl InReplyTo { + /// Creates a new `InReplyTo` with the given event ID. + pub fn new(event_id: EventId) -> Self { + Self { event_id } + } +} + +/// The event this relation belongs to replaces another event. +#[derive(Clone, Debug)] +#[cfg(feature = "unstable-pre-spec")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Replacement { + /// The ID of the event being replacing. + pub event_id: EventId, + + /// New content. + pub new_content: Box, +} + +#[cfg(feature = "unstable-pre-spec")] +impl Replacement { + /// Creates a new `Replacement` with the given event ID and new content. + pub fn new(event_id: EventId, new_content: Box) -> Self { + Self { event_id, new_content } + } +} + /// The payload for an audio message. #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] @@ -1119,8 +1171,7 @@ mod tests { use ruma_identifiers::event_id; use serde_json::{from_value as from_json_value, json}; - use super::{MessageEventContent, MessageType, Relation}; - use crate::room::relationships::InReplyTo; + use super::{InReplyTo, MessageEventContent, MessageType, Relation}; #[test] fn deserialize_reply() { diff --git a/crates/ruma-events/src/room/message/relation_serde.rs b/crates/ruma-events/src/room/message/relation_serde.rs new file mode 100644 index 00000000..a918a848 --- /dev/null +++ b/crates/ruma-events/src/room/message/relation_serde.rs @@ -0,0 +1,142 @@ +#[cfg(feature = "unstable-pre-spec")] +use ruma_identifiers::EventId; +use serde::{ser::SerializeStruct as _, Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "unstable-pre-spec")] +use super::Replacement; +use super::{InReplyTo, Relation}; +#[cfg(feature = "unstable-pre-spec")] +use crate::room::message::MessageEventContent; + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Option { + if let Some(in_reply_to) = ev.relates_to.in_reply_to { + return Some(Relation::Reply { in_reply_to }); + } + + #[cfg(feature = "unstable-pre-spec")] + if let Some(relation) = ev.relates_to.relation { + let relation = match relation { + RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => { + let new_content = ev.new_content?; + Relation::Replacement(Replacement { event_id, new_content }) + } + // FIXME: Maybe we should log this, though at this point we don't even have access + // to the rel_type of the unknown relation. + RelationJsonRepr::Unknown => return None, + }; + + return Some(relation); + } + + None + } + + EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation) +} + +pub fn serialize(relation: &Option, serializer: S) -> Result +where + S: Serializer, +{ + let relation = match relation { + Some(rel) => rel, + // FIXME: If this crate ends up depending on tracing, emit a warning here. + // This code path should not be reachable due to the skip_serializing_if serde attribute + // that should be applied together with `with = "relation_serde"`. + None => return serializer.serialize_struct("NoRelation", 0)?.end(), + }; + + let json_repr = match relation { + Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { + in_reply_to: Some(in_reply_to.clone()), + ..Default::default() + }), + #[cfg(feature = "unstable-pre-spec")] + Relation::Replacement(Replacement { event_id, new_content }) => { + EventWithRelatesToJsonRepr { + relates_to: RelatesToJsonRepr { + relation: Some(RelationJsonRepr::Replacement(ReplacementJsonRepr { + event_id: event_id.clone(), + })), + ..Default::default() + }, + new_content: Some(new_content.clone()), + } + } + }; + + json_repr.serialize(serializer) +} + +#[derive(Deserialize, Serialize)] +struct EventWithRelatesToJsonRepr { + #[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")] + relates_to: RelatesToJsonRepr, + + #[cfg(feature = "unstable-pre-spec")] + #[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")] + new_content: Option>, +} + +impl EventWithRelatesToJsonRepr { + fn new(relates_to: RelatesToJsonRepr) -> Self { + Self { + relates_to, + #[cfg(feature = "unstable-pre-spec")] + new_content: None, + } + } +} + +/// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an +/// event. +#[derive(Default, Deserialize, Serialize)] +struct RelatesToJsonRepr { + #[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")] + in_reply_to: Option, + + #[cfg(feature = "unstable-pre-spec")] + #[serde(flatten, skip_serializing_if = "Option::is_none")] + relation: Option, +} + +impl RelatesToJsonRepr { + fn is_empty(&self) -> bool { + #[cfg(not(feature = "unstable-pre-spec"))] + { + self.in_reply_to.is_none() + } + + #[cfg(feature = "unstable-pre-spec")] + { + self.in_reply_to.is_none() && self.relation.is_none() + } + } +} + +/// A relation, which associates new information to an existing event. +#[derive(Clone, Deserialize, Serialize)] +#[cfg(feature = "unstable-pre-spec")] +#[serde(tag = "rel_type")] +enum RelationJsonRepr { + /// An event that replaces another event. + #[serde(rename = "m.replace")] + Replacement(ReplacementJsonRepr), + + /// An unknown relation type. + /// + /// Not available in the public API, but exists here so deserialization + /// doesn't fail with new / custom `rel_type`s. + #[serde(other)] + Unknown, +} + +#[derive(Clone, Deserialize, Serialize)] +#[cfg(feature = "unstable-pre-spec")] +struct ReplacementJsonRepr { + event_id: EventId, +} diff --git a/crates/ruma-events/src/room/relationships.rs b/crates/ruma-events/src/room/relationships.rs deleted file mode 100644 index a4775fb6..00000000 --- a/crates/ruma-events/src/room/relationships.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Types for event relationships. -//! -//! Events in Matrix can relate to one another in a couple of ways, this module adds types to parse -//! the relationship of an event if any exists. -//! -//! MSC for all the relates_to types except replies: -//! - -use ruma_identifiers::EventId; -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "unstable-pre-spec")] -use crate::room::message::MessageEventContent; - -pub(crate) mod relation_serde; - -/// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an -/// `m.room.message` or `m.room.encrypted` event. -#[derive(Clone, Debug)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub enum Relation { - /// A reference to another event. - #[cfg(feature = "unstable-pre-spec")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] - Reference(Reference), - - /// An annotation to an event. - #[cfg(feature = "unstable-pre-spec")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] - Annotation(Annotation), - - /// An event that replaces another event. - #[cfg(feature = "unstable-pre-spec")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] - Replacement(Replacement), - - /// An `m.in_reply_to` relation indicating that the event is a reply to another event. - Reply { - /// Information about another message being replied to. - in_reply_to: InReplyTo, - }, -} - -/// The event this relation belongs to replaces another event. -#[derive(Clone, Debug)] -#[cfg(feature = "unstable-pre-spec")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct Replacement { - /// The ID of the event being replacing. - pub event_id: EventId, - - /// New content. - pub new_content: Box, -} - -#[cfg(feature = "unstable-pre-spec")] -impl Replacement { - /// Creates a new `Replacement` with the given event ID and new content. - pub fn new(event_id: EventId, new_content: Box) -> Self { - Self { event_id, new_content } - } -} - -/// Information about the event a "rich reply" is replying to. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct InReplyTo { - /// The event being replied to. - pub event_id: EventId, -} - -impl InReplyTo { - /// Creates a new `InReplyTo` with the given event ID. - pub fn new(event_id: EventId) -> Self { - Self { event_id } - } -} - -/// A reference to another event. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg(feature = "unstable-pre-spec")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct Reference { - /// The event we are referencing. - pub event_id: EventId, -} - -#[cfg(feature = "unstable-pre-spec")] -impl Reference { - /// Creates a new `Reference` with the given event ID. - pub fn new(event_id: EventId) -> Self { - Self { event_id } - } -} - -/// An annotation for an event. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct Annotation { - /// The event that is being annotated. - pub event_id: EventId, - - /// The annotation. - pub key: String, -} - -impl Annotation { - /// Creates a new `Annotation` with the given event ID and key. - pub fn new(event_id: EventId, key: String) -> Self { - Self { event_id, key } - } -} diff --git a/crates/ruma-events/tests/room_message.rs b/crates/ruma-events/tests/room_message.rs index deb5aeef..5c8145fc 100644 --- a/crates/ruma-events/tests/room_message.rs +++ b/crates/ruma-events/tests/room_message.rs @@ -9,12 +9,9 @@ use ruma_events::{ key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent, }; use ruma_events::{ - room::{ - message::{ - AudioMessageEventContent, MessageEvent, MessageEventContent, MessageType, - TextMessageEventContent, - }, - relationships::{InReplyTo, Relation}, + room::message::{ + AudioMessageEventContent, InReplyTo, MessageEvent, MessageEventContent, MessageType, + Relation, TextMessageEventContent, }, Unsigned, }; @@ -253,7 +250,7 @@ fn edit_deserialization_061() { #[test] #[cfg(feature = "unstable-pre-spec")] fn edit_deserialization_future() { - use ruma_events::room::relationships::Replacement; + use ruma_events::room::message::Replacement; let ev_id = event_id!("$1598361704261elfgc:localhost"); let json_data = json!({