events: Keep data of unknown relations
This commit is contained in:
parent
df0eee30e1
commit
07bc06038f
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use super::AnyMessageLikeEvent;
|
use super::AnyMessageLikeEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
serde::{Raw, StringEnum},
|
serde::{JsonObject, Raw, StringEnum},
|
||||||
OwnedEventId, PrivOwnedStr,
|
OwnedEventId, PrivOwnedStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,8 +81,9 @@ impl<C> Replacement<C> {
|
|||||||
/// The content of a [thread] relation.
|
/// The content of a [thread] relation.
|
||||||
///
|
///
|
||||||
/// [thread]: https://spec.matrix.org/latest/client-server-api/#threading
|
/// [thread]: https://spec.matrix.org/latest/client-server-api/#threading
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
#[serde(tag = "rel_type", rename = "m.thread")]
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
/// The ID of the root message in the thread.
|
/// The ID of the root message in the thread.
|
||||||
pub event_id: OwnedEventId,
|
pub event_id: OwnedEventId,
|
||||||
@ -95,10 +96,12 @@ pub struct Thread {
|
|||||||
/// If this event is not a reply, this is used as a fallback mechanism for clients that do not
|
/// 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
|
/// support threads. This should point to the latest message-like event in the thread and
|
||||||
/// `is_falling_back` must be set to `true`.
|
/// `is_falling_back` must be set to `true`.
|
||||||
|
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
||||||
pub in_reply_to: Option<InReplyTo>,
|
pub in_reply_to: Option<InReplyTo>,
|
||||||
|
|
||||||
/// Whether the `m.in_reply_to` field is a fallback for older clients or a genuine reply in a
|
/// Whether the `m.in_reply_to` field is a fallback for older clients or a genuine reply in a
|
||||||
/// thread.
|
/// thread.
|
||||||
|
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||||
pub is_falling_back: bool,
|
pub is_falling_back: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,3 +305,18 @@ pub enum RelationType {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_Custom(PrivOwnedStr),
|
_Custom(PrivOwnedStr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The payload for a custom relation.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct CustomRelation {
|
||||||
|
/// A custom relation type.
|
||||||
|
pub(super) rel_type: String,
|
||||||
|
|
||||||
|
/// The ID of the event this relation applies to.
|
||||||
|
pub(super) event_id: OwnedEventId,
|
||||||
|
|
||||||
|
/// Remaining event content.
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(super) data: JsonObject,
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! [`m.room.encrypted`]: https://spec.matrix.org/latest/client-server-api/#mroomencrypted
|
//! [`m.room.encrypted`]: https://spec.matrix.org/latest/client-server-api/#mroomencrypted
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::{borrow::Cow, collections::BTreeMap};
|
||||||
|
|
||||||
use js_int::UInt;
|
use js_int::UInt;
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
@ -10,11 +10,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use super::message;
|
use super::message;
|
||||||
use crate::{
|
use crate::{
|
||||||
events::relation::{Annotation, InReplyTo, Reference, Thread},
|
events::relation::{Annotation, CustomRelation, InReplyTo, Reference, RelationType, Thread},
|
||||||
OwnedDeviceId, OwnedEventId,
|
serde::JsonObject,
|
||||||
|
EventId, OwnedDeviceId, OwnedEventId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod relation_serde;
|
mod relation_serde;
|
||||||
|
|
||||||
/// The content of an `m.room.encrypted` event.
|
/// The content of an `m.room.encrypted` event.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||||
@ -106,7 +107,54 @@ pub enum Relation {
|
|||||||
Thread(Thread),
|
Thread(Thread),
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_Custom,
|
_Custom(CustomRelation),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Relation {
|
||||||
|
/// The type of this `Relation`.
|
||||||
|
///
|
||||||
|
/// Returns an `Option` because the `Reply` relation does not have a`rel_type` field.
|
||||||
|
pub fn rel_type(&self) -> Option<RelationType> {
|
||||||
|
match self {
|
||||||
|
Relation::Reply { .. } => None,
|
||||||
|
Relation::Replacement(_) => Some(RelationType::Replacement),
|
||||||
|
Relation::Reference(_) => Some(RelationType::Reference),
|
||||||
|
Relation::Annotation(_) => Some(RelationType::Annotation),
|
||||||
|
Relation::Thread(_) => Some(RelationType::Thread),
|
||||||
|
Relation::_Custom(c) => Some(c.rel_type.as_str().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ID of the event this relates to.
|
||||||
|
///
|
||||||
|
/// This is the `event_id` field at the root of an `m.relates_to` object, except in the case of
|
||||||
|
/// a reply relation where it's the `event_id` field in the `m.in_reply_to` object.
|
||||||
|
pub fn event_id(&self) -> &EventId {
|
||||||
|
match self {
|
||||||
|
Relation::Reply { in_reply_to } => &in_reply_to.event_id,
|
||||||
|
Relation::Replacement(r) => &r.event_id,
|
||||||
|
Relation::Reference(r) => &r.event_id,
|
||||||
|
Relation::Annotation(a) => &a.event_id,
|
||||||
|
Relation::Thread(t) => &t.event_id,
|
||||||
|
Relation::_Custom(c) => &c.event_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The associated data.
|
||||||
|
///
|
||||||
|
/// The returned JSON object won't contain the `rel_type` field, use
|
||||||
|
/// [`.rel_type()`][Self::rel_type] to access it. It also won't contain data
|
||||||
|
/// outside of `m.relates_to` (e.g. `m.new_content` for `m.replace` relations).
|
||||||
|
///
|
||||||
|
/// Prefer to use the public variants of `Relation` where possible; this method is meant to
|
||||||
|
/// be used for custom relations only.
|
||||||
|
pub fn data(&self) -> Cow<'_, JsonObject> {
|
||||||
|
if let Relation::_Custom(c) = self {
|
||||||
|
Cow::Borrowed(&c.data)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(self.serialize_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> From<message::Relation<C>> for Relation {
|
impl<C> From<message::Relation<C>> for Relation {
|
||||||
@ -121,7 +169,7 @@ impl<C> From<message::Relation<C>> for Relation {
|
|||||||
in_reply_to: t.in_reply_to,
|
in_reply_to: t.in_reply_to,
|
||||||
is_falling_back: t.is_falling_back,
|
is_falling_back: t.is_falling_back,
|
||||||
}),
|
}),
|
||||||
message::Relation::_Custom => Self::_Custom,
|
message::Relation::_Custom(c) => Self::_Custom(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,151 +1,101 @@
|
|||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};
|
||||||
|
|
||||||
use super::{Annotation, InReplyTo, Reference, Relation, Replacement, Thread};
|
use super::{InReplyTo, Relation, Thread};
|
||||||
use crate::OwnedEventId;
|
use crate::{
|
||||||
|
serde::{from_raw_json_value, JsonObject},
|
||||||
|
OwnedEventId,
|
||||||
|
};
|
||||||
|
|
||||||
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>,
|
||||||
{
|
{
|
||||||
let relates_to = RelatesToJsonRepr::deserialize(deserializer)?;
|
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
||||||
|
|
||||||
if let Some(
|
let RelationDeHelper { in_reply_to, rel_type } = from_raw_json_value(&json)?;
|
||||||
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
|
|
||||||
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
|
let rel = match (in_reply_to, rel_type.as_deref()) {
|
||||||
) = relates_to.relation
|
(None, None) => return Err(de::Error::missing_field("m.in_reply_to or rel_type")),
|
||||||
{
|
(_, Some("m.thread")) => Relation::Thread(from_raw_json_value(&json)?),
|
||||||
let in_reply_to = relates_to.in_reply_to;
|
(in_reply_to, Some("io.element.thread")) => {
|
||||||
return Ok(Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }));
|
let ThreadUnstableDeHelper { event_id, is_falling_back } =
|
||||||
|
from_raw_json_value(&json)?;
|
||||||
|
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back })
|
||||||
}
|
}
|
||||||
let rel = if let Some(in_reply_to) = relates_to.in_reply_to {
|
(_, Some("m.annotation")) => Relation::Annotation(from_raw_json_value(&json)?),
|
||||||
Relation::Reply { in_reply_to }
|
(_, Some("m.reference")) => Relation::Reference(from_raw_json_value(&json)?),
|
||||||
} else if let Some(relation) = relates_to.relation {
|
(_, Some("m.replace")) => Relation::Replacement(from_raw_json_value(&json)?),
|
||||||
match relation {
|
(Some(in_reply_to), _) => Relation::Reply { in_reply_to },
|
||||||
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
|
_ => Relation::_Custom(from_raw_json_value(&json)?),
|
||||||
RelationJsonRepr::Reference(r) => Relation::Reference(r),
|
|
||||||
RelationJsonRepr::Replacement(Replacement { event_id }) => {
|
|
||||||
Relation::Replacement(Replacement { event_id })
|
|
||||||
}
|
|
||||||
RelationJsonRepr::ThreadStable(_) | RelationJsonRepr::ThreadUnstable(_) => {
|
|
||||||
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 {
|
|
||||||
return Err(de::Error::missing_field("m.in_reply_to or rel_type"));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(rel)
|
Ok(rel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Relation {
|
#[derive(Default, Deserialize)]
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
struct RelationDeHelper {
|
||||||
where
|
#[serde(rename = "m.in_reply_to")]
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let relates_to = match self {
|
|
||||||
Relation::Annotation(r) => RelatesToJsonRepr {
|
|
||||||
relation: Some(RelationJsonRepr::Annotation(r.clone())),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Relation::Reference(r) => RelatesToJsonRepr {
|
|
||||||
relation: Some(RelationJsonRepr::Reference(r.clone())),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Relation::Replacement(r) => RelatesToJsonRepr {
|
|
||||||
relation: Some(RelationJsonRepr::Replacement(r.clone())),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Relation::Reply { in_reply_to } => {
|
|
||||||
RelatesToJsonRepr { in_reply_to: Some(in_reply_to.clone()), ..Default::default() }
|
|
||||||
}
|
|
||||||
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
|
||||||
RelatesToJsonRepr {
|
|
||||||
in_reply_to: in_reply_to.clone(),
|
|
||||||
relation: Some(RelationJsonRepr::ThreadStable(ThreadStableJsonRepr {
|
|
||||||
event_id: event_id.clone(),
|
|
||||||
is_falling_back: *is_falling_back,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Relation::_Custom => RelatesToJsonRepr::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
relates_to.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct 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<InReplyTo>,
|
in_reply_to: Option<InReplyTo>,
|
||||||
|
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
rel_type: Option<String>,
|
||||||
relation: Option<RelationJsonRepr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A thread relation without the reply fallback, with stable names.
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
|
||||||
struct ThreadStableJsonRepr {
|
|
||||||
/// The ID of the root message in the thread.
|
|
||||||
event_id: OwnedEventId,
|
|
||||||
|
|
||||||
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
|
||||||
/// thread.
|
|
||||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
|
||||||
is_falling_back: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A thread relation without the reply fallback, with unstable names.
|
/// A thread relation without the reply fallback, with unstable names.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
struct ThreadUnstableJsonRepr {
|
struct ThreadUnstableDeHelper {
|
||||||
/// The ID of the root message in the thread.
|
|
||||||
event_id: OwnedEventId,
|
event_id: OwnedEventId,
|
||||||
|
|
||||||
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
#[serde(rename = "io.element.show_reply", default)]
|
||||||
/// thread.
|
|
||||||
#[serde(
|
|
||||||
rename = "io.element.show_reply",
|
|
||||||
default,
|
|
||||||
skip_serializing_if = "ruma_common::serde::is_default"
|
|
||||||
)]
|
|
||||||
is_falling_back: bool,
|
is_falling_back: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A relation, which associates new information to an existing event.
|
impl Relation {
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
pub(super) fn serialize_data(&self) -> JsonObject {
|
||||||
#[serde(tag = "rel_type")]
|
match serde_json::to_value(self).expect("relation serialization to succeed") {
|
||||||
enum RelationJsonRepr {
|
JsonValue::Object(mut obj) => {
|
||||||
/// An annotation to an event.
|
obj.remove("rel_type");
|
||||||
#[serde(rename = "m.annotation")]
|
obj
|
||||||
Annotation(Annotation),
|
}
|
||||||
|
_ => panic!("all relations must serialize to objects"),
|
||||||
/// A reference to another event.
|
}
|
||||||
#[serde(rename = "m.reference")]
|
}
|
||||||
Reference(Reference),
|
}
|
||||||
|
|
||||||
/// An event that replaces another event.
|
impl Serialize for Relation {
|
||||||
#[serde(rename = "m.replace")]
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
Replacement(Replacement),
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
/// An event that belongs to a thread, with stable names.
|
{
|
||||||
#[serde(rename = "m.thread")]
|
match self {
|
||||||
ThreadStable(ThreadStableJsonRepr),
|
Relation::Reply { in_reply_to } => {
|
||||||
|
let mut st = serializer.serialize_struct("Relation", 1)?;
|
||||||
/// An event that belongs to a thread, with unstable names.
|
st.serialize_field("m.in_reply_to", in_reply_to)?;
|
||||||
#[serde(rename = "io.element.thread")]
|
st.end()
|
||||||
ThreadUnstable(ThreadUnstableJsonRepr),
|
}
|
||||||
|
Relation::Replacement(data) => {
|
||||||
/// An unknown relation type.
|
RelationSerHelper { rel_type: "m.replace", data }.serialize(serializer)
|
||||||
///
|
}
|
||||||
/// Not available in the public API, but exists here so deserialization
|
Relation::Reference(data) => {
|
||||||
/// doesn't fail with new / custom `rel_type`s.
|
RelationSerHelper { rel_type: "m.reference", data }.serialize(serializer)
|
||||||
#[serde(other)]
|
}
|
||||||
Unknown,
|
Relation::Annotation(data) => {
|
||||||
|
RelationSerHelper { rel_type: "m.annotation", data }.serialize(serializer)
|
||||||
|
}
|
||||||
|
Relation::Thread(data) => {
|
||||||
|
RelationSerHelper { rel_type: "m.thread", data }.serialize(serializer)
|
||||||
|
}
|
||||||
|
Relation::_Custom(c) => c.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct RelationSerHelper<'a, T> {
|
||||||
|
rel_type: &'a str,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: &'a T,
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::relation::{InReplyTo, Replacement, Thread},
|
events::relation::{CustomRelation, InReplyTo, RelationType, Replacement, Thread},
|
||||||
serde::{JsonObject, StringEnum},
|
serde::{JsonObject, StringEnum},
|
||||||
OwnedEventId, PrivOwnedStr,
|
EventId, OwnedEventId, PrivOwnedStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod audio;
|
mod audio;
|
||||||
@ -648,7 +648,53 @@ pub enum Relation<C> {
|
|||||||
Thread(Thread),
|
Thread(Thread),
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_Custom,
|
_Custom(CustomRelation),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Relation<C> {
|
||||||
|
/// The type of this `Relation`.
|
||||||
|
///
|
||||||
|
/// Returns an `Option` because the `Reply` relation does not have a`rel_type` field.
|
||||||
|
pub fn rel_type(&self) -> Option<RelationType> {
|
||||||
|
match self {
|
||||||
|
Relation::Reply { .. } => None,
|
||||||
|
Relation::Replacement(_) => Some(RelationType::Replacement),
|
||||||
|
Relation::Thread(_) => Some(RelationType::Thread),
|
||||||
|
Relation::_Custom(c) => Some(c.rel_type.as_str().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ID of the event this relates to.
|
||||||
|
///
|
||||||
|
/// This is the `event_id` field at the root of an `m.relates_to` object, except in the case of
|
||||||
|
/// a reply relation where it's the `event_id` field in the `m.in_reply_to` object.
|
||||||
|
pub fn event_id(&self) -> &EventId {
|
||||||
|
match self {
|
||||||
|
Relation::Reply { in_reply_to } => &in_reply_to.event_id,
|
||||||
|
Relation::Replacement(r) => &r.event_id,
|
||||||
|
Relation::Thread(t) => &t.event_id,
|
||||||
|
Relation::_Custom(c) => &c.event_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The associated data.
|
||||||
|
///
|
||||||
|
/// The returned JSON object won't contain the `rel_type` field, use
|
||||||
|
/// [`.rel_type()`][Self::rel_type] to access it. It also won't contain data
|
||||||
|
/// outside of `m.relates_to` (e.g. `m.new_content` for `m.replace` relations).
|
||||||
|
///
|
||||||
|
/// Prefer to use the public variants of `Relation` where possible; this method is meant to
|
||||||
|
/// be used for custom relations only.
|
||||||
|
pub fn data(&self) -> Cow<'_, JsonObject>
|
||||||
|
where
|
||||||
|
C: Clone,
|
||||||
|
{
|
||||||
|
if let Relation::_Custom(c) = self {
|
||||||
|
Cow::Borrowed(&c.data)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(self.serialize_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The format for the formatted representation of a message body.
|
/// The format for the formatted representation of a message body.
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use super::{InReplyTo, Relation, Replacement, Thread};
|
use super::{InReplyTo, Relation, Replacement, Thread};
|
||||||
use crate::OwnedEventId;
|
use crate::{events::relation::CustomRelation, serde::JsonObject, OwnedEventId};
|
||||||
|
|
||||||
/// Deserialize an event's `relates_to` field.
|
/// Deserialize an event's `relates_to` field.
|
||||||
///
|
///
|
||||||
@ -25,39 +26,39 @@ where
|
|||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
C: Deserialize<'de>,
|
C: Deserialize<'de>,
|
||||||
{
|
{
|
||||||
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
let EventWithRelatesToDeHelper { relates_to, new_content } =
|
||||||
|
EventWithRelatesToDeHelper::deserialize(deserializer)?;
|
||||||
if let Some(
|
let Some(relates_to) = relates_to else {
|
||||||
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
|
return Ok(None);
|
||||||
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
|
|
||||||
) = ev.relates_to.relation
|
|
||||||
{
|
|
||||||
let in_reply_to = ev.relates_to.in_reply_to;
|
|
||||||
return Ok(Some(Relation::Thread(Thread { event_id, in_reply_to, is_falling_back })));
|
|
||||||
}
|
|
||||||
|
|
||||||
let rel = if let Some(in_reply_to) = ev.relates_to.in_reply_to {
|
|
||||||
Some(Relation::Reply { in_reply_to })
|
|
||||||
} else if let Some(relation) = ev.relates_to.relation {
|
|
||||||
match relation {
|
|
||||||
RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => {
|
|
||||||
let new_content = ev
|
|
||||||
.new_content
|
|
||||||
.ok_or_else(|| serde::de::Error::missing_field("m.new_content"))?;
|
|
||||||
Some(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 => Some(Relation::_Custom),
|
|
||||||
RelationJsonRepr::ThreadStable(_) | RelationJsonRepr::ThreadUnstable(_) => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(rel)
|
let RelatesToDeHelper { in_reply_to, relation } = relates_to;
|
||||||
|
|
||||||
|
let rel = if let Some(RelationDeHelper::Known(relation)) = relation {
|
||||||
|
match relation {
|
||||||
|
KnownRelationDeHelper::Replacement(ReplacementJsonRepr { event_id }) => {
|
||||||
|
match new_content {
|
||||||
|
Some(new_content) => {
|
||||||
|
Relation::Replacement(Replacement { event_id, new_content })
|
||||||
|
}
|
||||||
|
None => return Err(de::Error::missing_field("m.new_content")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KnownRelationDeHelper::Thread(ThreadDeHelper { event_id, is_falling_back })
|
||||||
|
| KnownRelationDeHelper::ThreadUnstable(ThreadUnstableDeHelper {
|
||||||
|
event_id,
|
||||||
|
is_falling_back,
|
||||||
|
}) => Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }),
|
||||||
|
}
|
||||||
|
} else if let Some(in_reply_to) = in_reply_to {
|
||||||
|
Relation::Reply { in_reply_to }
|
||||||
|
} else if let Some(RelationDeHelper::Unknown(c)) = relation {
|
||||||
|
Relation::_Custom(c)
|
||||||
|
} else {
|
||||||
|
return Err(de::Error::missing_field("m.in_reply_to or rel_type"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(rel))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Serialize for Relation<C>
|
impl<C> Serialize for Relation<C>
|
||||||
@ -66,133 +67,157 @@ where
|
|||||||
{
|
{
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let json_repr = match self {
|
let (relates_to, new_content) = self.clone().into_parts();
|
||||||
Relation::Reply { in_reply_to } => {
|
|
||||||
EventWithRelatesToJsonRepr::<C>::new(RelatesToJsonRepr {
|
|
||||||
in_reply_to: Some(in_reply_to.clone()),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
|
||||||
EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
|
|
||||||
in_reply_to: in_reply_to.clone(),
|
|
||||||
relation: Some(RelationJsonRepr::ThreadStable(ThreadStableJsonRepr {
|
|
||||||
event_id: event_id.clone(),
|
|
||||||
is_falling_back: *is_falling_back,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Relation::_Custom => EventWithRelatesToJsonRepr::<C>::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
json_repr.serialize(serializer)
|
EventWithRelatesToSerHelper { relates_to, new_content }.serialize(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct EventWithRelatesToDeHelper<C> {
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: Option<RelatesToDeHelper>,
|
||||||
|
|
||||||
|
#[serde(rename = "m.new_content")]
|
||||||
|
new_content: Option<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct RelatesToDeHelper {
|
||||||
|
#[serde(rename = "m.in_reply_to")]
|
||||||
|
in_reply_to: Option<InReplyTo>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
relation: Option<RelationDeHelper>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RelationDeHelper {
|
||||||
|
Known(KnownRelationDeHelper),
|
||||||
|
Unknown(CustomRelation),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(tag = "rel_type")]
|
||||||
|
enum KnownRelationDeHelper {
|
||||||
|
#[serde(rename = "m.replace")]
|
||||||
|
Replacement(ReplacementJsonRepr),
|
||||||
|
|
||||||
|
#[serde(rename = "m.thread")]
|
||||||
|
Thread(ThreadDeHelper),
|
||||||
|
|
||||||
|
#[serde(rename = "io.element.thread")]
|
||||||
|
ThreadUnstable(ThreadUnstableDeHelper),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A replacement relation without `m.new_content`.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
struct EventWithRelatesToJsonRepr<C> {
|
pub(super) struct ReplacementJsonRepr {
|
||||||
#[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")]
|
event_id: OwnedEventId,
|
||||||
relates_to: RelatesToJsonRepr,
|
}
|
||||||
|
|
||||||
|
/// A thread relation without the reply fallback, with stable names.
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ThreadDeHelper {
|
||||||
|
event_id: OwnedEventId,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
is_falling_back: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A thread relation without the reply fallback, with unstable names.
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ThreadUnstableDeHelper {
|
||||||
|
event_id: OwnedEventId,
|
||||||
|
|
||||||
|
#[serde(rename = "io.element.show_reply", default)]
|
||||||
|
is_falling_back: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(super) struct EventWithRelatesToSerHelper<C> {
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: RelationSerHelper,
|
||||||
|
|
||||||
#[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
|
||||||
new_content: Option<C>,
|
new_content: Option<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> EventWithRelatesToJsonRepr<C> {
|
|
||||||
fn new(relates_to: RelatesToJsonRepr) -> Self {
|
|
||||||
Self { relates_to, new_content: None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> Default for EventWithRelatesToJsonRepr<C> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { relates_to: RelatesToJsonRepr::default(), new_content: None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct 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<InReplyTo>,
|
|
||||||
|
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
|
||||||
relation: Option<RelationJsonRepr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelatesToJsonRepr {
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
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(Serialize)]
|
||||||
#[serde(tag = "rel_type")]
|
#[serde(tag = "rel_type")]
|
||||||
enum RelationJsonRepr {
|
pub(super) enum RelationSerHelper {
|
||||||
/// An event that replaces another event.
|
/// An event that replaces another event.
|
||||||
#[serde(rename = "m.replace")]
|
#[serde(rename = "m.replace")]
|
||||||
Replacement(ReplacementJsonRepr),
|
Replacement(ReplacementJsonRepr),
|
||||||
|
|
||||||
/// An event that belongs to a thread, with stable names.
|
/// An event that belongs to a thread, with stable names.
|
||||||
#[serde(rename = "m.thread")]
|
#[serde(rename = "m.thread")]
|
||||||
ThreadStable(ThreadStableJsonRepr),
|
Thread(Thread),
|
||||||
|
|
||||||
/// An event that belongs to a thread, with unstable names.
|
|
||||||
#[serde(rename = "io.element.thread")]
|
|
||||||
ThreadUnstable(ThreadUnstableJsonRepr),
|
|
||||||
|
|
||||||
/// An unknown relation type.
|
/// An unknown relation type.
|
||||||
///
|
#[serde(untagged)]
|
||||||
/// Not available in the public API, but exists here so deserialization
|
Custom(CustomSerHelper),
|
||||||
/// doesn't fail with new / custom `rel_type`s.
|
|
||||||
#[serde(other)]
|
|
||||||
Unknown,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
impl<C> Relation<C> {
|
||||||
struct ReplacementJsonRepr {
|
fn into_parts(self) -> (RelationSerHelper, Option<C>) {
|
||||||
event_id: OwnedEventId,
|
match self {
|
||||||
|
Relation::Replacement(Replacement { event_id, new_content }) => (
|
||||||
|
RelationSerHelper::Replacement(ReplacementJsonRepr { event_id }),
|
||||||
|
Some(new_content),
|
||||||
|
),
|
||||||
|
Relation::Reply { in_reply_to } => {
|
||||||
|
(RelationSerHelper::Custom(in_reply_to.into()), None)
|
||||||
|
}
|
||||||
|
Relation::Thread(t) => (RelationSerHelper::Thread(t), None),
|
||||||
|
Relation::_Custom(c) => (RelationSerHelper::Custom(c.into()), None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A thread relation without the reply fallback, with stable names.
|
pub(super) fn serialize_data(&self) -> JsonObject
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
where
|
||||||
struct ThreadStableJsonRepr {
|
C: Clone,
|
||||||
/// The ID of the root message in the thread.
|
{
|
||||||
event_id: OwnedEventId,
|
let (relates_to, _) = self.clone().into_parts();
|
||||||
|
|
||||||
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
match serde_json::to_value(relates_to).expect("relation serialization to succeed") {
|
||||||
/// thread.
|
JsonValue::Object(mut obj) => {
|
||||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
obj.remove("rel_type");
|
||||||
is_falling_back: bool,
|
obj
|
||||||
|
}
|
||||||
|
_ => panic!("all relations must serialize to objects"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A thread relation without the reply fallback, with unstable names.
|
#[derive(Default, Serialize)]
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
pub(super) struct CustomSerHelper {
|
||||||
struct ThreadUnstableJsonRepr {
|
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
|
||||||
/// The ID of the root message in the thread.
|
in_reply_to: Option<InReplyTo>,
|
||||||
event_id: OwnedEventId,
|
|
||||||
|
|
||||||
/// Whether the `m.in_reply_to` field is a fallback for older clients or a real reply in a
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// thread.
|
rel_type: Option<String>,
|
||||||
#[serde(
|
|
||||||
rename = "io.element.show_reply",
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
default,
|
event_id: Option<OwnedEventId>,
|
||||||
skip_serializing_if = "ruma_common::serde::is_default"
|
|
||||||
)]
|
#[serde(flatten, skip_serializing_if = "JsonObject::is_empty")]
|
||||||
is_falling_back: bool,
|
data: JsonObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InReplyTo> for CustomSerHelper {
|
||||||
|
fn from(value: InReplyTo) -> Self {
|
||||||
|
Self { in_reply_to: Some(value), ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CustomRelation> for CustomSerHelper {
|
||||||
|
fn from(value: CustomRelation) -> Self {
|
||||||
|
let CustomRelation { rel_type, event_id, data } = value;
|
||||||
|
Self { rel_type: Some(rel_type), event_id: Some(event_id), data, ..Default::default() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use assert_matches2::assert_matches;
|
use assert_matches2::assert_matches;
|
||||||
use ruma_common::{
|
use ruma_common::{
|
||||||
events::{
|
events::{
|
||||||
relation::{InReplyTo, Reference, Thread},
|
relation::{CustomRelation, InReplyTo, Reference, Thread},
|
||||||
room::encrypted::{
|
room::encrypted::{
|
||||||
EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement,
|
EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement,
|
||||||
RoomEncryptedEventContent,
|
RoomEncryptedEventContent,
|
||||||
@ -428,3 +428,78 @@ fn content_annotation_deserialization() {
|
|||||||
assert_eq!(annotation.event_id, "$annotated_event");
|
assert_eq!(annotation.event_id, "$annotated_event");
|
||||||
assert_eq!(annotation.key, "some_key");
|
assert_eq!(annotation.key, "some_key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_relation_deserialization() {
|
||||||
|
let json = json!({
|
||||||
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
|
"sender_key": "aV9BpqYFqJpKYmgERyGv/6QyKMcgLqxM05V0gvzg9Yk",
|
||||||
|
"ciphertext": "AwgAEpABjy6BHczo7UZE3alyej6y2YQ5v+L9eB+fBqL7yteCPv8Jig\
|
||||||
|
FCXKWWuwpbZ4nQpvhUbqW0ZX2474FQf0l1dXGQWDMm0VP5p20elkzSf\
|
||||||
|
n0uzmHVKGQe+NHUKIczRWsUJ6AbrLBbfFKoIPwfbZ7nQQndjA6F0+PW\
|
||||||
|
MoMQHqcrtROrCV/TMux6kDKp7h7O77Y6wp6LD4rU1lwTmKnMYkQGnju\
|
||||||
|
c3+FAMvkow26TuS0/fhJG5m+f0GLlP8FQ3fu0Kjw2YUOLl/BU6gPWdk\
|
||||||
|
lDl5mzVO3tPnJMKZ0hn+AF",
|
||||||
|
"session_id": "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw",
|
||||||
|
"device_id": "DEVICE",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.ruma.custom",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"field": "value",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let content = from_json_value::<RoomEncryptedEventContent>(json).unwrap();
|
||||||
|
|
||||||
|
assert_matches!(content.scheme, EncryptedEventScheme::MegolmV1AesSha2(encrypted_content));
|
||||||
|
assert_eq!(encrypted_content.session_id, "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw");
|
||||||
|
assert_eq!(
|
||||||
|
encrypted_content.ciphertext,
|
||||||
|
"AwgAEpABjy6BHczo7UZE3alyej6y2YQ5v+L9eB+fBqL7yteCPv8Jig\
|
||||||
|
FCXKWWuwpbZ4nQpvhUbqW0ZX2474FQf0l1dXGQWDMm0VP5p20elkzSf\
|
||||||
|
n0uzmHVKGQe+NHUKIczRWsUJ6AbrLBbfFKoIPwfbZ7nQQndjA6F0+PW\
|
||||||
|
MoMQHqcrtROrCV/TMux6kDKp7h7O77Y6wp6LD4rU1lwTmKnMYkQGnju\
|
||||||
|
c3+FAMvkow26TuS0/fhJG5m+f0GLlP8FQ3fu0Kjw2YUOLl/BU6gPWdk\
|
||||||
|
lDl5mzVO3tPnJMKZ0hn+AF"
|
||||||
|
);
|
||||||
|
|
||||||
|
let relation = content.relates_to.unwrap();
|
||||||
|
assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.custom");
|
||||||
|
assert_eq!(relation.event_id(), "$related_event");
|
||||||
|
let data = relation.data();
|
||||||
|
assert_eq!(data.get("field").unwrap().as_str(), Some("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_relation_serialization() {
|
||||||
|
let json = json!({
|
||||||
|
"rel_type": "io.ruma.custom",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"field": "value",
|
||||||
|
});
|
||||||
|
let relation = from_json_value::<CustomRelation>(json).unwrap();
|
||||||
|
|
||||||
|
let content =
|
||||||
|
RoomEncryptedEventContent::new(encrypted_scheme(), Some(Relation::_Custom(relation)));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
|
"sender_key": "aV9BpqYFqJpKYmgERyGv/6QyKMcgLqxM05V0gvzg9Yk",
|
||||||
|
"ciphertext": "AwgAEpABjy6BHczo7UZE3alyej6y2YQ5v+L9eB+fBqL7yteCPv8Jig\
|
||||||
|
FCXKWWuwpbZ4nQpvhUbqW0ZX2474FQf0l1dXGQWDMm0VP5p20elkzSf\
|
||||||
|
n0uzmHVKGQe+NHUKIczRWsUJ6AbrLBbfFKoIPwfbZ7nQQndjA6F0+PW\
|
||||||
|
MoMQHqcrtROrCV/TMux6kDKp7h7O77Y6wp6LD4rU1lwTmKnMYkQGnju\
|
||||||
|
c3+FAMvkow26TuS0/fhJG5m+f0GLlP8FQ3fu0Kjw2YUOLl/BU6gPWdk\
|
||||||
|
lDl5mzVO3tPnJMKZ0hn+AF",
|
||||||
|
"session_id": "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw",
|
||||||
|
"device_id": "DEVICE",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.ruma.custom",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"field": "value",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -129,7 +129,7 @@ fn markdown_content_serialization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn relates_to_content_serialization() {
|
fn reply_content_serialization() {
|
||||||
#[rustfmt::skip] // rustfmt wants to merge the next two lines
|
#[rustfmt::skip] // rustfmt wants to merge the next two lines
|
||||||
let message_event_content =
|
let message_event_content =
|
||||||
assign!(MessageEventContent::plain("> <@test:example.com> test\n\ntest reply"), {
|
assign!(MessageEventContent::plain("> <@test:example.com> test\n\ntest reply"), {
|
||||||
@ -214,7 +214,7 @@ fn html_and_text_content_deserialization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn relates_to_content_deserialization() {
|
fn reply_content_deserialization() {
|
||||||
let json_data = json!({
|
let json_data = json!({
|
||||||
"org.matrix.msc1767.text": [
|
"org.matrix.msc1767.text": [
|
||||||
{ "body": "> <@test:example.com> test\n\ntest reply" },
|
{ "body": "> <@test:example.com> test\n\ntest reply" },
|
||||||
@ -237,6 +237,26 @@ fn relates_to_content_deserialization() {
|
|||||||
assert_eq!(event_id, "$15827405538098VGFWH:example.com");
|
assert_eq!(event_id, "$15827405538098VGFWH:example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thread_content_deserialization() {
|
||||||
|
let json_data = json!({
|
||||||
|
"org.matrix.msc1767.text": [
|
||||||
|
{ "body": "Test in thread" },
|
||||||
|
],
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.thread",
|
||||||
|
"event_id": "$15827405538098VGFWH:example.com",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
|
||||||
|
assert_eq!(content.text.find_plain(), Some("Test in thread"));
|
||||||
|
assert_eq!(content.text.find_html(), None);
|
||||||
|
|
||||||
|
assert_matches!(content.relates_to, Some(Relation::Thread(thread)));
|
||||||
|
assert_eq!(thread.event_id, "$15827405538098VGFWH:example.com");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn message_event_deserialization() {
|
fn message_event_deserialization() {
|
||||||
let json_data = json!({
|
let json_data = json!({
|
||||||
|
@ -2,7 +2,7 @@ use assert_matches2::assert_matches;
|
|||||||
use assign::assign;
|
use assign::assign;
|
||||||
use ruma_common::{
|
use ruma_common::{
|
||||||
events::{
|
events::{
|
||||||
relation::{InReplyTo, Replacement, Thread},
|
relation::{CustomRelation, InReplyTo, Replacement, Thread},
|
||||||
room::message::{MessageType, Relation, RoomMessageEventContent},
|
room::message::{MessageType, Relation, RoomMessageEventContent},
|
||||||
},
|
},
|
||||||
owned_event_id,
|
owned_event_id,
|
||||||
@ -249,3 +249,55 @@ fn thread_unstable_deserialize() {
|
|||||||
assert_eq!(thread.in_reply_to.unwrap().event_id, "$latesteventid");
|
assert_eq!(thread.in_reply_to.unwrap().event_id, "$latesteventid");
|
||||||
assert!(!thread.is_falling_back);
|
assert!(!thread.is_falling_back);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_deserialize() {
|
||||||
|
let json = json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.ruma.unknown",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<RoomMessageEventContent>(json),
|
||||||
|
Ok(RoomMessageEventContent {
|
||||||
|
msgtype: MessageType::Text(_),
|
||||||
|
relates_to: Some(relation),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.unknown");
|
||||||
|
assert_eq!(relation.event_id().as_str(), "$related_event");
|
||||||
|
let data = relation.data();
|
||||||
|
assert_eq!(data.get("key").unwrap().as_str(), Some("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_serialize() {
|
||||||
|
let json = json!({
|
||||||
|
"rel_type": "io.ruma.unknown",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"key": "value",
|
||||||
|
});
|
||||||
|
let relation = from_json_value::<CustomRelation>(json).unwrap();
|
||||||
|
|
||||||
|
let mut content = RoomMessageEventContent::text_plain("<text msg>");
|
||||||
|
content.relates_to = Some(Relation::_Custom(relation));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "<text msg>",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "io.ruma.unknown",
|
||||||
|
"event_id": "$related_event",
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user