events: Make Replacement generic over the parent type

Because the new content must be of the same type.
This commit is contained in:
Kévin Commaille 2022-10-12 19:22:40 +02:00 committed by Kévin Commaille
parent 7c802c89ca
commit 7c0f7ba5f1
15 changed files with 53 additions and 43 deletions

View File

@ -40,7 +40,7 @@ pub struct AudioEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<AudioEventContentWithoutRelation>>,
} }
impl AudioEventContent { impl AudioEventContent {

View File

@ -24,7 +24,7 @@ pub struct EmoteEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<EmoteEventContentWithoutRelation>>,
} }
impl EmoteEventContent { impl EmoteEventContent {

View File

@ -35,7 +35,7 @@ pub struct FileEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<FileEventContentWithoutRelation>>,
} }
impl FileEventContent { impl FileEventContent {

View File

@ -51,7 +51,7 @@ pub struct ImageEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<ImageEventContentWithoutRelation>>,
} }
impl ImageEventContent { impl ImageEventContent {

View File

@ -40,7 +40,7 @@ pub struct LocationEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<LocationEventContentWithoutRelation>>,
} }
impl LocationEventContent { impl LocationEventContent {

View File

@ -73,7 +73,7 @@ pub struct MessageEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<MessageEventContentWithoutRelation>>,
} }
impl MessageEventContent { impl MessageEventContent {

View File

@ -24,7 +24,7 @@ pub struct NoticeEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<NoticeEventContentWithoutRelation>>,
} }
impl NoticeEventContent { impl NoticeEventContent {

View File

@ -107,8 +107,8 @@ pub enum Relation {
_Custom, _Custom,
} }
impl From<message::Relation> for Relation { impl<C> From<message::Relation<C>> for Relation {
fn from(rel: message::Relation) -> Self { fn from(rel: message::Relation<C>) -> Self {
match rel { match rel {
message::Relation::Reply { in_reply_to } => Self::Reply { in_reply_to }, message::Relation::Reply { in_reply_to } => Self::Reply { in_reply_to },
message::Relation::Replacement(re) => { message::Relation::Replacement(re) => {

View File

@ -62,7 +62,7 @@ pub struct RoomMessageEventContent {
/// ///
/// [rich replies]: https://spec.matrix.org/v1.2/client-server-api/#rich-replies /// [rich replies]: https://spec.matrix.org/v1.2/client-server-api/#rich-replies
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<MessageType>>,
} }
impl RoomMessageEventContent { impl RoomMessageEventContent {
@ -467,7 +467,7 @@ impl From<RoomMessageEventContent> for MessageType {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[allow(clippy::manual_non_exhaustive)] #[allow(clippy::manual_non_exhaustive)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum Relation { pub enum Relation<C> {
/// An `m.in_reply_to` relation indicating that the event is a reply to another event. /// An `m.in_reply_to` relation indicating that the event is a reply to another event.
Reply { Reply {
/// Information about another message being replied to. /// Information about another message being replied to.
@ -475,7 +475,7 @@ pub enum Relation {
}, },
/// An event that replaces another event. /// An event that replaces another event.
Replacement(Replacement), Replacement(Replacement<C>),
/// An event that belongs to a thread. /// An event that belongs to a thread.
Thread(Thread), Thread(Thread),
@ -504,17 +504,17 @@ impl InReplyTo {
/// [replaces another event]: https://spec.matrix.org/v1.4/client-server-api/#event-replacements /// [replaces another event]: https://spec.matrix.org/v1.4/client-server-api/#event-replacements
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Replacement { pub struct Replacement<C> {
/// The ID of the event being replaced. /// The ID of the event being replaced.
pub event_id: OwnedEventId, pub event_id: OwnedEventId,
/// New content. /// New content.
pub new_content: Box<RoomMessageEventContent>, pub new_content: C,
} }
impl Replacement { impl<C> Replacement<C> {
/// Creates a new `Replacement` with the given event ID and new content. /// Creates a new `Replacement` with the given event ID and new content.
pub fn new(event_id: OwnedEventId, new_content: Box<RoomMessageEventContent>) -> Self { pub fn new(event_id: OwnedEventId, new_content: C) -> Self {
Self { event_id, new_content } Self { event_id, new_content }
} }
} }

View File

@ -47,8 +47,8 @@ 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 = let relates_to = Option::<Relation<MessageType>>::deserialize(&mut deserializer)
Option::<Relation>::deserialize(&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

@ -1,9 +1,12 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{InReplyTo, Relation, Replacement, RoomMessageEventContent, Thread}; use super::{InReplyTo, Relation, Replacement, Thread};
use crate::OwnedEventId; use crate::OwnedEventId;
impl<'de> Deserialize<'de> for Relation { impl<'de, C> Deserialize<'de> for Relation<C>
where
C: Deserialize<'de>,
{
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>,
@ -47,17 +50,22 @@ impl<'de> Deserialize<'de> for Relation {
} }
} }
impl Serialize for Relation { impl<C> Serialize for Relation<C>
where
C: Clone + Serialize,
{
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: Serializer,
{ {
#[allow(clippy::needless_update)] #[allow(clippy::needless_update)]
let json_repr = match self { let json_repr = match self {
Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { Relation::Reply { in_reply_to } => {
EventWithRelatesToJsonRepr::<C>::new(RelatesToJsonRepr {
in_reply_to: Some(in_reply_to.clone()), in_reply_to: Some(in_reply_to.clone()),
..Default::default() ..Default::default()
}), })
}
Relation::Replacement(Replacement { event_id, new_content }) => { Relation::Replacement(Replacement { event_id, new_content }) => {
EventWithRelatesToJsonRepr { EventWithRelatesToJsonRepr {
relates_to: RelatesToJsonRepr { relates_to: RelatesToJsonRepr {
@ -79,28 +87,34 @@ impl Serialize for Relation {
..Default::default() ..Default::default()
}) })
} }
Relation::_Custom => EventWithRelatesToJsonRepr::default(), Relation::_Custom => EventWithRelatesToJsonRepr::<C>::default(),
}; };
json_repr.serialize(serializer) json_repr.serialize(serializer)
} }
} }
#[derive(Default, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct EventWithRelatesToJsonRepr { struct EventWithRelatesToJsonRepr<C> {
#[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")] #[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")]
relates_to: RelatesToJsonRepr, relates_to: RelatesToJsonRepr,
#[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<Box<RoomMessageEventContent>>, new_content: Option<C>,
} }
impl EventWithRelatesToJsonRepr { impl<C> EventWithRelatesToJsonRepr<C> {
fn new(relates_to: RelatesToJsonRepr) -> Self { fn new(relates_to: RelatesToJsonRepr) -> Self {
Self { relates_to, new_content: None } 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 /// Struct modeling the different ways relationships can be expressed in a `m.relates_to` field of
/// an event. /// an event.
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]

View File

@ -50,7 +50,7 @@ pub struct VideoEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<VideoEventContentWithoutRelation>>,
} }
impl VideoEventContent { impl VideoEventContent {

View File

@ -38,7 +38,7 @@ pub struct VoiceEventContent {
/// Information about related messages. /// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation<VoiceEventContentWithoutRelation>>,
} }
impl VoiceEventContent { impl VoiceEventContent {

View File

@ -75,7 +75,7 @@ fn replacement_serialize() {
relates_to: Some(Relation::Replacement( relates_to: Some(Relation::Replacement(
Replacement::new( Replacement::new(
event_id!("$1598361704261elfgc").to_owned(), event_id!("$1598361704261elfgc").to_owned(),
Box::new(RoomMessageEventContent::text_plain("This is the new content.")), RoomMessageEventContent::text_plain("This is the new content.").into(),
) )
)) ))
} }
@ -142,7 +142,7 @@ fn replacement_deserialize() {
}) => replacement }) => replacement
); );
assert_eq!(replacement.event_id, "$1598361704261elfgc"); assert_eq!(replacement.event_id, "$1598361704261elfgc");
let text = assert_matches!(replacement.new_content.msgtype, MessageType::Text(text) => text); let text = assert_matches!(replacement.new_content, MessageType::Text(text) => text);
assert_eq!(text.body, "Hello! My name is bar"); assert_eq!(text.body, "Hello! My name is bar");
} }

View File

@ -1,10 +1,7 @@
use assert_matches::assert_matches; use assert_matches::assert_matches;
use ruma_common::{ use ruma_common::{
event_id, event_id,
events::room::message::{ events::room::message::{InReplyTo, MessageType, Relation, RoomMessageEventContent},
InReplyTo, MessageType, Relation, RoomMessageEventContent,
RoomMessageEventContentWithoutRelation,
},
}; };
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};
@ -13,7 +10,7 @@ fn serialize_room_message_content_without_relation() {
let mut content = RoomMessageEventContent::text_plain("Hello, world!"); let mut content = RoomMessageEventContent::text_plain("Hello, world!");
content.relates_to = content.relates_to =
Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) }); Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) });
let without_relation = RoomMessageEventContentWithoutRelation::from(content); let without_relation = MessageType::from(content);
#[cfg(not(feature = "unstable-msc3246"))] #[cfg(not(feature = "unstable-msc3246"))]
assert_eq!( assert_eq!(
@ -43,8 +40,8 @@ fn deserialize_room_message_content_without_relation() {
}); });
let text = assert_matches!( let text = assert_matches!(
from_json_value::<RoomMessageEventContentWithoutRelation>(json_data), from_json_value::<MessageType>(json_data),
Ok(RoomMessageEventContentWithoutRelation::Text(text)) => text Ok(MessageType::Text(text)) => text
); );
assert_eq!(text.body, "Hello, world!"); assert_eq!(text.body, "Hello, world!");
} }
@ -54,8 +51,7 @@ fn convert_room_message_content_without_relation_to_full() {
let mut content = RoomMessageEventContent::text_plain("Hello, world!"); let mut content = RoomMessageEventContent::text_plain("Hello, world!");
content.relates_to = content.relates_to =
Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) }); Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) });
let new_content = let new_content = RoomMessageEventContent::from(MessageType::from(content));
RoomMessageEventContent::from(RoomMessageEventContentWithoutRelation::from(content));
let (text, relates_to) = assert_matches!( let (text, relates_to) = assert_matches!(
new_content, new_content,