common: Add thread relation to Relation
According to MSC3440
This commit is contained in:
parent
e9c60cf36c
commit
5f51f9241f
@ -34,6 +34,7 @@ unstable-msc2675 = []
|
|||||||
unstable-msc2676 = []
|
unstable-msc2676 = []
|
||||||
unstable-msc2677 = []
|
unstable-msc2677 = []
|
||||||
unstable-msc3246 = ["unstable-msc3551", "thiserror"]
|
unstable-msc3246 = ["unstable-msc3551", "thiserror"]
|
||||||
|
unstable-msc3440 = []
|
||||||
unstable-msc3488 = ["unstable-msc1767"]
|
unstable-msc3488 = ["unstable-msc1767"]
|
||||||
unstable-msc3551 = ["unstable-msc1767"]
|
unstable-msc3551 = ["unstable-msc1767"]
|
||||||
unstable-msc3552 = ["unstable-msc1767", "unstable-msc3551"]
|
unstable-msc3552 = ["unstable-msc1767", "unstable-msc3551"]
|
||||||
|
@ -22,9 +22,7 @@ pub struct RoomEncryptedEventContent {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub scheme: EncryptedEventScheme,
|
pub scheme: EncryptedEventScheme,
|
||||||
|
|
||||||
/// Information about related messages for [rich replies].
|
/// Information about related events.
|
||||||
///
|
|
||||||
/// [rich replies]: https://spec.matrix.org/v1.2/client-server-api/#rich-replies
|
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||||
pub relates_to: Option<Relation>,
|
pub relates_to: Option<Relation>,
|
||||||
}
|
}
|
||||||
@ -103,6 +101,10 @@ pub enum Relation {
|
|||||||
#[cfg(feature = "unstable-msc2677")]
|
#[cfg(feature = "unstable-msc2677")]
|
||||||
Annotation(Annotation),
|
Annotation(Annotation),
|
||||||
|
|
||||||
|
/// An event that belongs to a thread.
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
Thread(Thread),
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_Custom,
|
_Custom,
|
||||||
}
|
}
|
||||||
@ -115,6 +117,12 @@ impl From<message::Relation> for Relation {
|
|||||||
message::Relation::Replacement(re) => {
|
message::Relation::Replacement(re) => {
|
||||||
Self::Replacement(Replacement { event_id: re.event_id })
|
Self::Replacement(Replacement { event_id: re.event_id })
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
message::Relation::Thread(t) => Self::Thread(Thread {
|
||||||
|
event_id: t.event_id,
|
||||||
|
in_reply_to: t.in_reply_to,
|
||||||
|
is_falling_back: t.is_falling_back,
|
||||||
|
}),
|
||||||
message::Relation::_Custom => Self::_Custom,
|
message::Relation::_Custom => Self::_Custom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,6 +176,44 @@ impl Annotation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A thread relation for an event.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct Thread {
|
||||||
|
/// The ID of the root message in the thread.
|
||||||
|
pub event_id: Box<EventId>,
|
||||||
|
|
||||||
|
/// A reply relation.
|
||||||
|
///
|
||||||
|
/// If this event is a reply and belongs to a thread, this points to the message that is being
|
||||||
|
/// replied to, and `is_falling_back` must be set to `false`.
|
||||||
|
///
|
||||||
|
/// If this event is not a reply, this is used as a fallback mechanism for clients that do not
|
||||||
|
/// support threads. This should point to the latest message-like event in the thread and
|
||||||
|
/// `is_falling_back` must be set to `true`.
|
||||||
|
pub in_reply_to: InReplyTo,
|
||||||
|
|
||||||
|
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
||||||
|
/// thread.
|
||||||
|
pub is_falling_back: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
impl Thread {
|
||||||
|
/// Convenience method to create a regular `Thread` with the given event ID and latest
|
||||||
|
/// message-like event ID.
|
||||||
|
pub fn plain(event_id: Box<EventId>, latest_event_id: Box<EventId>) -> Self {
|
||||||
|
Self { event_id, in_reply_to: InReplyTo::new(latest_event_id), is_falling_back: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience method to create a reply `Thread` with the given event ID and replied-to event
|
||||||
|
/// ID.
|
||||||
|
pub fn reply(event_id: Box<EventId>, reply_to_event_id: Box<EventId>) -> Self {
|
||||||
|
Self { event_id, in_reply_to: InReplyTo::new(reply_to_event_id), is_falling_back: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The content of an `m.room.encrypted` event using the `m.olm.v1.curve25519-aes-sha2` algorithm.
|
/// The content of an `m.room.encrypted` event using the `m.olm.v1.curve25519-aes-sha2` algorithm.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
@ -4,37 +4,52 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
use super::Annotation;
|
use super::Annotation;
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
use super::Replacement;
|
use super::Replacement;
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
use super::Thread;
|
||||||
use super::{InReplyTo, Reference, Relation};
|
use super::{InReplyTo, Reference, Relation};
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
use crate::EventId;
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Relation {
|
impl<'de> Deserialize<'de> for Relation {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Relation {
|
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
||||||
if let Some(in_reply_to) = ev.relates_to.in_reply_to {
|
|
||||||
return Relation::Reply { in_reply_to };
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(relation) = ev.relates_to.relation {
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
return match relation {
|
if let Some(RelationJsonRepr::Thread(ThreadJsonRepr { event_id, is_falling_back })) =
|
||||||
#[cfg(feature = "unstable-msc2677")]
|
ev.relates_to.relation
|
||||||
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
|
{
|
||||||
RelationJsonRepr::Reference(r) => Relation::Reference(r),
|
let in_reply_to = ev
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
.relates_to
|
||||||
RelationJsonRepr::Replacement(Replacement { event_id }) => {
|
.in_reply_to
|
||||||
Relation::Replacement(Replacement { event_id })
|
.ok_or_else(|| serde::de::Error::missing_field("m.in_reply_to"))?;
|
||||||
}
|
return Ok(Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }));
|
||||||
// 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 => Relation::_Custom,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Relation::_Custom
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation)
|
let rel = if let Some(in_reply_to) = ev.relates_to.in_reply_to {
|
||||||
|
Relation::Reply { in_reply_to }
|
||||||
|
} else if let Some(relation) = ev.relates_to.relation {
|
||||||
|
match relation {
|
||||||
|
#[cfg(feature = "unstable-msc2677")]
|
||||||
|
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
|
||||||
|
RelationJsonRepr::Reference(r) => Relation::Reference(r),
|
||||||
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
|
RelationJsonRepr::Replacement(Replacement { event_id }) => {
|
||||||
|
Relation::Replacement(Replacement { event_id })
|
||||||
|
}
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
RelationJsonRepr::Thread(_) => unreachable!(),
|
||||||
|
// 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 => Relation::_Custom,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Relation::_Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(rel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +77,17 @@ impl Serialize for Relation {
|
|||||||
Relation::Reply { in_reply_to } => {
|
Relation::Reply { in_reply_to } => {
|
||||||
RelatesToJsonRepr { in_reply_to: Some(in_reply_to.clone()), ..Default::default() }
|
RelatesToJsonRepr { in_reply_to: Some(in_reply_to.clone()), ..Default::default() }
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
||||||
|
RelatesToJsonRepr {
|
||||||
|
in_reply_to: Some(in_reply_to.clone()),
|
||||||
|
relation: Some(RelationJsonRepr::Thread(ThreadJsonRepr {
|
||||||
|
event_id: event_id.clone(),
|
||||||
|
is_falling_back: *is_falling_back,
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
Relation::_Custom => RelatesToJsonRepr::default(),
|
Relation::_Custom => RelatesToJsonRepr::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,8 +101,8 @@ struct EventWithRelatesToJsonRepr {
|
|||||||
relates_to: RelatesToJsonRepr,
|
relates_to: RelatesToJsonRepr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an
|
/// Struct modeling the different ways relationships can be expressed in a `m.relates_to` field of
|
||||||
/// event.
|
/// an event.
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
struct RelatesToJsonRepr {
|
struct RelatesToJsonRepr {
|
||||||
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
||||||
@ -92,6 +118,23 @@ impl RelatesToJsonRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A thread relation without the reply fallback.
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
struct ThreadJsonRepr {
|
||||||
|
/// The ID of the root message in the thread.
|
||||||
|
pub event_id: Box<EventId>,
|
||||||
|
|
||||||
|
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
||||||
|
/// thread.
|
||||||
|
#[serde(
|
||||||
|
rename = "io.element.show_reply",
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "ruma_common::serde::is_default"
|
||||||
|
)]
|
||||||
|
pub is_falling_back: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// A relation, which associates new information to an existing event.
|
/// A relation, which associates new information to an existing event.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
#[serde(tag = "rel_type")]
|
#[serde(tag = "rel_type")]
|
||||||
@ -110,6 +153,11 @@ enum RelationJsonRepr {
|
|||||||
#[serde(rename = "m.replace")]
|
#[serde(rename = "m.replace")]
|
||||||
Replacement(Replacement),
|
Replacement(Replacement),
|
||||||
|
|
||||||
|
/// An event that belongs to a thread.
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
#[serde(rename = "io.element.thread")]
|
||||||
|
Thread(ThreadJsonRepr),
|
||||||
|
|
||||||
/// An unknown relation type.
|
/// An unknown relation type.
|
||||||
///
|
///
|
||||||
/// Not available in the public API, but exists here so deserialization
|
/// Not available in the public API, but exists here so deserialization
|
||||||
|
@ -338,8 +338,6 @@ impl From<MessageType> for RoomMessageEventContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Message event relationship.
|
/// Message event relationship.
|
||||||
///
|
|
||||||
/// Currently used for replies and editing (message replacement).
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[allow(clippy::manual_non_exhaustive)]
|
#[allow(clippy::manual_non_exhaustive)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
@ -354,6 +352,10 @@ pub enum Relation {
|
|||||||
#[cfg(feature = "unstable-msc2676")]
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
Replacement(Replacement),
|
Replacement(Replacement),
|
||||||
|
|
||||||
|
/// An event that belongs to a thread.
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
Thread(Thread),
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_Custom,
|
_Custom,
|
||||||
}
|
}
|
||||||
@ -393,6 +395,44 @@ impl Replacement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The content of a thread relation.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct Thread {
|
||||||
|
/// The ID of the root message in the thread.
|
||||||
|
pub event_id: Box<EventId>,
|
||||||
|
|
||||||
|
/// A reply relation.
|
||||||
|
///
|
||||||
|
/// If this event is a reply and belongs to a thread, this points to the message that is being
|
||||||
|
/// replied to, and `is_falling_back` must be set to `false`.
|
||||||
|
///
|
||||||
|
/// If this event is not a reply, this is used as a fallback mechanism for clients that do not
|
||||||
|
/// support threads. This should point to the latest message-like event in the thread and
|
||||||
|
/// `is_falling_back` must be set to `true`.
|
||||||
|
pub in_reply_to: InReplyTo,
|
||||||
|
|
||||||
|
/// Whether the `m.in_reply_to` field is a fallback for older clients or a genuine reply in a
|
||||||
|
/// thread.
|
||||||
|
pub is_falling_back: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
impl Thread {
|
||||||
|
/// Convenience method to create a regular `Thread` with the given event ID and latest
|
||||||
|
/// message-like event ID.
|
||||||
|
pub fn plain(event_id: Box<EventId>, latest_event_id: Box<EventId>) -> Self {
|
||||||
|
Self { event_id, in_reply_to: InReplyTo::new(latest_event_id), is_falling_back: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience method to create a reply `Thread` with the given event ID and replied-to event
|
||||||
|
/// ID.
|
||||||
|
pub fn reply(event_id: Box<EventId>, reply_to_event_id: Box<EventId>) -> Self {
|
||||||
|
Self { event_id, in_reply_to: InReplyTo::new(reply_to_event_id), is_falling_back: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The payload for an audio message.
|
/// The payload for an audio message.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
@ -1006,35 +1046,3 @@ pub struct CustomEventContent {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
data: JsonObject,
|
data: JsonObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::event_id;
|
|
||||||
use matches::assert_matches;
|
|
||||||
use serde_json::{from_value as from_json_value, json};
|
|
||||||
|
|
||||||
use super::{InReplyTo, MessageType, Relation, RoomMessageEventContent};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_reply() {
|
|
||||||
let ev_id = event_id!("$1598361704261elfgc:localhost");
|
|
||||||
|
|
||||||
let json = json!({
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": "<text msg>",
|
|
||||||
"m.relates_to": {
|
|
||||||
"m.in_reply_to": {
|
|
||||||
"event_id": ev_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<RoomMessageEventContent>(json).unwrap(),
|
|
||||||
RoomMessageEventContent {
|
|
||||||
msgtype: MessageType::Text(_),
|
|
||||||
relates_to: Some(Relation::Reply { in_reply_to: InReplyTo { event_id } }),
|
|
||||||
} if event_id == ev_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,8 +4,10 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
use super::Replacement;
|
use super::Replacement;
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
use super::RoomMessageEventContent;
|
use super::RoomMessageEventContent;
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
use super::Thread;
|
||||||
use super::{InReplyTo, Relation};
|
use super::{InReplyTo, Relation};
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
#[cfg(any(feature = "unstable-msc2676", feature = "unstable-msc3440"))]
|
||||||
use crate::EventId;
|
use crate::EventId;
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Relation {
|
impl<'de> Deserialize<'de> for Relation {
|
||||||
@ -15,13 +17,22 @@ impl<'de> Deserialize<'de> for Relation {
|
|||||||
{
|
{
|
||||||
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
||||||
|
|
||||||
if let Some(in_reply_to) = ev.relates_to.in_reply_to {
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
return Ok(Relation::Reply { in_reply_to });
|
if let Some(RelationJsonRepr::Thread(ThreadJsonRepr { event_id, is_falling_back })) =
|
||||||
|
ev.relates_to.relation
|
||||||
|
{
|
||||||
|
let in_reply_to = ev
|
||||||
|
.relates_to
|
||||||
|
.in_reply_to
|
||||||
|
.ok_or_else(|| serde::de::Error::missing_field("m.in_reply_to"))?;
|
||||||
|
return Ok(Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
let rel = if let Some(in_reply_to) = ev.relates_to.in_reply_to {
|
||||||
if let Some(relation) = ev.relates_to.relation {
|
Relation::Reply { in_reply_to }
|
||||||
return Ok(match relation {
|
} else if let Some(relation) = ev.relates_to.relation {
|
||||||
|
match relation {
|
||||||
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => {
|
RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => {
|
||||||
let new_content = ev
|
let new_content = ev
|
||||||
.new_content
|
.new_content
|
||||||
@ -31,10 +42,14 @@ impl<'de> Deserialize<'de> for Relation {
|
|||||||
// FIXME: Maybe we should log this, though at this point we don't even have
|
// FIXME: Maybe we should log this, though at this point we don't even have
|
||||||
// access to the rel_type of the unknown relation.
|
// access to the rel_type of the unknown relation.
|
||||||
RelationJsonRepr::Unknown => Relation::_Custom,
|
RelationJsonRepr::Unknown => Relation::_Custom,
|
||||||
});
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
}
|
RelationJsonRepr::Thread(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Relation::_Custom
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Relation::_Custom)
|
Ok(rel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +76,17 @@ impl Serialize for Relation {
|
|||||||
new_content: Some(new_content.clone()),
|
new_content: Some(new_content.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
||||||
|
EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
|
||||||
|
in_reply_to: Some(in_reply_to.clone()),
|
||||||
|
relation: Some(RelationJsonRepr::Thread(ThreadJsonRepr {
|
||||||
|
event_id: event_id.clone(),
|
||||||
|
is_falling_back: *is_falling_back,
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
Relation::_Custom => EventWithRelatesToJsonRepr::default(),
|
Relation::_Custom => EventWithRelatesToJsonRepr::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,41 +114,37 @@ impl EventWithRelatesToJsonRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an
|
/// Struct modeling the different ways relationships can be expressed in a `m.relates_to` field of
|
||||||
/// event.
|
/// an event.
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
struct RelatesToJsonRepr {
|
struct RelatesToJsonRepr {
|
||||||
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
||||||
in_reply_to: Option<InReplyTo>,
|
in_reply_to: Option<InReplyTo>,
|
||||||
|
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||||
relation: Option<RelationJsonRepr>,
|
relation: Option<RelationJsonRepr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelatesToJsonRepr {
|
impl RelatesToJsonRepr {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
#[cfg(not(feature = "unstable-msc2676"))]
|
self.in_reply_to.is_none() && self.relation.is_none()
|
||||||
{
|
|
||||||
self.in_reply_to.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
|
||||||
{
|
|
||||||
self.in_reply_to.is_none() && self.relation.is_none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A relation, which associates new information to an existing event.
|
/// A relation, which associates new information to an existing event.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
#[cfg(feature = "unstable-msc2676")]
|
|
||||||
#[serde(tag = "rel_type")]
|
#[serde(tag = "rel_type")]
|
||||||
enum RelationJsonRepr {
|
enum RelationJsonRepr {
|
||||||
/// An event that replaces another event.
|
/// An event that replaces another event.
|
||||||
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
#[serde(rename = "m.replace")]
|
#[serde(rename = "m.replace")]
|
||||||
Replacement(ReplacementJsonRepr),
|
Replacement(ReplacementJsonRepr),
|
||||||
|
|
||||||
|
/// An event that belongs to a thread.
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
#[serde(rename = "io.element.thread")]
|
||||||
|
Thread(ThreadJsonRepr),
|
||||||
|
|
||||||
/// An unknown relation type.
|
/// An unknown relation type.
|
||||||
///
|
///
|
||||||
/// Not available in the public API, but exists here so deserialization
|
/// Not available in the public API, but exists here so deserialization
|
||||||
@ -136,3 +158,20 @@ enum RelationJsonRepr {
|
|||||||
struct ReplacementJsonRepr {
|
struct ReplacementJsonRepr {
|
||||||
event_id: Box<EventId>,
|
event_id: Box<EventId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A thread relation without the reply fallback.
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
struct ThreadJsonRepr {
|
||||||
|
/// The ID of the root message in the thread.
|
||||||
|
event_id: Box<EventId>,
|
||||||
|
|
||||||
|
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
||||||
|
/// thread.
|
||||||
|
#[serde(
|
||||||
|
rename = "io.element.show_reply",
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "ruma_common::serde::is_default"
|
||||||
|
)]
|
||||||
|
is_falling_back: bool,
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ mod message_event;
|
|||||||
mod pdu;
|
mod pdu;
|
||||||
mod redacted;
|
mod redacted;
|
||||||
mod redaction;
|
mod redaction;
|
||||||
|
mod relations;
|
||||||
mod room_message;
|
mod room_message;
|
||||||
mod state_event;
|
mod state_event;
|
||||||
mod stripped;
|
mod stripped;
|
||||||
|
217
crates/ruma-common/tests/events/relations.rs
Normal file
217
crates/ruma-common/tests/events/relations.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
use assign::assign;
|
||||||
|
use matches::assert_matches;
|
||||||
|
use ruma_common::{
|
||||||
|
event_id,
|
||||||
|
events::room::message::{InReplyTo, MessageType, Relation, RoomMessageEventContent},
|
||||||
|
};
|
||||||
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_deserialize() {
|
||||||
|
let ev_id = event_id!("$1598361704261elfgc:localhost");
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": ev_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<RoomMessageEventContent>(json).unwrap(),
|
||||||
|
RoomMessageEventContent {
|
||||||
|
msgtype: MessageType::Text(_),
|
||||||
|
relates_to: Some(Relation::Reply { in_reply_to: InReplyTo { event_id, .. }, .. }),
|
||||||
|
..
|
||||||
|
} if event_id == ev_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_serialize() {
|
||||||
|
let content = assign!(RoomMessageEventContent::text_plain("This is a reply"), {
|
||||||
|
relates_to: Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$1598361704261elfgc").to_owned()) }),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "This is a reply",
|
||||||
|
"m.relates_to": {
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
|
fn replacement_serialize() {
|
||||||
|
use ruma_common::events::room::message::Replacement;
|
||||||
|
|
||||||
|
let content = assign!(
|
||||||
|
RoomMessageEventContent::text_plain("<text msg>"),
|
||||||
|
{
|
||||||
|
relates_to: Some(Relation::Replacement(
|
||||||
|
Replacement::new(
|
||||||
|
event_id!("$1598361704261elfgc").to_owned(),
|
||||||
|
Box::new(RoomMessageEventContent::text_plain("This is the new content.")),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.new_content": {
|
||||||
|
"body": "This is the new content.",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
},
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.replace",
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-msc2676")]
|
||||||
|
fn replacement_deserialize() {
|
||||||
|
use ruma_common::events::room::message::Replacement;
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.new_content": {
|
||||||
|
"body": "Hello! My name is bar",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
},
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.replace",
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<RoomMessageEventContent>(json).unwrap(),
|
||||||
|
RoomMessageEventContent {
|
||||||
|
msgtype: MessageType::Text(_),
|
||||||
|
relates_to: Some(Relation::Replacement(Replacement { event_id, new_content, .. })),
|
||||||
|
..
|
||||||
|
} if event_id == "$1598361704261elfgc"
|
||||||
|
&& matches!(&new_content.msgtype, MessageType::Text(text) if text.body == "Hello! My name is bar")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
fn thread_plain_serialize() {
|
||||||
|
use ruma_common::events::room::message::Thread;
|
||||||
|
|
||||||
|
let content = assign!(
|
||||||
|
RoomMessageEventContent::text_plain("<text msg>"),
|
||||||
|
{
|
||||||
|
relates_to: Some(Relation::Thread(
|
||||||
|
Thread::plain(
|
||||||
|
event_id!("$1598361704261elfgc").to_owned(),
|
||||||
|
event_id!("$latesteventid").to_owned(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.element.thread",
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": "$latesteventid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
fn thread_reply_serialize() {
|
||||||
|
use ruma_common::events::room::message::Thread;
|
||||||
|
|
||||||
|
let content = assign!(
|
||||||
|
RoomMessageEventContent::text_plain("<text msg>"),
|
||||||
|
{
|
||||||
|
relates_to: Some(Relation::Thread(
|
||||||
|
Thread::reply(
|
||||||
|
event_id!("$1598361704261elfgc").to_owned(),
|
||||||
|
event_id!("$repliedtoeventid").to_owned(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.element.thread",
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": "$repliedtoeventid",
|
||||||
|
},
|
||||||
|
"io.element.show_reply": true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-msc3440")]
|
||||||
|
fn thread_deserialize() {
|
||||||
|
use ruma_common::events::room::message::Thread;
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.element.thread",
|
||||||
|
"event_id": "$1598361704261elfgc",
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": "$latesteventid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<RoomMessageEventContent>(json).unwrap(),
|
||||||
|
RoomMessageEventContent {
|
||||||
|
msgtype: MessageType::Text(_),
|
||||||
|
relates_to: Some(Relation::Thread(
|
||||||
|
Thread {
|
||||||
|
event_id,
|
||||||
|
in_reply_to: InReplyTo { event_id: reply_to_event_id, .. },
|
||||||
|
is_falling_back,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
..
|
||||||
|
} if event_id == "$1598361704261elfgc"
|
||||||
|
&& reply_to_event_id == "$latesteventid"
|
||||||
|
&& !is_falling_back
|
||||||
|
);
|
||||||
|
}
|
@ -118,6 +118,7 @@ unstable-msc2675 = ["ruma-common/unstable-msc2675"]
|
|||||||
unstable-msc2676 = ["ruma-common/unstable-msc2676"]
|
unstable-msc2676 = ["ruma-common/unstable-msc2676"]
|
||||||
unstable-msc2677 = ["ruma-common/unstable-msc2677"]
|
unstable-msc2677 = ["ruma-common/unstable-msc2677"]
|
||||||
unstable-msc3246 = ["ruma-common/unstable-msc3246"]
|
unstable-msc3246 = ["ruma-common/unstable-msc3246"]
|
||||||
|
unstable-msc3440 = ["ruma-common/unstable-msc3440"]
|
||||||
unstable-msc3488 = [
|
unstable-msc3488 = [
|
||||||
"ruma-client-api/unstable-msc3488",
|
"ruma-client-api/unstable-msc3488",
|
||||||
"ruma-common/unstable-msc3488",
|
"ruma-common/unstable-msc3488",
|
||||||
@ -138,6 +139,7 @@ __ci = [
|
|||||||
"unstable-msc2676",
|
"unstable-msc2676",
|
||||||
"unstable-msc2677",
|
"unstable-msc2677",
|
||||||
"unstable-msc3246",
|
"unstable-msc3246",
|
||||||
|
"unstable-msc3440",
|
||||||
"unstable-msc3488",
|
"unstable-msc3488",
|
||||||
"unstable-msc3551",
|
"unstable-msc3551",
|
||||||
"unstable-msc3552",
|
"unstable-msc3552",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user