events: Fix deserialization without relation

This commit is contained in:
Kévin Commaille 2022-11-25 18:10:50 +01:00
parent 0feb39298a
commit dc18b12506
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
16 changed files with 141 additions and 103 deletions

View File

@ -6,6 +6,8 @@ Bug fixes:
during creation of the rich reply during creation of the rich reply
* Don't include sensitive information in `Debug`-format of types from the `events::key` * Don't include sensitive information in `Debug`-format of types from the `events::key`
and `events::secret` modules and `events::secret` modules
* Fix deserialization of `RoomMessageEventContent` and `RoomEncryptedEventContent` when there
is no relation
Breaking changes: Breaking changes:

View File

@ -39,7 +39,11 @@ pub struct AudioEventContent {
pub audio: AudioContent, pub audio: AudioContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<AudioEventContentWithoutRelation>>, pub relates_to: Option<Relation<AudioEventContentWithoutRelation>>,
} }

View File

@ -23,7 +23,11 @@ pub struct EmoteEventContent {
pub message: MessageContent, pub message: MessageContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<EmoteEventContentWithoutRelation>>, pub relates_to: Option<Relation<EmoteEventContentWithoutRelation>>,
} }

View File

@ -34,7 +34,11 @@ pub struct FileEventContent {
pub file: FileContent, pub file: FileContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<FileEventContentWithoutRelation>>, pub relates_to: Option<Relation<FileEventContentWithoutRelation>>,
} }

View File

@ -50,7 +50,11 @@ pub struct ImageEventContent {
pub caption: Option<MessageContent>, pub caption: Option<MessageContent>,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<ImageEventContentWithoutRelation>>, pub relates_to: Option<Relation<ImageEventContentWithoutRelation>>,
} }

View File

@ -39,7 +39,11 @@ pub struct LocationEventContent {
pub ts: Option<MilliSecondsSinceUnixEpoch>, pub ts: Option<MilliSecondsSinceUnixEpoch>,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<LocationEventContentWithoutRelation>>, pub relates_to: Option<Relation<LocationEventContentWithoutRelation>>,
} }

View File

@ -72,7 +72,11 @@ pub struct MessageEventContent {
pub message: MessageContent, pub message: MessageContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<MessageEventContentWithoutRelation>>, pub relates_to: Option<Relation<MessageEventContentWithoutRelation>>,
} }

View File

@ -23,7 +23,11 @@ pub struct NoticeEventContent {
pub message: MessageContent, pub message: MessageContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<NoticeEventContentWithoutRelation>>, pub relates_to: Option<Relation<NoticeEventContentWithoutRelation>>,
} }

View File

@ -23,7 +23,11 @@ pub struct RoomEncryptedEventContent {
pub scheme: EncryptedEventScheme, pub scheme: EncryptedEventScheme,
/// Information about related events. /// Information about related events.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation>,
} }
@ -400,6 +404,8 @@ mod tests {
assert_eq!(c.ciphertext.len(), 1); assert_eq!(c.ciphertext.len(), 1);
assert_eq!(c.ciphertext["test_curve_key"].body, "encrypted_body"); assert_eq!(c.ciphertext["test_curve_key"].body, "encrypted_body");
assert_eq!(c.ciphertext["test_curve_key"].message_type, uint!(1)); assert_eq!(c.ciphertext["test_curve_key"].message_type, uint!(1));
assert_matches!(content.relates_to, None);
} }
#[test] #[test]

View File

@ -5,47 +5,43 @@ use super::Annotation;
use super::{InReplyTo, Reference, Relation, Replacement, Thread}; use super::{InReplyTo, Reference, Relation, Replacement, Thread};
use crate::OwnedEventId; use crate::OwnedEventId;
impl<'de> Deserialize<'de> for Relation { pub(super) fn deserialize_relation<'de, D>(deserializer: D) -> Result<Option<Relation>, D::Error>
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
where D: Deserializer<'de>,
D: Deserializer<'de>, {
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
if let Some(
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
) = ev.relates_to.relation
{ {
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?; let in_reply_to = ev
.relates_to
if let Some( .in_reply_to
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back }) .ok_or_else(|| serde::de::Error::missing_field("m.in_reply_to"))?;
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }), return Ok(Some(Relation::Thread(Thread { event_id, in_reply_to, 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 }));
}
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),
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 {
Relation::_Custom
};
Ok(rel)
} }
let rel = if let Some(in_reply_to) = ev.relates_to.in_reply_to {
Some(Relation::Reply { in_reply_to })
} else {
ev.relates_to.relation.map(|relation| match relation {
#[cfg(feature = "unstable-msc2677")]
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
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,
})
};
Ok(rel)
} }
impl Serialize for Relation { impl Serialize for Relation {

View File

@ -21,7 +21,7 @@ mod image;
mod key_verification_request; mod key_verification_request;
mod location; mod location;
mod notice; mod notice;
mod relation_serde; pub(crate) mod relation_serde;
mod reply; mod reply;
pub mod sanitize; pub mod sanitize;
mod server_notice; mod server_notice;

View File

@ -5,13 +5,13 @@ use serde_json::value::RawValue as RawJsonValue;
#[cfg(feature = "unstable-msc3552")] #[cfg(feature = "unstable-msc3552")]
use super::ImageMessageEventContent; use super::ImageMessageEventContent;
use super::{relation_serde::deserialize_relation, MessageType, RoomMessageEventContent};
#[cfg(feature = "unstable-msc3246")] #[cfg(feature = "unstable-msc3246")]
use super::{AudioInfo, AudioMessageEventContent}; use super::{AudioInfo, AudioMessageEventContent};
#[cfg(feature = "unstable-msc3551")] #[cfg(feature = "unstable-msc3551")]
use super::{FileInfo, FileMessageEventContent}; use super::{FileInfo, FileMessageEventContent};
#[cfg(feature = "unstable-msc3488")] #[cfg(feature = "unstable-msc3488")]
use super::{LocationInfo, LocationMessageEventContent}; use super::{LocationInfo, LocationMessageEventContent};
use super::{MessageType, Relation, RoomMessageEventContent};
#[cfg(feature = "unstable-msc3553")] #[cfg(feature = "unstable-msc3553")]
use super::{VideoInfo, VideoMessageEventContent}; use super::{VideoInfo, VideoMessageEventContent};
#[cfg(feature = "unstable-msc3246")] #[cfg(feature = "unstable-msc3246")]
@ -47,8 +47,7 @@ impl<'de> Deserialize<'de> for RoomMessageEventContent {
{ {
let json = Box::<RawJsonValue>::deserialize(deserializer)?; let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let mut deserializer = serde_json::Deserializer::from_str(json.get()); let mut deserializer = serde_json::Deserializer::from_str(json.get());
let relates_to = Option::<Relation<MessageType>>::deserialize(&mut deserializer) let relates_to = deserialize_relation(&mut deserializer).map_err(de::Error::custom)?;
.map_err(de::Error::custom)?;
Ok(Self { msgtype: from_raw_json_value(&json)?, relates_to }) Ok(Self { msgtype: from_raw_json_value(&json)?, relates_to })
} }

View File

@ -3,51 +3,49 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{InReplyTo, Relation, Replacement, Thread}; use super::{InReplyTo, Relation, Replacement, Thread};
use crate::OwnedEventId; use crate::OwnedEventId;
impl<'de, C> Deserialize<'de> for Relation<C> pub(crate) fn deserialize_relation<'de, D, C>(
deserializer: D,
) -> Result<Option<Relation<C>>, D::Error>
where where
D: Deserializer<'de>,
C: Deserialize<'de>, C: Deserialize<'de>,
{ {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
where
D: Deserializer<'de>, if let Some(
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
) = ev.relates_to.relation
{ {
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?; let in_reply_to = ev
.relates_to
if let Some( .in_reply_to
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back }) .ok_or_else(|| serde::de::Error::missing_field("m.in_reply_to"))?;
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }), return Ok(Some(Relation::Thread(Thread { event_id, in_reply_to, 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 }));
}
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 {
RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => {
let new_content = ev
.new_content
.ok_or_else(|| serde::de::Error::missing_field("m.new_content"))?;
Relation::Replacement(Replacement { event_id, new_content })
}
// FIXME: Maybe we should log this, though at this point we don't even have
// access to the rel_type of the unknown relation.
RelationJsonRepr::Unknown => Relation::_Custom,
RelationJsonRepr::ThreadStable(_) | RelationJsonRepr::ThreadUnstable(_) => {
unreachable!()
}
}
} else {
Relation::_Custom
};
Ok(rel)
} }
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)
} }
impl<C> Serialize for Relation<C> impl<C> Serialize for Relation<C>

View File

@ -49,7 +49,11 @@ pub struct VideoEventContent {
pub caption: Option<MessageContent>, pub caption: Option<MessageContent>,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<VideoEventContentWithoutRelation>>, pub relates_to: Option<Relation<VideoEventContentWithoutRelation>>,
} }

View File

@ -37,7 +37,11 @@ pub struct VoiceEventContent {
pub voice: VoiceContent, pub voice: VoiceContent,
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<VoiceEventContentWithoutRelation>>, pub relates_to: Option<Relation<VoiceEventContentWithoutRelation>>,
} }

View File

@ -367,18 +367,19 @@ fn verification_request_deserialization() {
] ]
}); });
let content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
let verification = assert_matches!( let verification = assert_matches!(
from_json_value::<RoomMessageEventContent>(json_data), content.msgtype,
Ok(RoomMessageEventContent { MessageType::VerificationRequest(verification) => verification
msgtype: MessageType::VerificationRequest(verification),
..
}) => verification
); );
assert_eq!(verification.body, "@example:localhost is requesting to verify your key, ..."); assert_eq!(verification.body, "@example:localhost is requesting to verify your key, ...");
assert_eq!(verification.to, user_id); assert_eq!(verification.to, user_id);
assert_eq!(verification.from_device, device_id); assert_eq!(verification.from_device, device_id);
assert_eq!(verification.methods.len(), 3); assert_eq!(verification.methods.len(), 3);
assert!(verification.methods.contains(&VerificationMethod::SasV1)); assert!(verification.methods.contains(&VerificationMethod::SasV1));
assert_matches!(content.relates_to, None);
} }
#[test] #[test]
@ -413,17 +414,17 @@ fn content_deserialization() {
"url": "mxc://example.org/ffed755USFFxlgbQYZGtryd" "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
}); });
let content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
let audio = assert_matches!( let audio = assert_matches!(
from_json_value::<RoomMessageEventContent>(json_data), content.msgtype,
Ok(RoomMessageEventContent { MessageType::Audio(audio) => audio
msgtype: MessageType::Audio(audio),
..
}) => audio
); );
assert_eq!(audio.body, "test"); assert_eq!(audio.body, "test");
assert_matches!(audio.info, None); assert_matches!(audio.info, None);
let url = assert_matches!(audio.source, MediaSource::Plain(url) => url); let url = assert_matches!(audio.source, MediaSource::Plain(url) => url);
assert_eq!(url, "mxc://example.org/ffed755USFFxlgbQYZGtryd"); assert_eq!(url, "mxc://example.org/ffed755USFFxlgbQYZGtryd");
assert_matches!(content.relates_to, None);
} }
#[test] #[test]