1use js_int::Int;
2use ruma_common::{
3 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
4 serde::{CanBeEmpty, Raw},
5};
6use serde::{Deserialize, de::DeserializeOwned};
7
8use super::{
9 MessageLikeEventContent, OriginalSyncMessageLikeEvent, PossiblyRedactedStateEventContent,
10 relation::{BundledMessageLikeRelations, BundledStateRelations},
11 room::redaction::RoomRedactionEventContent,
12};
13use crate::TimelineEventType;
14
15mod redacted_because_serde;
16
17#[derive(Clone, Debug, Deserialize)]
19#[serde(bound = "OriginalSyncMessageLikeEvent<C>: DeserializeOwned")]
20#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
21pub struct MessageLikeUnsigned<C: MessageLikeEventContent> {
22 pub age: Option<Int>,
28
29 pub transaction_id: Option<OwnedTransactionId>,
32
33 #[serde(rename = "m.relations", default)]
37 pub relations: BundledMessageLikeRelations<OriginalSyncMessageLikeEvent<C>>,
38}
39
40impl<C: MessageLikeEventContent> MessageLikeUnsigned<C> {
41 pub fn new() -> Self {
43 Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
44 }
45}
46
47impl<C: MessageLikeEventContent> Default for MessageLikeUnsigned<C> {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl<C: MessageLikeEventContent> CanBeEmpty for MessageLikeUnsigned<C> {
54 fn is_empty(&self) -> bool {
60 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
61 }
62}
63
64#[derive(Clone, Debug, Deserialize)]
66#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
67pub struct StateUnsigned<C: PossiblyRedactedStateEventContent> {
68 pub age: Option<Int>,
74
75 pub transaction_id: Option<OwnedTransactionId>,
78
79 pub prev_content: Option<C>,
81
82 #[serde(rename = "m.relations", default)]
86 pub relations: BundledStateRelations,
87}
88
89impl<C: PossiblyRedactedStateEventContent> StateUnsigned<C> {
90 pub fn new() -> Self {
92 Self { age: None, transaction_id: None, prev_content: None, relations: Default::default() }
93 }
94}
95
96impl<C: PossiblyRedactedStateEventContent> CanBeEmpty for StateUnsigned<C> {
97 fn is_empty(&self) -> bool {
103 self.age.is_none()
104 && self.transaction_id.is_none()
105 && self.prev_content.is_none()
106 && self.relations.is_empty()
107 }
108}
109
110impl<C: PossiblyRedactedStateEventContent> Default for StateUnsigned<C> {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116#[derive(Clone, Debug, Deserialize)]
118#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
119pub struct RedactedUnsigned {
120 pub redacted_because: Raw<AnyRedactionEvent>,
122}
123
124impl RedactedUnsigned {
125 pub fn new(redacted_because: Raw<AnyRedactionEvent>) -> Self {
127 Self { redacted_because }
128 }
129}
130
131#[derive(Clone, Debug)]
134#[non_exhaustive]
135#[allow(clippy::large_enum_variant)]
136pub enum AnyRedactionEvent {
137 RoomRedaction(UnsignedRoomRedactionEvent),
139
140 #[cfg(feature = "unstable-msc4293")]
142 RoomMember(super::room::member::SyncRoomMemberEvent),
143
144 #[doc(hidden)]
145 _Custom(CustomRedactionEvent),
146}
147
148impl AnyRedactionEvent {
149 pub fn event_type(&self) -> TimelineEventType {
151 match self {
152 Self::RoomRedaction(_) => TimelineEventType::RoomRedaction,
153 #[cfg(feature = "unstable-msc4293")]
154 Self::RoomMember(_) => TimelineEventType::RoomMember,
155 Self::_Custom(e) => TimelineEventType::from(&*e.event_type),
156 }
157 }
158
159 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
161 match self {
162 Self::RoomRedaction(e) => e.origin_server_ts,
163 #[cfg(feature = "unstable-msc4293")]
164 Self::RoomMember(e) => e.origin_server_ts(),
165 Self::_Custom(e) => e.origin_server_ts,
166 }
167 }
168
169 pub fn event_id(&self) -> &EventId {
171 match self {
172 Self::RoomRedaction(e) => &e.event_id,
173 #[cfg(feature = "unstable-msc4293")]
174 Self::RoomMember(e) => e.event_id(),
175 Self::_Custom(e) => &e.event_id,
176 }
177 }
178
179 pub fn sender(&self) -> &UserId {
181 match self {
182 Self::RoomRedaction(e) => &e.sender,
183 #[cfg(feature = "unstable-msc4293")]
184 Self::RoomMember(e) => e.sender(),
185 Self::_Custom(e) => &e.sender,
186 }
187 }
188}
189
190#[derive(Clone, Debug, Deserialize)]
199#[non_exhaustive]
200pub struct UnsignedRoomRedactionEvent {
201 pub content: RoomRedactionEventContent,
203
204 pub event_id: OwnedEventId,
206
207 pub sender: OwnedUserId,
209
210 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
212
213 #[serde(default)]
215 pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
216}
217
218#[doc(hidden)]
220#[derive(Clone, Debug)]
221pub struct CustomRedactionEvent {
222 event_type: Box<str>,
224
225 event_id: OwnedEventId,
227
228 sender: OwnedUserId,
230
231 origin_server_ts: MilliSecondsSinceUnixEpoch,
233}
234
235#[cfg(test)]
236mod tests {
237 use assert_matches2::assert_matches;
238 use js_int::uint;
239 use serde_json::{from_value as from_json_value, json};
240
241 use super::AnyRedactionEvent;
242 use crate::TimelineEventType;
243
244 #[test]
245 fn deserialize_any_redaction_event_room_redaction() {
246 let json = json!({
247 "type": "m.room.redaction",
248 "content": {
249 "redacts": "$redactedevent",
250 },
251 "event_id": "$redactionevent",
252 "origin_server_ts": 1,
253 "sender": "@carl:example.com",
254 });
255
256 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
257 assert_eq!(event.event_id(), "$redactionevent");
258 assert_eq!(event.origin_server_ts().0, uint!(1));
259 assert_eq!(event.sender(), "@carl:example.com");
260 assert_eq!(event.event_type(), TimelineEventType::RoomRedaction);
261 assert_matches!(event, AnyRedactionEvent::RoomRedaction(_));
262 }
263
264 #[test]
265 fn deserialize_any_redaction_event_custom() {
266 let json = json!({
267 "type": "local.dev.custom_type",
268 "content": {},
269 "event_id": "$redactionevent",
270 "origin_server_ts": 1,
271 "sender": "@carl:example.com",
272 });
273
274 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
275 assert_eq!(event.event_id(), "$redactionevent");
276 assert_eq!(event.origin_server_ts().0, uint!(1));
277 assert_eq!(event.sender(), "@carl:example.com");
278 assert_eq!(event.event_type().to_string(), "local.dev.custom_type");
279 }
280}