1use std::fmt::Debug;
6
7use js_int::UInt;
8use ruma_common::{
9 OwnedEventId,
10 serde::{JsonObject, Raw, StringEnum},
11};
12use serde::{Deserialize, Serialize};
13
14use crate::{AnySyncMessageLikeEvent, PrivOwnedStr};
15
16mod rel_serde;
17
18#[derive(Clone, Debug, Deserialize, Serialize)]
22#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
23pub struct Reply {
24 #[serde(rename = "m.in_reply_to")]
26 pub in_reply_to: InReplyTo,
27}
28
29impl Reply {
30 pub fn new(in_reply_to: InReplyTo) -> Self {
32 Self { in_reply_to }
33 }
34
35 pub fn with_event_id(event_id: OwnedEventId) -> Self {
37 Self { in_reply_to: InReplyTo::new(event_id) }
38 }
39}
40
41#[derive(Clone, Debug, Deserialize, Serialize)]
45#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
46pub struct InReplyTo {
47 pub event_id: OwnedEventId,
49}
50
51impl InReplyTo {
52 pub fn new(event_id: OwnedEventId) -> Self {
54 Self { event_id }
55 }
56}
57
58#[derive(Clone, Debug, Deserialize, Serialize)]
62#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
63#[serde(tag = "rel_type", rename = "m.annotation")]
64pub struct Annotation {
65 pub event_id: OwnedEventId,
67
68 pub key: String,
75}
76
77impl Annotation {
78 pub fn new(event_id: OwnedEventId, key: String) -> Self {
80 Self { event_id, key }
81 }
82}
83
84#[derive(Clone, Debug)]
88#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
89pub struct Replacement<C> {
90 pub event_id: OwnedEventId,
92
93 pub new_content: C,
95}
96
97impl<C> Replacement<C> {
98 pub fn new(event_id: OwnedEventId, new_content: C) -> Self {
100 Self { event_id, new_content }
101 }
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize)]
108#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
109#[serde(tag = "rel_type", rename = "m.thread")]
110pub struct Thread {
111 pub event_id: OwnedEventId,
113
114 #[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
123 pub in_reply_to: Option<InReplyTo>,
124
125 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
128 pub is_falling_back: bool,
129}
130
131impl Thread {
132 pub fn plain(event_id: OwnedEventId, latest_event_id: OwnedEventId) -> Self {
135 Self { event_id, in_reply_to: Some(InReplyTo::new(latest_event_id)), is_falling_back: true }
136 }
137
138 pub fn without_fallback(event_id: OwnedEventId) -> Self {
141 Self { event_id, in_reply_to: None, is_falling_back: false }
142 }
143
144 pub fn reply(event_id: OwnedEventId, reply_to_event_id: OwnedEventId) -> Self {
147 Self {
148 event_id,
149 in_reply_to: Some(InReplyTo::new(reply_to_event_id)),
150 is_falling_back: false,
151 }
152 }
153}
154
155#[derive(Clone, Debug, Deserialize, Serialize)]
157#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
158pub struct BundledThread {
159 pub latest_event: Raw<AnySyncMessageLikeEvent>,
161
162 pub count: UInt,
164
165 pub current_user_participated: bool,
167}
168
169impl BundledThread {
170 pub fn new(
172 latest_event: Raw<AnySyncMessageLikeEvent>,
173 count: UInt,
174 current_user_participated: bool,
175 ) -> Self {
176 Self { latest_event, count, current_user_participated }
177 }
178}
179
180#[derive(Clone, Debug, Deserialize, Serialize)]
184#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
185#[serde(tag = "rel_type", rename = "m.reference")]
186pub struct Reference {
187 pub event_id: OwnedEventId,
189}
190
191impl Reference {
192 pub fn new(event_id: OwnedEventId) -> Self {
194 Self { event_id }
195 }
196}
197
198#[derive(Clone, Debug, Deserialize, Serialize)]
200#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
201pub struct BundledReference {
202 pub event_id: OwnedEventId,
204}
205
206impl BundledReference {
207 pub fn new(event_id: OwnedEventId) -> Self {
209 Self { event_id }
210 }
211}
212
213#[derive(Clone, Debug, Default, Deserialize, Serialize)]
215#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
216pub struct ReferenceChunk {
217 pub chunk: Vec<BundledReference>,
219}
220
221impl ReferenceChunk {
222 pub fn new(chunk: Vec<BundledReference>) -> Self {
224 Self { chunk }
225 }
226}
227
228#[derive(Clone, Debug, Serialize)]
232#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
233pub struct BundledMessageLikeRelations<E> {
234 #[serde(rename = "m.replace", skip_serializing_if = "Option::is_none")]
236 pub replace: Option<Box<E>>,
237
238 #[serde(skip_serializing)]
242 has_invalid_replacement: bool,
243
244 #[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
246 pub thread: Option<Box<BundledThread>>,
247
248 #[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
250 pub reference: Option<Box<ReferenceChunk>>,
251}
252
253impl<E> BundledMessageLikeRelations<E> {
254 pub const fn new() -> Self {
256 Self { replace: None, has_invalid_replacement: false, thread: None, reference: None }
257 }
258
259 pub fn has_replacement(&self) -> bool {
266 self.replace.is_some() || self.has_invalid_replacement
267 }
268
269 pub fn is_empty(&self) -> bool {
271 self.replace.is_none() && self.thread.is_none() && self.reference.is_none()
272 }
273
274 pub(crate) fn map_replace<T>(self, f: impl FnOnce(E) -> T) -> BundledMessageLikeRelations<T> {
277 let Self { replace, has_invalid_replacement, thread, reference } = self;
278 let replace = replace.map(|r| Box::new(f(*r)));
279 BundledMessageLikeRelations { replace, has_invalid_replacement, thread, reference }
280 }
281}
282
283impl<E> Default for BundledMessageLikeRelations<E> {
284 fn default() -> Self {
285 Self::new()
286 }
287}
288
289#[derive(Clone, Debug, Default, Deserialize, Serialize)]
293#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
294pub struct BundledStateRelations {
295 #[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
297 pub thread: Option<Box<BundledThread>>,
298
299 #[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
301 pub reference: Option<Box<ReferenceChunk>>,
302}
303
304impl BundledStateRelations {
305 pub const fn new() -> Self {
307 Self { thread: None, reference: None }
308 }
309
310 pub fn is_empty(&self) -> bool {
312 self.thread.is_none() && self.reference.is_none()
313 }
314}
315
316#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
318#[derive(Clone, StringEnum)]
319#[ruma_enum(rename_all(prefix = "m.", rule = "snake_case"))]
320#[non_exhaustive]
321pub enum RelationType {
322 Annotation,
324
325 #[ruma_enum(rename = "m.replace")]
327 Replacement,
328
329 Thread,
331
332 Reference,
334
335 #[doc(hidden)]
336 _Custom(PrivOwnedStr),
337}
338
339#[doc(hidden)]
341#[derive(Clone, Debug, Deserialize, Serialize)]
342#[serde(transparent)]
343pub struct CustomRelation(pub(super) JsonObject);
344
345impl CustomRelation {
346 pub(super) fn rel_type(&self) -> Option<RelationType> {
347 Some(self.0.get("rel_type")?.as_str()?.into())
348 }
349}