events: Don't fail event content parsing on invalid relation
This commit is contained in:
		
							parent
							
								
									6602782a15
								
							
						
					
					
						commit
						740f357e97
					
				| @ -1,5 +1,15 @@ | |||||||
| # [unreleased] | # [unreleased] | ||||||
| 
 | 
 | ||||||
|  | Breaking changes: | ||||||
|  | 
 | ||||||
|  | - Remove `event_id` methods from relation types | ||||||
|  | 
 | ||||||
|  | Improvements: | ||||||
|  | 
 | ||||||
|  | - Don't fail event content parsing on invalid relation | ||||||
|  |   - We previously already accepted custom or slightly malformed relations | ||||||
|  |   - Now, even invalid / missing `rel_type` and `event_id` are accepted | ||||||
|  | 
 | ||||||
| # 0.27.9 | # 0.27.9 | ||||||
| 
 | 
 | ||||||
| Bug fixes: | Bug fixes: | ||||||
|  | |||||||
| @ -316,14 +316,11 @@ pub enum RelationType { | |||||||
| /// The payload for a custom relation.
 | /// The payload for a custom relation.
 | ||||||
| #[doc(hidden)] | #[doc(hidden)] | ||||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | #[derive(Clone, Debug, Deserialize, Serialize)] | ||||||
| pub struct CustomRelation { | #[serde(transparent)] | ||||||
|     /// A custom relation type.
 | pub struct CustomRelation(pub(super) JsonObject); | ||||||
|     pub(super) rel_type: String, |  | ||||||
| 
 | 
 | ||||||
|     /// The ID of the event this relation applies to.
 | impl CustomRelation { | ||||||
|     pub(super) event_id: OwnedEventId, |     pub(super) fn rel_type(&self) -> Option<RelationType> { | ||||||
| 
 |         Some(self.0.get("rel_type")?.as_str()?.into()) | ||||||
|     /// Remaining event content.
 |     } | ||||||
|     #[serde(flatten)] |  | ||||||
|     pub(super) data: JsonObject, |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| use std::{borrow::Cow, collections::BTreeMap}; | use std::{borrow::Cow, collections::BTreeMap}; | ||||||
| 
 | 
 | ||||||
| use js_int::UInt; | use js_int::UInt; | ||||||
| use ruma_common::{serde::JsonObject, EventId, OwnedDeviceId, OwnedEventId}; | use ruma_common::{serde::JsonObject, OwnedDeviceId, OwnedEventId}; | ||||||
| use ruma_macros::EventContent; | use ruma_macros::EventContent; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| @ -118,36 +118,21 @@ impl Relation { | |||||||
|             Relation::Reference(_) => Some(RelationType::Reference), |             Relation::Reference(_) => Some(RelationType::Reference), | ||||||
|             Relation::Annotation(_) => Some(RelationType::Annotation), |             Relation::Annotation(_) => Some(RelationType::Annotation), | ||||||
|             Relation::Thread(_) => Some(RelationType::Thread), |             Relation::Thread(_) => Some(RelationType::Thread), | ||||||
|             Relation::_Custom(c) => Some(c.rel_type.as_str().into()), |             Relation::_Custom(c) => c.rel_type(), | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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 associated data.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The returned JSON object won't contain the `rel_type` field, use
 |     /// The returned JSON object holds the contents of `m.relates_to`, including `rel_type` and
 | ||||||
|     /// [`.rel_type()`][Self::rel_type] to access it. It also won't contain data
 |     /// `event_id` if present, but not things like `m.new_content` for `m.replace` relations that
 | ||||||
|     /// outside of `m.relates_to` (e.g. `m.new_content` for `m.replace` relations).
 |     /// live next to `m.relates_to`.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 |     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 | ||||||
|     /// be used for custom relations only.
 |     /// be used for custom relations only.
 | ||||||
|     pub fn data(&self) -> Cow<'_, JsonObject> { |     pub fn data(&self) -> Cow<'_, JsonObject> { | ||||||
|         if let Relation::_Custom(c) = self { |         if let Relation::_Custom(CustomRelation(data)) = self { | ||||||
|             Cow::Borrowed(&c.data) |             Cow::Borrowed(data) | ||||||
|         } else { |         } else { | ||||||
|             Cow::Owned(self.serialize_data()) |             Cow::Owned(self.serialize_data()) | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ use ruma_common::{ | |||||||
|     serde::{from_raw_json_value, JsonObject}, |     serde::{from_raw_json_value, JsonObject}, | ||||||
|     OwnedEventId, |     OwnedEventId, | ||||||
| }; | }; | ||||||
| use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize}; | use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize}; | ||||||
| use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; | use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; | ||||||
| 
 | 
 | ||||||
| use super::{InReplyTo, Relation, Thread}; | use super::{InReplyTo, Relation, Thread}; | ||||||
| @ -17,7 +17,6 @@ impl<'de> Deserialize<'de> for Relation { | |||||||
|         let RelationDeHelper { in_reply_to, rel_type } = from_raw_json_value(&json)?; |         let RelationDeHelper { in_reply_to, rel_type } = from_raw_json_value(&json)?; | ||||||
| 
 | 
 | ||||||
|         let rel = match (in_reply_to, rel_type.as_deref()) { |         let rel = match (in_reply_to, rel_type.as_deref()) { | ||||||
|             (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)?), |             (_, Some("m.thread")) => Relation::Thread(from_raw_json_value(&json)?), | ||||||
|             (in_reply_to, Some("io.element.thread")) => { |             (in_reply_to, Some("io.element.thread")) => { | ||||||
|                 let ThreadUnstableDeHelper { event_id, is_falling_back } = |                 let ThreadUnstableDeHelper { event_id, is_falling_back } = | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| 
 | 
 | ||||||
| use ruma_common::{serde::JsonObject, EventId}; | use ruma_common::serde::JsonObject; | ||||||
| 
 | 
 | ||||||
| use crate::relation::{CustomRelation, InReplyTo, RelationType, Replacement, Thread}; | use crate::relation::{CustomRelation, InReplyTo, RelationType, Replacement, Thread}; | ||||||
| 
 | 
 | ||||||
| @ -34,28 +34,15 @@ impl<C> Relation<C> { | |||||||
|             Relation::Reply { .. } => None, |             Relation::Reply { .. } => None, | ||||||
|             Relation::Replacement(_) => Some(RelationType::Replacement), |             Relation::Replacement(_) => Some(RelationType::Replacement), | ||||||
|             Relation::Thread(_) => Some(RelationType::Thread), |             Relation::Thread(_) => Some(RelationType::Thread), | ||||||
|             Relation::_Custom(c) => Some(c.rel_type.as_str().into()), |             Relation::_Custom(c) => c.rel_type(), | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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 associated data.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The returned JSON object won't contain the `rel_type` field, use
 |     /// The returned JSON object holds the contents of `m.relates_to`, including `rel_type` and
 | ||||||
|     /// [`.rel_type()`][Self::rel_type] to access it. It also won't contain data
 |     /// `event_id` if present, but not things like `m.new_content` for `m.replace` relations that
 | ||||||
|     /// outside of `m.relates_to` (e.g. `m.new_content` for `m.replace` relations).
 |     /// live next to `m.relates_to`.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 |     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 | ||||||
|     /// be used for custom relations only.
 |     /// be used for custom relations only.
 | ||||||
| @ -63,8 +50,8 @@ impl<C> Relation<C> { | |||||||
|     where |     where | ||||||
|         C: Clone, |         C: Clone, | ||||||
|     { |     { | ||||||
|         if let Relation::_Custom(c) = self { |         if let Relation::_Custom(CustomRelation(data)) = self { | ||||||
|             Cow::Borrowed(&c.data) |             Cow::Borrowed(data) | ||||||
|         } else { |         } else { | ||||||
|             Cow::Owned(self.serialize_data()) |             Cow::Owned(self.serialize_data()) | ||||||
|         } |         } | ||||||
| @ -97,32 +84,21 @@ impl RelationWithoutReplacement { | |||||||
|         match self { |         match self { | ||||||
|             Self::Reply { .. } => None, |             Self::Reply { .. } => None, | ||||||
|             Self::Thread(_) => Some(RelationType::Thread), |             Self::Thread(_) => Some(RelationType::Thread), | ||||||
|             Self::_Custom(c) => Some(c.rel_type.as_str().into()), |             Self::_Custom(c) => c.rel_type(), | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// 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 { |  | ||||||
|             Self::Reply { in_reply_to } => &in_reply_to.event_id, |  | ||||||
|             Self::Thread(t) => &t.event_id, |  | ||||||
|             Self::_Custom(c) => &c.event_id, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// The associated data.
 |     /// The associated data.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The returned JSON object won't contain the `rel_type` field, use
 |     /// The returned JSON object holds the contents of `m.relates_to`, including `rel_type` and
 | ||||||
|     /// [`.rel_type()`][Self::rel_type] to access it.
 |     /// `event_id` if present, but not things like `m.new_content` for `m.replace` relations that
 | ||||||
|  |     /// live next to `m.relates_to`.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 |     /// Prefer to use the public variants of `Relation` where possible; this method is meant to
 | ||||||
|     /// be used for custom relations only.
 |     /// be used for custom relations only.
 | ||||||
|     pub fn data(&self) -> Cow<'_, JsonObject> { |     pub fn data(&self) -> Cow<'_, JsonObject> { | ||||||
|         if let Self::_Custom(c) = self { |         if let Self::_Custom(CustomRelation(data)) = self { | ||||||
|             Cow::Borrowed(&c.data) |             Cow::Borrowed(data) | ||||||
|         } else { |         } else { | ||||||
|             Cow::Owned(self.serialize_data()) |             Cow::Owned(self.serialize_data()) | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -35,8 +35,8 @@ where | |||||||
| 
 | 
 | ||||||
|     let RelatesToDeHelper { in_reply_to, relation } = relates_to; |     let RelatesToDeHelper { in_reply_to, relation } = relates_to; | ||||||
| 
 | 
 | ||||||
|     let rel = if let Some(RelationDeHelper::Known(relation)) = relation { |     let rel = match relation { | ||||||
|         match relation { |         RelationDeHelper::Known(relation) => match relation { | ||||||
|             KnownRelationDeHelper::Replacement(ReplacementJsonRepr { event_id }) => { |             KnownRelationDeHelper::Replacement(ReplacementJsonRepr { event_id }) => { | ||||||
|                 match new_content { |                 match new_content { | ||||||
|                     Some(new_content) => { |                     Some(new_content) => { | ||||||
| @ -50,13 +50,14 @@ where | |||||||
|                 event_id, |                 event_id, | ||||||
|                 is_falling_back, |                 is_falling_back, | ||||||
|             }) => Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }), |             }) => Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }), | ||||||
|  |         }, | ||||||
|  |         RelationDeHelper::Unknown(c) => { | ||||||
|  |             if let Some(in_reply_to) = in_reply_to { | ||||||
|  |                 Relation::Reply { in_reply_to } | ||||||
|  |             } else { | ||||||
|  |                 Relation::_Custom(c) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } 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)) |     Ok(Some(rel)) | ||||||
| @ -91,7 +92,7 @@ pub(crate) struct RelatesToDeHelper { | |||||||
|     in_reply_to: Option<InReplyTo>, |     in_reply_to: Option<InReplyTo>, | ||||||
| 
 | 
 | ||||||
|     #[serde(flatten)] |     #[serde(flatten)] | ||||||
|     relation: Option<RelationDeHelper>, |     relation: RelationDeHelper, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize)] | ||||||
| @ -200,26 +201,19 @@ pub(super) struct CustomSerHelper { | |||||||
|     #[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>, | ||||||
| 
 | 
 | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     rel_type: Option<String>, |  | ||||||
| 
 |  | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     event_id: Option<OwnedEventId>, |  | ||||||
| 
 |  | ||||||
|     #[serde(flatten, skip_serializing_if = "JsonObject::is_empty")] |     #[serde(flatten, skip_serializing_if = "JsonObject::is_empty")] | ||||||
|     data: JsonObject, |     data: JsonObject, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<InReplyTo> for CustomSerHelper { | impl From<InReplyTo> for CustomSerHelper { | ||||||
|     fn from(value: InReplyTo) -> Self { |     fn from(value: InReplyTo) -> Self { | ||||||
|         Self { in_reply_to: Some(value), ..Default::default() } |         Self { in_reply_to: Some(value), data: JsonObject::new() } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<CustomRelation> for CustomSerHelper { | impl From<CustomRelation> for CustomSerHelper { | ||||||
|     fn from(value: CustomRelation) -> Self { |     fn from(CustomRelation(data): CustomRelation) -> Self { | ||||||
|         let CustomRelation { rel_type, event_id, data } = value; |         Self { in_reply_to: None, data } | ||||||
|         Self { rel_type: Some(rel_type), event_id: Some(event_id), data, ..Default::default() } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,7 +7,9 @@ use ruma_events::{ | |||||||
|         RoomEncryptedEventContent, |         RoomEncryptedEventContent, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | use serde_json::{ | ||||||
|  |     from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| fn encrypted_scheme() -> EncryptedEventScheme { | fn encrypted_scheme() -> EncryptedEventScheme { | ||||||
|     EncryptedEventScheme::MegolmV1AesSha2( |     EncryptedEventScheme::MegolmV1AesSha2( | ||||||
| @ -429,7 +431,12 @@ fn content_annotation_deserialization() { | |||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| fn custom_relation_deserialization() { | fn custom_relation_deserialization() { | ||||||
|     let json = json!({ |     let relation_json = json!({ | ||||||
|  |         "rel_type": "io.ruma.custom", | ||||||
|  |         "event_id": "$related_event", | ||||||
|  |         "field": "value", | ||||||
|  |     }); | ||||||
|  |     let content_json = json!({ | ||||||
|         "algorithm": "m.megolm.v1.aes-sha2", |         "algorithm": "m.megolm.v1.aes-sha2", | ||||||
|         "sender_key": "aV9BpqYFqJpKYmgERyGv/6QyKMcgLqxM05V0gvzg9Yk", |         "sender_key": "aV9BpqYFqJpKYmgERyGv/6QyKMcgLqxM05V0gvzg9Yk", | ||||||
|         "ciphertext": "AwgAEpABjy6BHczo7UZE3alyej6y2YQ5v+L9eB+fBqL7yteCPv8Jig\ |         "ciphertext": "AwgAEpABjy6BHczo7UZE3alyej6y2YQ5v+L9eB+fBqL7yteCPv8Jig\ | ||||||
| @ -440,14 +447,10 @@ fn custom_relation_deserialization() { | |||||||
|                       lDl5mzVO3tPnJMKZ0hn+AF",
 |                       lDl5mzVO3tPnJMKZ0hn+AF",
 | ||||||
|         "session_id": "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw", |         "session_id": "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw", | ||||||
|         "device_id": "DEVICE", |         "device_id": "DEVICE", | ||||||
|         "m.relates_to": { |         "m.relates_to": relation_json, | ||||||
|             "rel_type": "io.ruma.custom", |  | ||||||
|             "event_id": "$related_event", |  | ||||||
|             "field": "value", |  | ||||||
|         }, |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     let content = from_json_value::<RoomEncryptedEventContent>(json).unwrap(); |     let content = from_json_value::<RoomEncryptedEventContent>(content_json).unwrap(); | ||||||
| 
 | 
 | ||||||
|     assert_matches!(content.scheme, EncryptedEventScheme::MegolmV1AesSha2(encrypted_content)); |     assert_matches!(content.scheme, EncryptedEventScheme::MegolmV1AesSha2(encrypted_content)); | ||||||
|     assert_eq!(encrypted_content.session_id, "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw"); |     assert_eq!(encrypted_content.session_id, "IkwqWxT2zy3DI1E/zM2Wq+CE8tr3eEpsxsVGjGrMPdw"); | ||||||
| @ -463,9 +466,7 @@ fn custom_relation_deserialization() { | |||||||
| 
 | 
 | ||||||
|     let relation = content.relates_to.unwrap(); |     let relation = content.relates_to.unwrap(); | ||||||
|     assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.custom"); |     assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.custom"); | ||||||
|     assert_eq!(relation.event_id(), "$related_event"); |     assert_eq!(JsonValue::Object(relation.data().into_owned()), relation_json); | ||||||
|     let data = relation.data(); |  | ||||||
|     assert_eq!(data.get("field").unwrap().as_str(), Some("value")); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
|  | |||||||
| @ -5,7 +5,9 @@ use ruma_events::{ | |||||||
|     relation::{CustomRelation, InReplyTo, Replacement, Thread}, |     relation::{CustomRelation, InReplyTo, Replacement, Thread}, | ||||||
|     room::message::{MessageType, Relation, RoomMessageEventContent}, |     room::message::{MessageType, Relation, RoomMessageEventContent}, | ||||||
| }; | }; | ||||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | use serde_json::{ | ||||||
|  |     from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| fn reply_deserialize() { | fn reply_deserialize() { | ||||||
| @ -250,18 +252,19 @@ fn thread_unstable_deserialize() { | |||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| fn custom_deserialize() { | fn custom_deserialize() { | ||||||
|     let json = json!({ |     let relation_json = json!({ | ||||||
|  |         "rel_type": "io.ruma.unknown", | ||||||
|  |         "event_id": "$related_event", | ||||||
|  |         "key": "value", | ||||||
|  |     }); | ||||||
|  |     let content_json = json!({ | ||||||
|         "msgtype": "m.text", |         "msgtype": "m.text", | ||||||
|         "body": "<text msg>", |         "body": "<text msg>", | ||||||
|         "m.relates_to": { |         "m.relates_to": relation_json, | ||||||
|             "rel_type": "io.ruma.unknown", |  | ||||||
|             "event_id": "$related_event", |  | ||||||
|             "key": "value", |  | ||||||
|         }, |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     assert_matches!( |     assert_matches!( | ||||||
|         from_json_value::<RoomMessageEventContent>(json), |         from_json_value::<RoomMessageEventContent>(content_json), | ||||||
|         Ok(RoomMessageEventContent { |         Ok(RoomMessageEventContent { | ||||||
|             msgtype: MessageType::Text(_), |             msgtype: MessageType::Text(_), | ||||||
|             relates_to: Some(relation), |             relates_to: Some(relation), | ||||||
| @ -269,9 +272,7 @@ fn custom_deserialize() { | |||||||
|         }) |         }) | ||||||
|     ); |     ); | ||||||
|     assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.unknown"); |     assert_eq!(relation.rel_type().unwrap().as_str(), "io.ruma.unknown"); | ||||||
|     assert_eq!(relation.event_id().as_str(), "$related_event"); |     assert_eq!(JsonValue::Object(relation.data().into_owned()), relation_json); | ||||||
|     let data = relation.data(); |  | ||||||
|     assert_eq!(data.get("key").unwrap().as_str(), Some("value")); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
|  | |||||||
| @ -21,7 +21,9 @@ use ruma_events::{ | |||||||
|     }, |     }, | ||||||
|     AnySyncTimelineEvent, Mentions, MessageLikeUnsigned, |     AnySyncTimelineEvent, Mentions, MessageLikeUnsigned, | ||||||
| }; | }; | ||||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | use serde_json::{ | ||||||
|  |     from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| macro_rules! json_object { | macro_rules! json_object { | ||||||
|     ( $($tt:tt)+ ) => { |     ( $($tt:tt)+ ) => { | ||||||
| @ -1230,3 +1232,29 @@ fn mentions_room_deserialization() { | |||||||
|     let mentions = content.mentions.unwrap(); |     let mentions = content.mentions.unwrap(); | ||||||
|     assert!(mentions.room); |     assert!(mentions.room); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn invalid_replacement() { | ||||||
|  |     // As generated by Element Web: https://github.com/vector-im/element-web/issues/26554
 | ||||||
|  |     let relation = json!({ | ||||||
|  |         "rel_type": "m.replace", | ||||||
|  |         "event_id": "~!kCCQTCfnABLKGGvQjo:matrix.org:m1699715385559.77", | ||||||
|  |     }); | ||||||
|  |     let json_data = json!({ | ||||||
|  |         "msgtype": "m.text", | ||||||
|  |         "body": " * edited text", | ||||||
|  |         "m.new_content": { | ||||||
|  |             "msgtype": "m.text", | ||||||
|  |             "body": "edited text", | ||||||
|  |             "m.mentions": {}, | ||||||
|  |         }, | ||||||
|  |         "m.mentions": {}, | ||||||
|  |         "m.relates_to": relation | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     let content = from_json_value::<RoomMessageEventContent>(json_data).unwrap(); | ||||||
|  |     let relates_to = content.relates_to.unwrap(); | ||||||
|  |     let data = relates_to.data(); | ||||||
|  |     assert_matches!(&data, Cow::Borrowed(_)); // data is stored in JSON form because it's invalid
 | ||||||
|  |     assert_eq!(JsonValue::Object(data.into_owned()), relation); | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user