1#[cfg(feature = "unstable-msc4354")]
2use std::time::Duration;
3
4use js_int::Int;
5use ruma_common::{
6 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
7 serde::{CanBeEmpty, Raw},
8};
9use serde::{Deserialize, de::DeserializeOwned};
10
11use super::{
12 MessageLikeEventContent, OriginalSyncMessageLikeEvent, PossiblyRedactedStateEventContent,
13 relation::{BundledMessageLikeRelations, BundledStateRelations},
14 room::redaction::RoomRedactionEventContent,
15};
16use crate::TimelineEventType;
17
18mod redacted_because_serde;
19
20#[derive(Clone, Debug, Deserialize)]
22#[serde(bound = "OriginalSyncMessageLikeEvent<C>: DeserializeOwned")]
23#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
24pub struct MessageLikeUnsigned<C: MessageLikeEventContent> {
25 pub age: Option<Int>,
31
32 pub transaction_id: Option<OwnedTransactionId>,
35
36 #[serde(rename = "m.relations", default)]
40 pub relations: BundledMessageLikeRelations<OriginalSyncMessageLikeEvent<C>>,
41
42 #[cfg(feature = "unstable-msc4354")]
48 #[serde(rename = "msc4354_sticky_duration_ttl_ms", default)]
49 #[serde(with = "ruma_common::serde::duration::opt_ms")]
50 pub sticky_duration_ttl_ms: Option<Duration>,
51}
52
53impl<C: MessageLikeEventContent> MessageLikeUnsigned<C> {
54 pub fn new() -> Self {
56 Self {
57 age: None,
58 transaction_id: None,
59 relations: BundledMessageLikeRelations::default(),
60 #[cfg(feature = "unstable-msc4354")]
61 sticky_duration_ttl_ms: None,
62 }
63 }
64}
65
66impl<C: MessageLikeEventContent> Default for MessageLikeUnsigned<C> {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl<C: MessageLikeEventContent> CanBeEmpty for MessageLikeUnsigned<C> {
73 fn is_empty(&self) -> bool {
79 let empty =
80 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty();
81 #[cfg(feature = "unstable-msc4354")]
82 let empty = empty && self.sticky_duration_ttl_ms.is_none();
83 empty
84 }
85}
86
87#[derive(Clone, Debug, Deserialize)]
89#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
90pub struct StateUnsigned<C: PossiblyRedactedStateEventContent> {
91 pub age: Option<Int>,
97
98 pub transaction_id: Option<OwnedTransactionId>,
101
102 pub replaces_state: Option<OwnedEventId>,
107
108 pub prev_content: Option<C>,
110
111 #[serde(rename = "m.relations", default)]
115 pub relations: BundledStateRelations,
116
117 #[cfg(feature = "unstable-msc4354")]
123 #[serde(rename = "msc4354_sticky_duration_ttl_ms", default)]
124 #[serde(with = "ruma_common::serde::duration::opt_ms")]
125 pub sticky_duration_ttl_ms: Option<Duration>,
126}
127
128impl<C: PossiblyRedactedStateEventContent> StateUnsigned<C> {
129 pub fn new() -> Self {
131 Self {
132 age: None,
133 transaction_id: None,
134 replaces_state: None,
135 prev_content: None,
136 relations: Default::default(),
137 #[cfg(feature = "unstable-msc4354")]
138 sticky_duration_ttl_ms: None,
139 }
140 }
141}
142
143impl<C: PossiblyRedactedStateEventContent> CanBeEmpty for StateUnsigned<C> {
144 fn is_empty(&self) -> bool {
150 let Self {
151 age,
152 transaction_id,
153 replaces_state,
154 prev_content,
155 relations,
156 #[cfg(feature = "unstable-msc4354")]
157 sticky_duration_ttl_ms,
158 } = self;
159
160 let empty = age.is_none()
161 && transaction_id.is_none()
162 && replaces_state.is_none()
163 && prev_content.is_none()
164 && relations.is_empty();
165 #[cfg(feature = "unstable-msc4354")]
166 let empty = empty && sticky_duration_ttl_ms.is_none();
167 empty
168 }
169}
170
171impl<C: PossiblyRedactedStateEventContent> Default for StateUnsigned<C> {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177#[derive(Clone, Debug, Deserialize)]
179#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
180pub struct RedactedUnsigned {
181 pub redacted_because: Raw<AnyRedactionEvent>,
183}
184
185impl RedactedUnsigned {
186 pub fn new(redacted_because: Raw<AnyRedactionEvent>) -> Self {
188 Self { redacted_because }
189 }
190}
191
192#[derive(Clone, Debug)]
195#[non_exhaustive]
196#[allow(clippy::large_enum_variant)]
197pub enum AnyRedactionEvent {
198 RoomRedaction(UnsignedRoomRedactionEvent),
200
201 #[cfg(feature = "unstable-msc4293")]
203 RoomMember(super::room::member::SyncRoomMemberEvent),
204
205 #[doc(hidden)]
206 _Custom(CustomRedactionEvent),
207}
208
209impl AnyRedactionEvent {
210 pub fn event_type(&self) -> TimelineEventType {
212 match self {
213 Self::RoomRedaction(_) => TimelineEventType::RoomRedaction,
214 #[cfg(feature = "unstable-msc4293")]
215 Self::RoomMember(_) => TimelineEventType::RoomMember,
216 Self::_Custom(e) => TimelineEventType::from(&*e.event_type),
217 }
218 }
219
220 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
222 match self {
223 Self::RoomRedaction(e) => e.origin_server_ts,
224 #[cfg(feature = "unstable-msc4293")]
225 Self::RoomMember(e) => e.origin_server_ts(),
226 Self::_Custom(e) => e.origin_server_ts,
227 }
228 }
229
230 pub fn event_id(&self) -> &EventId {
232 match self {
233 Self::RoomRedaction(e) => &e.event_id,
234 #[cfg(feature = "unstable-msc4293")]
235 Self::RoomMember(e) => e.event_id(),
236 Self::_Custom(e) => &e.event_id,
237 }
238 }
239
240 pub fn sender(&self) -> &UserId {
242 match self {
243 Self::RoomRedaction(e) => &e.sender,
244 #[cfg(feature = "unstable-msc4293")]
245 Self::RoomMember(e) => e.sender(),
246 Self::_Custom(e) => &e.sender,
247 }
248 }
249}
250
251#[derive(Clone, Debug, Deserialize)]
260#[non_exhaustive]
261pub struct UnsignedRoomRedactionEvent {
262 pub content: RoomRedactionEventContent,
264
265 pub event_id: OwnedEventId,
267
268 pub sender: OwnedUserId,
270
271 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
273
274 #[serde(default)]
276 pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
277}
278
279#[doc(hidden)]
281#[derive(Clone, Debug)]
282pub struct CustomRedactionEvent {
283 event_type: Box<str>,
285
286 event_id: OwnedEventId,
288
289 sender: OwnedUserId,
291
292 origin_server_ts: MilliSecondsSinceUnixEpoch,
294}
295
296#[cfg(test)]
297mod tests {
298 use assert_matches::assert_matches;
299 use js_int::uint;
300 use serde_json::{from_value as from_json_value, json};
301
302 use super::AnyRedactionEvent;
303 use crate::TimelineEventType;
304
305 #[test]
306 fn deserialize_any_redaction_event_room_redaction() {
307 let json = json!({
308 "type": "m.room.redaction",
309 "content": {
310 "redacts": "$redactedevent",
311 },
312 "event_id": "$redactionevent",
313 "origin_server_ts": 1,
314 "sender": "@carl:example.com",
315 });
316
317 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
318 assert_eq!(event.event_id(), "$redactionevent");
319 assert_eq!(event.origin_server_ts().0, uint!(1));
320 assert_eq!(event.sender(), "@carl:example.com");
321 assert_eq!(event.event_type(), TimelineEventType::RoomRedaction);
322 assert_matches!(event, AnyRedactionEvent::RoomRedaction(_));
323 }
324
325 #[test]
326 fn deserialize_any_redaction_event_custom() {
327 let json = json!({
328 "type": "local.dev.custom_type",
329 "content": {},
330 "event_id": "$redactionevent",
331 "origin_server_ts": 1,
332 "sender": "@carl:example.com",
333 });
334
335 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
336 assert_eq!(event.event_id(), "$redactionevent");
337 assert_eq!(event.origin_server_ts().0, uint!(1));
338 assert_eq!(event.sender(), "@carl:example.com");
339 assert_eq!(event.event_type().to_string(), "local.dev.custom_type");
340 }
341}