ruma_events/
relation.rs

1//! Types describing [relationships between events].
2//!
3//! [relationships between events]: https://spec.matrix.org/latest/client-server-api/#forming-relationships-between-events
4
5use std::fmt::Debug;
6
7use js_int::UInt;
8use ruma_common::{
9    serde::{JsonObject, Raw, StringEnum},
10    OwnedEventId,
11};
12use serde::{Deserialize, Serialize};
13
14use super::AnyMessageLikeEvent;
15use crate::PrivOwnedStr;
16
17mod rel_serde;
18
19/// Information about the event a [rich reply] is replying to.
20///
21/// [rich reply]: https://spec.matrix.org/latest/client-server-api/#rich-replies
22#[derive(Clone, Debug, Deserialize, Serialize)]
23#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
24pub struct InReplyTo {
25    /// The event being replied to.
26    pub event_id: OwnedEventId,
27}
28
29impl InReplyTo {
30    /// Creates a new `InReplyTo` with the given event ID.
31    pub fn new(event_id: OwnedEventId) -> Self {
32        Self { event_id }
33    }
34}
35
36/// An [annotation] for an event.
37///
38/// [annotation]: https://spec.matrix.org/latest/client-server-api/#event-annotations-and-reactions
39#[derive(Clone, Debug, Deserialize, Serialize)]
40#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
41#[serde(tag = "rel_type", rename = "m.annotation")]
42pub struct Annotation {
43    /// The event that is being annotated.
44    pub event_id: OwnedEventId,
45
46    /// A string that indicates the annotation being applied.
47    ///
48    /// When sending emoji reactions, this field should include the colourful variation-16 when
49    /// applicable.
50    ///
51    /// Clients should render reactions that have a long `key` field in a sensible manner.
52    pub key: String,
53}
54
55impl Annotation {
56    /// Creates a new `Annotation` with the given event ID and key.
57    pub fn new(event_id: OwnedEventId, key: String) -> Self {
58        Self { event_id, key }
59    }
60}
61
62/// The content of a [replacement] relation.
63///
64/// [replacement]: https://spec.matrix.org/latest/client-server-api/#event-replacements
65#[derive(Clone, Debug)]
66#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
67pub struct Replacement<C> {
68    /// The ID of the event being replaced.
69    pub event_id: OwnedEventId,
70
71    /// New content.
72    pub new_content: C,
73}
74
75impl<C> Replacement<C> {
76    /// Creates a new `Replacement` with the given event ID and new content.
77    pub fn new(event_id: OwnedEventId, new_content: C) -> Self {
78        Self { event_id, new_content }
79    }
80}
81
82/// The content of a [thread] relation.
83///
84/// [thread]: https://spec.matrix.org/latest/client-server-api/#threading
85#[derive(Clone, Debug, Serialize, Deserialize)]
86#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
87#[serde(tag = "rel_type", rename = "m.thread")]
88pub struct Thread {
89    /// The ID of the root message in the thread.
90    pub event_id: OwnedEventId,
91
92    /// A reply relation.
93    ///
94    /// If this event is a reply and belongs to a thread, this points to the message that is being
95    /// replied to, and `is_falling_back` must be set to `false`.
96    ///
97    /// If this event is not a reply, this is used as a fallback mechanism for clients that do not
98    /// support threads. This should point to the latest message-like event in the thread and
99    /// `is_falling_back` must be set to `true`.
100    #[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
101    pub in_reply_to: Option<InReplyTo>,
102
103    /// Whether the `m.in_reply_to` field is a fallback for older clients or a genuine reply in a
104    /// thread.
105    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
106    pub is_falling_back: bool,
107}
108
109impl Thread {
110    /// Convenience method to create a regular `Thread` relation with the given root event ID and
111    /// latest message-like event ID.
112    pub fn plain(event_id: OwnedEventId, latest_event_id: OwnedEventId) -> Self {
113        Self { event_id, in_reply_to: Some(InReplyTo::new(latest_event_id)), is_falling_back: true }
114    }
115
116    /// Convenience method to create a regular `Thread` relation with the given root event ID and
117    /// *without* the recommended reply fallback.
118    pub fn without_fallback(event_id: OwnedEventId) -> Self {
119        Self { event_id, in_reply_to: None, is_falling_back: false }
120    }
121
122    /// Convenience method to create a reply `Thread` relation with the given root event ID and
123    /// replied-to event ID.
124    pub fn reply(event_id: OwnedEventId, reply_to_event_id: OwnedEventId) -> Self {
125        Self {
126            event_id,
127            in_reply_to: Some(InReplyTo::new(reply_to_event_id)),
128            is_falling_back: false,
129        }
130    }
131}
132
133/// A bundled thread.
134#[derive(Clone, Debug, Deserialize, Serialize)]
135#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
136pub struct BundledThread {
137    /// The latest event in the thread.
138    pub latest_event: Raw<AnyMessageLikeEvent>,
139
140    /// The number of events in the thread.
141    pub count: UInt,
142
143    /// Whether the current logged in user has participated in the thread.
144    pub current_user_participated: bool,
145}
146
147impl BundledThread {
148    /// Creates a new `BundledThread` with the given event, count and user participated flag.
149    pub fn new(
150        latest_event: Raw<AnyMessageLikeEvent>,
151        count: UInt,
152        current_user_participated: bool,
153    ) -> Self {
154        Self { latest_event, count, current_user_participated }
155    }
156}
157
158/// A [reference] to another event.
159///
160/// [reference]: https://spec.matrix.org/latest/client-server-api/#reference-relations
161#[derive(Clone, Debug, Deserialize, Serialize)]
162#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
163#[serde(tag = "rel_type", rename = "m.reference")]
164pub struct Reference {
165    /// The ID of the event being referenced.
166    pub event_id: OwnedEventId,
167}
168
169impl Reference {
170    /// Creates a new `Reference` with the given event ID.
171    pub fn new(event_id: OwnedEventId) -> Self {
172        Self { event_id }
173    }
174}
175
176/// A bundled reference.
177#[derive(Clone, Debug, Deserialize, Serialize)]
178#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
179pub struct BundledReference {
180    /// The ID of the event referencing this event.
181    pub event_id: OwnedEventId,
182}
183
184impl BundledReference {
185    /// Creates a new `BundledThread` with the given event ID.
186    pub fn new(event_id: OwnedEventId) -> Self {
187        Self { event_id }
188    }
189}
190
191/// A chunk of references.
192#[derive(Clone, Debug, Default, Deserialize, Serialize)]
193#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
194pub struct ReferenceChunk {
195    /// A batch of bundled references.
196    pub chunk: Vec<BundledReference>,
197}
198
199impl ReferenceChunk {
200    /// Creates a new `ReferenceChunk` with the given chunk.
201    pub fn new(chunk: Vec<BundledReference>) -> Self {
202        Self { chunk }
203    }
204}
205
206/// [Bundled aggregations] of related child events of a message-like event.
207///
208/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
209#[derive(Clone, Debug, Serialize)]
210#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
211pub struct BundledMessageLikeRelations<E> {
212    /// Replacement relation.
213    #[serde(rename = "m.replace", skip_serializing_if = "Option::is_none")]
214    pub replace: Option<Box<E>>,
215
216    /// Set when the above fails to deserialize.
217    ///
218    /// Intentionally *not* public.
219    #[serde(skip_serializing)]
220    has_invalid_replacement: bool,
221
222    /// Thread relation.
223    #[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
224    pub thread: Option<Box<BundledThread>>,
225
226    /// Reference relations.
227    #[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
228    pub reference: Option<Box<ReferenceChunk>>,
229}
230
231impl<E> BundledMessageLikeRelations<E> {
232    /// Creates a new empty `BundledMessageLikeRelations`.
233    pub const fn new() -> Self {
234        Self { replace: None, has_invalid_replacement: false, thread: None, reference: None }
235    }
236
237    /// Whether this bundle contains a replacement relation.
238    ///
239    /// This may be `true` even if the `replace` field is `None`, because Matrix versions prior to
240    /// 1.7 had a different incompatible format for bundled replacements. Use this method to check
241    /// whether an event was replaced. If this returns `true` but `replace` is `None`, use one of
242    /// the endpoints from `ruma::api::client::relations` to fetch the relation details.
243    pub fn has_replacement(&self) -> bool {
244        self.replace.is_some() || self.has_invalid_replacement
245    }
246
247    /// Returns `true` if all fields are empty.
248    pub fn is_empty(&self) -> bool {
249        self.replace.is_none() && self.thread.is_none() && self.reference.is_none()
250    }
251
252    /// Transform `BundledMessageLikeRelations<E>` to `BundledMessageLikeRelations<T>` using the
253    /// given closure to convert the `replace` field if it is `Some(_)`.
254    pub(crate) fn map_replace<T>(self, f: impl FnOnce(E) -> T) -> BundledMessageLikeRelations<T> {
255        let Self { replace, has_invalid_replacement, thread, reference } = self;
256        let replace = replace.map(|r| Box::new(f(*r)));
257        BundledMessageLikeRelations { replace, has_invalid_replacement, thread, reference }
258    }
259}
260
261impl<E> Default for BundledMessageLikeRelations<E> {
262    fn default() -> Self {
263        Self::new()
264    }
265}
266
267/// [Bundled aggregations] of related child events of a state event.
268///
269/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
270#[derive(Clone, Debug, Default, Deserialize, Serialize)]
271#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
272pub struct BundledStateRelations {
273    /// Thread relation.
274    #[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
275    pub thread: Option<Box<BundledThread>>,
276
277    /// Reference relations.
278    #[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
279    pub reference: Option<Box<ReferenceChunk>>,
280}
281
282impl BundledStateRelations {
283    /// Creates a new empty `BundledStateRelations`.
284    pub const fn new() -> Self {
285        Self { thread: None, reference: None }
286    }
287
288    /// Returns `true` if all fields are empty.
289    pub fn is_empty(&self) -> bool {
290        self.thread.is_none() && self.reference.is_none()
291    }
292}
293
294/// Relation types as defined in `rel_type` of an `m.relates_to` field.
295#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
296#[derive(Clone, PartialEq, Eq, StringEnum)]
297#[ruma_enum(rename_all = "m.snake_case")]
298#[non_exhaustive]
299pub enum RelationType {
300    /// `m.annotation`, an annotation, principally used by reactions.
301    Annotation,
302
303    /// `m.replace`, a replacement.
304    Replacement,
305
306    /// `m.thread`, a participant to a thread.
307    Thread,
308
309    /// `m.reference`, a reference to another event.
310    Reference,
311
312    #[doc(hidden)]
313    _Custom(PrivOwnedStr),
314}
315
316/// The payload for a custom relation.
317#[doc(hidden)]
318#[derive(Clone, Debug, Deserialize, Serialize)]
319#[serde(transparent)]
320pub struct CustomRelation(pub(super) JsonObject);
321
322impl CustomRelation {
323    pub(super) fn rel_type(&self) -> Option<RelationType> {
324        Some(self.0.get("rel_type")?.as_str()?.into())
325    }
326}