events: More relation serde fixes

It seems that we cant count on serde_json::to_value to detect
duplicates,
because it swallows them instead of returning an error.
The only solution then is to serialize to string and try to deserialize
again.
This commit is contained in:
Kévin Commaille 2024-06-25 13:00:30 +02:00 committed by Kévin Commaille
parent 862be071d2
commit f17de39ed4
9 changed files with 260 additions and 28 deletions

View File

@ -4,6 +4,8 @@ Bug fixes:
- Fix deserialization of `AnyGlobalAccountDataEvent` for variants with a type
fragment.
- Fix serialization of `room::message::Relation` and `room::encrypted::Relation`
which could cause duplicate `rel_type` keys.
Improvements:

View File

@ -165,7 +165,10 @@ mod tests {
use std::collections::BTreeMap;
use assert_matches2::assert_matches;
use ruma_common::{event_id, serde::Base64};
use ruma_common::{
event_id,
serde::{Base64, Raw},
};
use serde_json::{
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
@ -348,4 +351,26 @@ mod tests {
assert_eq!(sas.message_authentication_code, MessageAuthenticationCode::HkdfHmacSha256V2);
assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
}
#[test]
fn in_room_serialization_roundtrip() {
let event_id = event_id!("$1598361704261elfgc:localhost");
let content = KeyVerificationAcceptEventContent {
relates_to: Reference { event_id: event_id.to_owned() },
method: AcceptMethod::SasV1(SasV1Content {
hash: HashAlgorithm::Sha256,
key_agreement_protocol: KeyAgreementProtocol::Curve25519,
message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256V2,
short_authentication_string: vec![ShortAuthenticationString::Decimal],
commitment: Base64::new(b"hello".to_vec()),
}),
};
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.method, AcceptMethod::SasV1(_));
assert_eq!(deser_content.relates_to.event_id, event_id);
}
}

View File

@ -37,7 +37,7 @@ impl From<Annotation> for ReactionEventContent {
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use ruma_common::owned_event_id;
use ruma_common::{owned_event_id, serde::Raw};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::ReactionEventContent;
@ -79,4 +79,18 @@ mod tests {
})
);
}
#[test]
fn serialization_roundtrip() {
let content = ReactionEventContent::new(Annotation::new(
owned_event_id!("$my_reaction"),
"🏠".to_owned(),
));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_eq!(deser_content.relates_to.event_id, content.relates_to.event_id);
assert_eq!(deser_content.relates_to.key, content.relates_to.key);
}
}

View File

@ -84,6 +84,7 @@ impl<C> Replacement<C> {
/// [thread]: https://spec.matrix.org/latest/client-server-api/#threading
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "rel_type", rename = "m.thread")]
pub struct Thread {
/// The ID of the root message in the thread.
pub event_id: OwnedEventId,

View File

@ -165,6 +165,7 @@ impl<C> From<message::Relation<C>> for Relation {
/// [replaces another event]: https://spec.matrix.org/latest/client-server-api/#event-replacements
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "rel_type", rename = "m.replace")]
pub struct Replacement {
/// The ID of the event being replaced.
pub event_id: OwnedEventId,

View File

@ -74,27 +74,11 @@ impl Serialize for Relation {
st.serialize_field("m.in_reply_to", in_reply_to)?;
st.end()
}
Relation::Replacement(data) => {
RelationSerHelper { rel_type: "m.replace", data }.serialize(serializer)
}
Relation::Reference(data) => {
RelationSerHelper { rel_type: "m.reference", data }.serialize(serializer)
}
Relation::Annotation(data) => {
RelationSerHelper { rel_type: "m.annotation", data }.serialize(serializer)
}
Relation::Thread(data) => {
RelationSerHelper { rel_type: "m.thread", data }.serialize(serializer)
}
Relation::Replacement(data) => data.serialize(serializer),
Relation::Reference(data) => data.serialize(serializer),
Relation::Annotation(data) => data.serialize(serializer),
Relation::Thread(data) => data.serialize(serializer),
Relation::_Custom(c) => c.serialize(serializer),
}
}
}
#[derive(Serialize)]
struct RelationSerHelper<'a, T> {
rel_type: &'a str,
#[serde(flatten)]
data: &'a T,
}

View File

@ -157,7 +157,7 @@ pub(super) enum RelationSerHelper {
Replacement(ReplacementJsonRepr),
/// An event that belongs to a thread, with stable names.
#[serde(rename = "m.thread")]
#[serde(untagged)]
Thread(Thread),
/// An unknown relation type.

View File

@ -1,7 +1,7 @@
use assert_matches2::assert_matches;
use ruma_common::{owned_device_id, owned_event_id};
use ruma_common::{owned_device_id, owned_event_id, serde::Raw};
use ruma_events::{
relation::{CustomRelation, InReplyTo, Reference, Thread},
relation::{Annotation, CustomRelation, InReplyTo, Reference, Thread},
room::encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement,
RoomEncryptedEventContent,
@ -82,6 +82,17 @@ fn content_no_relation_deserialization() {
assert_matches!(content.relates_to, None);
}
#[test]
fn content_no_relation_serialization_roundtrip() {
let content = RoomEncryptedEventContent::new(encrypted_scheme(), None);
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, None);
}
#[test]
fn content_reply_serialization() {
let content = RoomEncryptedEventContent::new(
@ -149,6 +160,22 @@ fn content_reply_deserialization() {
assert_eq!(in_reply_to.event_id, "$replied_to_event");
}
#[test]
fn content_reply_serialization_roundtrip() {
let reply = InReplyTo::new(owned_event_id!("$replied_to_event"));
let content = RoomEncryptedEventContent::new(
encrypted_scheme(),
Some(Relation::Reply { in_reply_to: reply.clone() }),
);
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, Some(Relation::Reply { in_reply_to: deser_reply }));
assert_eq!(deser_reply.event_id, reply.event_id);
}
#[test]
fn content_replacement_serialization() {
let content = RoomEncryptedEventContent::new(
@ -214,6 +241,22 @@ fn content_replacement_deserialization() {
assert_eq!(replacement.event_id, "$replaced_event");
}
#[test]
fn content_replacement_serialization_roundtrip() {
let replacement = Replacement::new(owned_event_id!("$replaced_event"));
let content = RoomEncryptedEventContent::new(
encrypted_scheme(),
Some(Relation::Replacement(replacement.clone())),
);
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, Some(Relation::Replacement(deser_replacement)));
assert_eq!(deser_replacement.event_id, replacement.event_id);
}
#[test]
fn content_reference_serialization() {
let content = RoomEncryptedEventContent::new(
@ -279,6 +322,22 @@ fn content_reference_deserialization() {
assert_eq!(reference.event_id, "$referenced_event");
}
#[test]
fn content_reference_serialization_roundtrip() {
let reference = Reference::new(owned_event_id!("$referenced_event"));
let content = RoomEncryptedEventContent::new(
encrypted_scheme(),
Some(Relation::Reference(reference.clone())),
);
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, Some(Relation::Reference(deser_reference)));
assert_eq!(deser_reference.event_id, reference.event_id);
}
#[test]
fn content_thread_serialization() {
let content = RoomEncryptedEventContent::new(
@ -357,9 +416,23 @@ fn content_thread_deserialization() {
}
#[test]
fn content_annotation_serialization() {
use ruma_events::relation::Annotation;
fn content_thread_serialization_roundtrip() {
let thread = Thread::plain(owned_event_id!("$thread_root"), owned_event_id!("$prev_event"));
let content =
RoomEncryptedEventContent::new(encrypted_scheme(), Some(Relation::Thread(thread.clone())));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, Some(Relation::Thread(deser_thread)));
assert_eq!(deser_thread.event_id, thread.event_id);
assert_eq!(deser_thread.in_reply_to.unwrap().event_id, thread.in_reply_to.unwrap().event_id);
assert_eq!(deser_thread.is_falling_back, thread.is_falling_back);
}
#[test]
fn content_annotation_serialization() {
let content = RoomEncryptedEventContent::new(
encrypted_scheme(),
Some(Relation::Annotation(Annotation::new(
@ -429,6 +502,23 @@ fn content_annotation_deserialization() {
assert_eq!(annotation.key, "some_key");
}
#[test]
fn content_annotation_serialization_roundtrip() {
let annotation = Annotation::new(owned_event_id!("$annotated_event"), "some_key".to_owned());
let content = RoomEncryptedEventContent::new(
encrypted_scheme(),
Some(Relation::Annotation(annotation.clone())),
);
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
assert_matches!(deser_content.relates_to, Some(Relation::Annotation(deser_annotation)));
assert_eq!(deser_annotation.event_id, annotation.event_id);
assert_eq!(deser_annotation.key, annotation.key);
}
#[test]
fn custom_relation_deserialization() {
let relation_json = json!({
@ -502,3 +592,31 @@ fn custom_relation_serialization() {
})
);
}
#[test]
fn custom_serialization_roundtrip() {
let rel_type = "io.ruma.unknown";
let event_id = "$related_event";
let key = "value";
let json_relation = json!({
"rel_type": rel_type,
"event_id": event_id,
"key": key,
});
let relation = from_json_value::<CustomRelation>(json_relation).unwrap();
let content =
RoomEncryptedEventContent::new(encrypted_scheme(), Some(Relation::_Custom(relation)));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(content.scheme, EncryptedEventScheme::MegolmV1AesSha2(_));
let deser_relates_to = deser_content.relates_to.unwrap();
assert_matches!(&deser_relates_to, Relation::_Custom(_));
assert_eq!(deser_relates_to.rel_type().unwrap().as_str(), rel_type);
let deser_relation = deser_relates_to.data();
assert_eq!(deser_relation.get("rel_type").unwrap().as_str().unwrap(), rel_type);
assert_eq!(deser_relation.get("event_id").unwrap().as_str().unwrap(), event_id);
assert_eq!(deser_relation.get("key").unwrap().as_str().unwrap(), key);
}

View File

@ -1,6 +1,6 @@
use assert_matches2::assert_matches;
use assign::assign;
use ruma_common::owned_event_id;
use ruma_common::{owned_event_id, serde::Raw};
use ruma_events::{
relation::{CustomRelation, InReplyTo, Replacement, Thread},
room::message::{MessageType, Relation, RoomMessageEventContent},
@ -52,6 +52,22 @@ fn reply_serialize() {
);
}
#[test]
fn reply_serialization_roundtrip() {
let body = "This is a reply";
let mut content = RoomMessageEventContent::text_plain(body);
let reply = InReplyTo::new(owned_event_id!("$1598361704261elfgc"));
content.relates_to = Some(Relation::Reply { in_reply_to: reply.clone() });
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.msgtype, MessageType::Text(deser_msg));
assert_eq!(deser_msg.body, body);
assert_matches!(content.relates_to.unwrap(), Relation::Reply { in_reply_to: deser_reply });
assert_eq!(deser_reply.event_id, reply.event_id);
}
#[test]
fn replacement_serialize() {
let content = assign!(
@ -111,6 +127,28 @@ fn replacement_deserialize() {
assert_eq!(text.body, "Hello! My name is bar");
}
#[test]
fn replacement_serialization_roundtrip() {
let body = "<text msg>";
let mut content = RoomMessageEventContent::text_plain(body);
let new_body = "This is the new content.";
let replacement = Replacement::new(
owned_event_id!("$1598361704261elfgc"),
RoomMessageEventContent::text_plain(new_body).into(),
);
content.relates_to = Some(Relation::Replacement(replacement.clone()));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.msgtype, MessageType::Text(deser_msg));
assert_eq!(deser_msg.body, body);
assert_matches!(content.relates_to.unwrap(), Relation::Replacement(deser_replacement));
assert_eq!(deser_replacement.event_id, replacement.event_id);
assert_matches!(deser_replacement.new_content.msgtype, MessageType::Text(deser_new_msg));
assert_eq!(deser_new_msg.body, new_body);
}
#[test]
fn thread_plain_serialize() {
let content = assign!(
@ -250,6 +288,25 @@ fn thread_unstable_deserialize() {
assert!(!thread.is_falling_back);
}
#[test]
fn thread_serialization_roundtrip() {
let body = "<text msg>";
let mut content = RoomMessageEventContent::text_plain(body);
let thread =
Thread::plain(owned_event_id!("$1598361704261elfgc"), owned_event_id!("$latesteventid"));
content.relates_to = Some(Relation::Thread(thread.clone()));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.msgtype, MessageType::Text(deser_msg));
assert_eq!(deser_msg.body, body);
assert_matches!(content.relates_to.unwrap(), Relation::Thread(deser_thread));
assert_eq!(deser_thread.event_id, thread.event_id);
assert_eq!(deser_thread.in_reply_to.unwrap().event_id, thread.in_reply_to.unwrap().event_id);
assert_eq!(deser_thread.is_falling_back, thread.is_falling_back);
}
#[test]
fn custom_deserialize() {
let relation_json = json!({
@ -300,3 +357,33 @@ fn custom_serialize() {
})
);
}
#[test]
fn custom_serialization_roundtrip() {
let rel_type = "io.ruma.unknown";
let event_id = "$related_event";
let key = "value";
let json_relation = json!({
"rel_type": rel_type,
"event_id": event_id,
"key": key,
});
let relation = from_json_value::<CustomRelation>(json_relation).unwrap();
let body = "<text msg>";
let mut content = RoomMessageEventContent::text_plain(body);
content.relates_to = Some(Relation::_Custom(relation));
let json_content = Raw::new(&content).unwrap();
let deser_content = json_content.deserialize().unwrap();
assert_matches!(deser_content.msgtype, MessageType::Text(deser_msg));
assert_eq!(deser_msg.body, body);
let deser_relates_to = deser_content.relates_to.unwrap();
assert_matches!(&deser_relates_to, Relation::_Custom(_));
assert_eq!(deser_relates_to.rel_type().unwrap().as_str(), rel_type);
let deser_relation = deser_relates_to.data();
assert_eq!(deser_relation.get("rel_type").unwrap().as_str().unwrap(), rel_type);
assert_eq!(deser_relation.get("event_id").unwrap().as_str().unwrap(), event_id);
assert_eq!(deser_relation.get("key").unwrap().as_str().unwrap(), key);
}