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>,
104
105 pub prev_content: Option<C>,
107
108 #[serde(rename = "m.relations", default)]
112 pub relations: BundledStateRelations,
113
114 #[cfg(feature = "unstable-msc4354")]
120 #[serde(rename = "msc4354_sticky_duration_ttl_ms", default)]
121 #[serde(with = "ruma_common::serde::duration::opt_ms")]
122 pub sticky_duration_ttl_ms: Option<Duration>,
123}
124
125impl<C: PossiblyRedactedStateEventContent> StateUnsigned<C> {
126 pub fn new() -> Self {
128 Self {
129 age: None,
130 transaction_id: None,
131 replaces_state: None,
132 prev_content: None,
133 relations: Default::default(),
134 #[cfg(feature = "unstable-msc4354")]
135 sticky_duration_ttl_ms: None,
136 }
137 }
138}
139
140impl<C: PossiblyRedactedStateEventContent> CanBeEmpty for StateUnsigned<C> {
141 fn is_empty(&self) -> bool {
147 let empty = self.age.is_none()
148 && self.transaction_id.is_none()
149 && self.prev_content.is_none()
150 && self.relations.is_empty();
151 #[cfg(feature = "unstable-msc4354")]
152 let empty = empty && self.sticky_duration_ttl_ms.is_none();
153 empty
154 }
155}
156
157impl<C: PossiblyRedactedStateEventContent> Default for StateUnsigned<C> {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163#[derive(Clone, Debug, Deserialize)]
165#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
166pub struct RedactedUnsigned {
167 pub redacted_because: Raw<AnyRedactionEvent>,
169}
170
171impl RedactedUnsigned {
172 pub fn new(redacted_because: Raw<AnyRedactionEvent>) -> Self {
174 Self { redacted_because }
175 }
176}
177
178#[derive(Clone, Debug)]
181#[non_exhaustive]
182#[allow(clippy::large_enum_variant)]
183pub enum AnyRedactionEvent {
184 RoomRedaction(UnsignedRoomRedactionEvent),
186
187 #[cfg(feature = "unstable-msc4293")]
189 RoomMember(super::room::member::SyncRoomMemberEvent),
190
191 #[doc(hidden)]
192 _Custom(CustomRedactionEvent),
193}
194
195impl AnyRedactionEvent {
196 pub fn event_type(&self) -> TimelineEventType {
198 match self {
199 Self::RoomRedaction(_) => TimelineEventType::RoomRedaction,
200 #[cfg(feature = "unstable-msc4293")]
201 Self::RoomMember(_) => TimelineEventType::RoomMember,
202 Self::_Custom(e) => TimelineEventType::from(&*e.event_type),
203 }
204 }
205
206 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
208 match self {
209 Self::RoomRedaction(e) => e.origin_server_ts,
210 #[cfg(feature = "unstable-msc4293")]
211 Self::RoomMember(e) => e.origin_server_ts(),
212 Self::_Custom(e) => e.origin_server_ts,
213 }
214 }
215
216 pub fn event_id(&self) -> &EventId {
218 match self {
219 Self::RoomRedaction(e) => &e.event_id,
220 #[cfg(feature = "unstable-msc4293")]
221 Self::RoomMember(e) => e.event_id(),
222 Self::_Custom(e) => &e.event_id,
223 }
224 }
225
226 pub fn sender(&self) -> &UserId {
228 match self {
229 Self::RoomRedaction(e) => &e.sender,
230 #[cfg(feature = "unstable-msc4293")]
231 Self::RoomMember(e) => e.sender(),
232 Self::_Custom(e) => &e.sender,
233 }
234 }
235}
236
237#[derive(Clone, Debug, Deserialize)]
246#[non_exhaustive]
247pub struct UnsignedRoomRedactionEvent {
248 pub content: RoomRedactionEventContent,
250
251 pub event_id: OwnedEventId,
253
254 pub sender: OwnedUserId,
256
257 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
259
260 #[serde(default)]
262 pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
263}
264
265#[doc(hidden)]
267#[derive(Clone, Debug)]
268pub struct CustomRedactionEvent {
269 event_type: Box<str>,
271
272 event_id: OwnedEventId,
274
275 sender: OwnedUserId,
277
278 origin_server_ts: MilliSecondsSinceUnixEpoch,
280}
281
282#[cfg(test)]
283mod tests {
284 use assert_matches2::assert_matches;
285 use js_int::uint;
286 use serde_json::{from_value as from_json_value, json};
287
288 use super::AnyRedactionEvent;
289 use crate::TimelineEventType;
290
291 #[test]
292 fn deserialize_any_redaction_event_room_redaction() {
293 let json = json!({
294 "type": "m.room.redaction",
295 "content": {
296 "redacts": "$redactedevent",
297 },
298 "event_id": "$redactionevent",
299 "origin_server_ts": 1,
300 "sender": "@carl:example.com",
301 });
302
303 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
304 assert_eq!(event.event_id(), "$redactionevent");
305 assert_eq!(event.origin_server_ts().0, uint!(1));
306 assert_eq!(event.sender(), "@carl:example.com");
307 assert_eq!(event.event_type(), TimelineEventType::RoomRedaction);
308 assert_matches!(event, AnyRedactionEvent::RoomRedaction(_));
309 }
310
311 #[test]
312 fn deserialize_any_redaction_event_custom() {
313 let json = json!({
314 "type": "local.dev.custom_type",
315 "content": {},
316 "event_id": "$redactionevent",
317 "origin_server_ts": 1,
318 "sender": "@carl:example.com",
319 });
320
321 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
322 assert_eq!(event.event_id(), "$redactionevent");
323 assert_eq!(event.origin_server_ts().0, uint!(1));
324 assert_eq!(event.sender(), "@carl:example.com");
325 assert_eq!(event.event_type().to_string(), "local.dev.custom_type");
326 }
327}