ruma_events/room/message/
relation_serde.rs

1use ruma_common::{serde::JsonObject, OwnedEventId};
2use serde::{de, Deserialize, Deserializer, Serialize};
3use serde_json::Value as JsonValue;
4
5use super::{InReplyTo, Relation, RelationWithoutReplacement, Replacement, Thread};
6use crate::relation::CustomRelation;
7
8/// Deserialize an event's `relates_to` field.
9///
10/// Use it like this:
11/// ```
12/// # use serde::{Deserialize, Serialize};
13/// use ruma_events::room::message::{deserialize_relation, MessageType, Relation};
14///
15/// #[derive(Deserialize, Serialize)]
16/// struct MyEventContent {
17///     #[serde(
18///         flatten,
19///         skip_serializing_if = "Option::is_none",
20///         deserialize_with = "deserialize_relation"
21///     )]
22///     relates_to: Option<Relation<MessageType>>,
23/// }
24/// ```
25pub fn deserialize_relation<'de, D, C>(deserializer: D) -> Result<Option<Relation<C>>, D::Error>
26where
27    D: Deserializer<'de>,
28    C: Deserialize<'de>,
29{
30    let EventWithRelatesToDeHelper { relates_to, new_content } =
31        EventWithRelatesToDeHelper::deserialize(deserializer)?;
32    let Some(relates_to) = relates_to else {
33        return Ok(None);
34    };
35
36    let RelatesToDeHelper { in_reply_to, relation } = relates_to;
37
38    let rel = match relation {
39        RelationDeHelper::Known(relation) => match relation {
40            KnownRelationDeHelper::Replacement(ReplacementJsonRepr { event_id }) => {
41                match new_content {
42                    Some(new_content) => {
43                        Relation::Replacement(Replacement { event_id, new_content })
44                    }
45                    None => return Err(de::Error::missing_field("m.new_content")),
46                }
47            }
48            KnownRelationDeHelper::Thread(ThreadDeHelper { event_id, is_falling_back })
49            | KnownRelationDeHelper::ThreadUnstable(ThreadUnstableDeHelper {
50                event_id,
51                is_falling_back,
52            }) => Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }),
53        },
54        RelationDeHelper::Unknown(c) => {
55            if let Some(in_reply_to) = in_reply_to {
56                Relation::Reply { in_reply_to }
57            } else {
58                Relation::_Custom(c)
59            }
60        }
61    };
62
63    Ok(Some(rel))
64}
65
66impl<C> Serialize for Relation<C>
67where
68    C: Clone + Serialize,
69{
70    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: serde::Serializer,
73    {
74        let (relates_to, new_content) = self.clone().into_parts();
75
76        EventWithRelatesToSerHelper { relates_to, new_content }.serialize(serializer)
77    }
78}
79
80#[derive(Deserialize)]
81pub(crate) struct EventWithRelatesToDeHelper<C> {
82    #[serde(rename = "m.relates_to")]
83    relates_to: Option<RelatesToDeHelper>,
84
85    #[serde(rename = "m.new_content")]
86    new_content: Option<C>,
87}
88
89#[derive(Deserialize)]
90pub(crate) struct RelatesToDeHelper {
91    #[serde(rename = "m.in_reply_to")]
92    in_reply_to: Option<InReplyTo>,
93
94    #[serde(flatten)]
95    relation: RelationDeHelper,
96}
97
98#[derive(Deserialize)]
99#[serde(untagged)]
100pub(crate) enum RelationDeHelper {
101    Known(KnownRelationDeHelper),
102    Unknown(CustomRelation),
103}
104
105#[derive(Deserialize)]
106#[serde(tag = "rel_type")]
107pub(crate) enum KnownRelationDeHelper {
108    #[serde(rename = "m.replace")]
109    Replacement(ReplacementJsonRepr),
110
111    #[serde(rename = "m.thread")]
112    Thread(ThreadDeHelper),
113
114    #[serde(rename = "io.element.thread")]
115    ThreadUnstable(ThreadUnstableDeHelper),
116}
117
118/// A replacement relation without `m.new_content`.
119#[derive(Deserialize, Serialize)]
120pub(crate) struct ReplacementJsonRepr {
121    event_id: OwnedEventId,
122}
123
124/// A thread relation without the reply fallback, with stable names.
125#[derive(Deserialize)]
126pub(crate) struct ThreadDeHelper {
127    event_id: OwnedEventId,
128
129    #[serde(default)]
130    is_falling_back: bool,
131}
132
133/// A thread relation without the reply fallback, with unstable names.
134#[derive(Deserialize)]
135pub(crate) struct ThreadUnstableDeHelper {
136    event_id: OwnedEventId,
137
138    #[serde(rename = "io.element.show_reply", default)]
139    is_falling_back: bool,
140}
141
142#[derive(Serialize)]
143pub(super) struct EventWithRelatesToSerHelper<C> {
144    #[serde(rename = "m.relates_to")]
145    relates_to: RelationSerHelper,
146
147    #[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
148    new_content: Option<C>,
149}
150
151/// A relation, which associates new information to an existing event.
152#[derive(Serialize)]
153#[serde(tag = "rel_type")]
154pub(super) enum RelationSerHelper {
155    /// An event that replaces another event.
156    #[serde(rename = "m.replace")]
157    Replacement(ReplacementJsonRepr),
158
159    /// An event that belongs to a thread, with stable names.
160    #[serde(untagged)]
161    Thread(Thread),
162
163    /// An unknown relation type.
164    #[serde(untagged)]
165    Custom(CustomSerHelper),
166}
167
168impl<C> Relation<C> {
169    fn into_parts(self) -> (RelationSerHelper, Option<C>) {
170        match self {
171            Relation::Replacement(Replacement { event_id, new_content }) => (
172                RelationSerHelper::Replacement(ReplacementJsonRepr { event_id }),
173                Some(new_content),
174            ),
175            Relation::Reply { in_reply_to } => {
176                (RelationSerHelper::Custom(in_reply_to.into()), None)
177            }
178            Relation::Thread(t) => (RelationSerHelper::Thread(t), None),
179            Relation::_Custom(c) => (RelationSerHelper::Custom(c.into()), None),
180        }
181    }
182
183    pub(super) fn serialize_data(&self) -> JsonObject
184    where
185        C: Clone,
186    {
187        let (relates_to, _) = self.clone().into_parts();
188
189        match serde_json::to_value(relates_to).expect("relation serialization to succeed") {
190            JsonValue::Object(mut obj) => {
191                obj.remove("rel_type");
192                obj
193            }
194            _ => panic!("all relations must serialize to objects"),
195        }
196    }
197}
198
199#[derive(Default, Serialize)]
200pub(super) struct CustomSerHelper {
201    #[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
202    in_reply_to: Option<InReplyTo>,
203
204    #[serde(flatten, skip_serializing_if = "JsonObject::is_empty")]
205    data: JsonObject,
206}
207
208impl From<InReplyTo> for CustomSerHelper {
209    fn from(value: InReplyTo) -> Self {
210        Self { in_reply_to: Some(value), data: JsonObject::new() }
211    }
212}
213
214impl From<CustomRelation> for CustomSerHelper {
215    fn from(CustomRelation(data): CustomRelation) -> Self {
216        Self { in_reply_to: None, data }
217    }
218}
219
220impl From<&RelationWithoutReplacement> for RelationSerHelper {
221    fn from(value: &RelationWithoutReplacement) -> Self {
222        match value.clone() {
223            RelationWithoutReplacement::Reply { in_reply_to } => {
224                RelationSerHelper::Custom(in_reply_to.into())
225            }
226            RelationWithoutReplacement::Thread(t) => RelationSerHelper::Thread(t),
227            RelationWithoutReplacement::_Custom(c) => RelationSerHelper::Custom(c.into()),
228        }
229    }
230}
231
232impl Serialize for RelationWithoutReplacement {
233    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
234    where
235        S: serde::Serializer,
236    {
237        RelationSerHelper::from(self).serialize(serializer)
238    }
239}
240
241impl RelationWithoutReplacement {
242    pub(super) fn serialize_data(&self) -> JsonObject {
243        let helper = RelationSerHelper::from(self);
244
245        match serde_json::to_value(helper).expect("relation serialization to succeed") {
246            JsonValue::Object(mut obj) => {
247                obj.remove("rel_type");
248                obj
249            }
250            _ => panic!("all relations must serialize to objects"),
251        }
252    }
253}