Implement MSC3925

… without a feature flag because it is a breaking change, so the feature
would have to be non-additive, which is a no-go.
Limited support for the previous replacement format is kept.
This commit is contained in:
Jonas Platte 2023-03-15 13:08:40 +01:00
parent 19d44489c3
commit e61e0ccf6b
No known key found for this signature in database
GPG Key ID: AAA7A61F696C3E0C
6 changed files with 86 additions and 45 deletions

View File

@ -74,7 +74,7 @@ impl StaticStateEventContent for CustomStateEventContent {
// Like `StateUnsigned`, but without `prev_content`. // Like `StateUnsigned`, but without `prev_content`.
// We don't care about `prev_content` since we'd only store the event type that is the same // We don't care about `prev_content` since we'd only store the event type that is the same
// as in the content. // as in the content.
type Unsigned = MessageLikeUnsigned; type Unsigned = MessageLikeUnsigned<CustomMessageLikeEventContent>;
type PossiblyRedacted = Self; type PossiblyRedacted = Self;
} }
impl PossiblyRedactedStateEventContent for CustomStateEventContent { impl PossiblyRedactedStateEventContent for CustomStateEventContent {

View File

@ -119,7 +119,7 @@ pub struct OriginalMessageLikeEvent<C: MessageLikeEventContent> {
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
/// Additional key-value pairs not signed by the homeserver. /// Additional key-value pairs not signed by the homeserver.
pub unsigned: MessageLikeUnsigned, pub unsigned: MessageLikeUnsigned<C>,
} }
/// An unredacted message-like event without a `room_id`. /// An unredacted message-like event without a `room_id`.
@ -141,7 +141,7 @@ pub struct OriginalSyncMessageLikeEvent<C: MessageLikeEventContent> {
pub origin_server_ts: MilliSecondsSinceUnixEpoch, pub origin_server_ts: MilliSecondsSinceUnixEpoch,
/// Additional key-value pairs not signed by the homeserver. /// Additional key-value pairs not signed by the homeserver.
pub unsigned: MessageLikeUnsigned, pub unsigned: MessageLikeUnsigned<C>,
} }
/// A redacted message-like event. /// A redacted message-like event.

View File

@ -10,9 +10,11 @@ use serde::{Deserialize, Serialize};
use super::AnyMessageLikeEvent; use super::AnyMessageLikeEvent;
use crate::{ use crate::{
serde::{Raw, StringEnum}, serde::{Raw, StringEnum},
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, PrivOwnedStr, OwnedEventId, PrivOwnedStr,
}; };
mod rel_serde;
/// Information about the event a [rich reply] is replying to. /// Information about the event a [rich reply] is replying to.
/// ///
/// [rich reply]: https://spec.matrix.org/latest/client-server-api/#rich-replies /// [rich reply]: https://spec.matrix.org/latest/client-server-api/#rich-replies
@ -58,31 +60,6 @@ impl Annotation {
} }
} }
/// A bundled replacement.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledReplacement {
/// The ID of the replacing event.
pub event_id: OwnedEventId,
/// The user ID of the sender of the latest replacement.
pub sender: OwnedUserId,
/// Timestamp in milliseconds on originating homeserver when the latest replacement was sent.
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
}
impl BundledReplacement {
/// Creates a new `BundledReplacement` with the given event ID, sender and timestamp.
pub fn new(
event_id: OwnedEventId,
sender: OwnedUserId,
origin_server_ts: MilliSecondsSinceUnixEpoch,
) -> Self {
Self { event_id, sender, origin_server_ts }
}
}
/// The content of a [replacement] relation. /// The content of a [replacement] relation.
/// ///
/// [replacement]: https://spec.matrix.org/latest/client-server-api/#event-replacements /// [replacement]: https://spec.matrix.org/latest/client-server-api/#event-replacements
@ -221,12 +198,18 @@ impl ReferenceChunk {
/// [Bundled aggregations] of related child events of a message-like event. /// [Bundled aggregations] of related child events of a message-like event.
/// ///
/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations /// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledMessageLikeRelations { pub struct BundledMessageLikeRelations<E> {
/// Replacement relation. /// Replacement relation.
#[serde(rename = "m.replace", skip_serializing_if = "Option::is_none")] #[serde(rename = "m.replace", skip_serializing_if = "Option::is_none")]
pub replace: Option<Box<BundledReplacement>>, pub replace: Option<Box<E>>,
/// Set when the above fails to deserialize.
///
/// Intentionally *not* public.
#[serde(skip_serializing)]
has_invalid_replacement: bool,
/// Thread relation. /// Thread relation.
#[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")] #[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
@ -237,10 +220,20 @@ pub struct BundledMessageLikeRelations {
pub reference: Option<Box<ReferenceChunk>>, pub reference: Option<Box<ReferenceChunk>>,
} }
impl BundledMessageLikeRelations { impl<E> BundledMessageLikeRelations<E> {
/// Creates a new empty `BundledMessageLikeRelations`. /// Creates a new empty `BundledMessageLikeRelations`.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { replace: None, thread: None, reference: None } Self { replace: None, has_invalid_replacement: false, thread: None, reference: None }
}
/// Whether this bundle contains a replacement relation.
///
/// This may be `true` even if the `replace` field is `None`, because Matrix versions prior to
/// 1.7 had a different incompatible format for bundled replacements. Use this method to check
/// whether an event was replaced. If this returns `true` but `replace` is `None`, use one of
/// the endpoints from `ruma::api::client::relations` to fetch the relation details.
pub fn has_replacement(&self) -> bool {
self.replace.is_some() || self.has_invalid_replacement
} }
/// Returns `true` if all fields are empty. /// Returns `true` if all fields are empty.
@ -249,6 +242,12 @@ impl BundledMessageLikeRelations {
} }
} }
impl<E> Default for BundledMessageLikeRelations<E> {
fn default() -> Self {
Self::new()
}
}
/// [Bundled aggregations] of related child events of a state event. /// [Bundled aggregations] of related child events of a state event.
/// ///
/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations /// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations

View File

@ -0,0 +1,35 @@
use serde::{de::DeserializeOwned, Deserialize, Deserializer};
use super::{BundledMessageLikeRelations, BundledThread, ReferenceChunk};
use crate::serde::Raw;
#[derive(Deserialize)]
struct BundledMessageLikeRelationsJsonRepr<E> {
#[serde(rename = "m.replace")]
replace: Option<Raw<Box<E>>>,
#[serde(rename = "m.thread")]
thread: Option<Box<BundledThread>>,
#[serde(rename = "m.reference")]
reference: Option<Box<ReferenceChunk>>,
}
impl<'de, E> Deserialize<'de> for BundledMessageLikeRelations<E>
where
E: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let BundledMessageLikeRelationsJsonRepr { replace, thread, reference } =
BundledMessageLikeRelationsJsonRepr::deserialize(deserializer)?;
let (replace, has_invalid_replacement) =
match replace.as_ref().map(Raw::deserialize).transpose() {
Ok(replace) => (replace, false),
Err(_) => (None, true),
};
Ok(BundledMessageLikeRelations { replace, has_invalid_replacement, thread, reference })
}
}

View File

@ -60,7 +60,7 @@ pub struct OriginalRoomRedactionEvent {
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
/// Additional key-value pairs not signed by the homeserver. /// Additional key-value pairs not signed by the homeserver.
pub unsigned: MessageLikeUnsigned, pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
} }
/// Redacted redaction event. /// Redacted redaction event.
@ -106,7 +106,7 @@ pub struct OriginalSyncRoomRedactionEvent {
pub origin_server_ts: MilliSecondsSinceUnixEpoch, pub origin_server_ts: MilliSecondsSinceUnixEpoch,
/// Additional key-value pairs not signed by the homeserver. /// Additional key-value pairs not signed by the homeserver.
pub unsigned: MessageLikeUnsigned, pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
} }
/// Redacted redaction event without a `room_id`. /// Redacted redaction event without a `room_id`.

View File

@ -1,19 +1,20 @@
use js_int::Int; use js_int::Int;
use serde::Deserialize; use serde::{de::DeserializeOwned, Deserialize};
use super::{ use super::{
relation::{BundledMessageLikeRelations, BundledStateRelations}, relation::{BundledMessageLikeRelations, BundledStateRelations},
room::redaction::RoomRedactionEventContent, room::redaction::RoomRedactionEventContent,
PossiblyRedactedStateEventContent, MessageLikeEventContent, OriginalSyncMessageLikeEvent, PossiblyRedactedStateEventContent,
}; };
use crate::{ use crate::{
serde::CanBeEmpty, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, serde::CanBeEmpty, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
}; };
/// Extra information about a message event that is not incorporated into the event's hash. /// Extra information about a message event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(bound = "OriginalSyncMessageLikeEvent<C>: DeserializeOwned")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct MessageLikeUnsigned { pub struct MessageLikeUnsigned<C: MessageLikeEventContent> {
/// The time in milliseconds that has elapsed since the event was sent. /// 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 /// This field is generated by the local homeserver, and may be incorrect if the local time on
@ -29,17 +30,23 @@ pub struct MessageLikeUnsigned {
/// ///
/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations /// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations
#[serde(rename = "m.relations", default)] #[serde(rename = "m.relations", default)]
pub relations: BundledMessageLikeRelations, pub relations: BundledMessageLikeRelations<OriginalSyncMessageLikeEvent<C>>,
} }
impl MessageLikeUnsigned { impl<C: MessageLikeEventContent> MessageLikeUnsigned<C> {
/// Create a new `Unsigned` with fields set to `None`. /// Create a new `Unsigned` with fields set to `None`.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
} }
} }
impl CanBeEmpty for MessageLikeUnsigned { impl<C: MessageLikeEventContent> Default for MessageLikeUnsigned<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: MessageLikeEventContent> CanBeEmpty for MessageLikeUnsigned<C> {
/// Whether this unsigned data is empty (all fields are `None`). /// 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 /// This method is used to determine whether to skip serializing the `unsigned` field in room
@ -142,5 +149,5 @@ pub struct UnsignedRoomRedactionEvent {
/// Additional key-value pairs not signed by the homeserver. /// Additional key-value pairs not signed by the homeserver.
#[serde(default)] #[serde(default)]
pub unsigned: MessageLikeUnsigned, pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
} }