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 replaces_state: Option<OwnedEventId>,
81
82 pub prev_content: Option<C>,
84
85 #[serde(rename = "m.relations", default)]
89 pub relations: BundledStateRelations,
90}
91
92impl<C: PossiblyRedactedStateEventContent> StateUnsigned<C> {
93 pub fn new() -> Self {
95 Self {
96 age: None,
97 transaction_id: None,
98 replaces_state: None,
99 prev_content: None,
100 relations: Default::default(),
101 }
102 }
103}
104
105impl<C: PossiblyRedactedStateEventContent> CanBeEmpty for StateUnsigned<C> {
106 fn is_empty(&self) -> bool {
112 self.age.is_none()
113 && self.transaction_id.is_none()
114 && self.prev_content.is_none()
115 && self.relations.is_empty()
116 }
117}
118
119impl<C: PossiblyRedactedStateEventContent> Default for StateUnsigned<C> {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125#[derive(Clone, Debug, Deserialize)]
127#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
128pub struct RedactedUnsigned {
129 pub redacted_because: Raw<AnyRedactionEvent>,
131}
132
133impl RedactedUnsigned {
134 pub fn new(redacted_because: Raw<AnyRedactionEvent>) -> Self {
136 Self { redacted_because }
137 }
138}
139
140#[derive(Clone, Debug)]
143#[non_exhaustive]
144#[allow(clippy::large_enum_variant)]
145pub enum AnyRedactionEvent {
146 RoomRedaction(UnsignedRoomRedactionEvent),
148
149 #[cfg(feature = "unstable-msc4293")]
151 RoomMember(super::room::member::SyncRoomMemberEvent),
152
153 #[doc(hidden)]
154 _Custom(CustomRedactionEvent),
155}
156
157impl AnyRedactionEvent {
158 pub fn event_type(&self) -> TimelineEventType {
160 match self {
161 Self::RoomRedaction(_) => TimelineEventType::RoomRedaction,
162 #[cfg(feature = "unstable-msc4293")]
163 Self::RoomMember(_) => TimelineEventType::RoomMember,
164 Self::_Custom(e) => TimelineEventType::from(&*e.event_type),
165 }
166 }
167
168 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
170 match self {
171 Self::RoomRedaction(e) => e.origin_server_ts,
172 #[cfg(feature = "unstable-msc4293")]
173 Self::RoomMember(e) => e.origin_server_ts(),
174 Self::_Custom(e) => e.origin_server_ts,
175 }
176 }
177
178 pub fn event_id(&self) -> &EventId {
180 match self {
181 Self::RoomRedaction(e) => &e.event_id,
182 #[cfg(feature = "unstable-msc4293")]
183 Self::RoomMember(e) => e.event_id(),
184 Self::_Custom(e) => &e.event_id,
185 }
186 }
187
188 pub fn sender(&self) -> &UserId {
190 match self {
191 Self::RoomRedaction(e) => &e.sender,
192 #[cfg(feature = "unstable-msc4293")]
193 Self::RoomMember(e) => e.sender(),
194 Self::_Custom(e) => &e.sender,
195 }
196 }
197}
198
199#[derive(Clone, Debug, Deserialize)]
208#[non_exhaustive]
209pub struct UnsignedRoomRedactionEvent {
210 pub content: RoomRedactionEventContent,
212
213 pub event_id: OwnedEventId,
215
216 pub sender: OwnedUserId,
218
219 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
221
222 #[serde(default)]
224 pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
225}
226
227#[doc(hidden)]
229#[derive(Clone, Debug)]
230pub struct CustomRedactionEvent {
231 event_type: Box<str>,
233
234 event_id: OwnedEventId,
236
237 sender: OwnedUserId,
239
240 origin_server_ts: MilliSecondsSinceUnixEpoch,
242}
243
244#[cfg(test)]
245mod tests {
246 use assert_matches2::assert_matches;
247 use js_int::uint;
248 use serde_json::{from_value as from_json_value, json};
249
250 use super::AnyRedactionEvent;
251 use crate::TimelineEventType;
252
253 #[test]
254 fn deserialize_any_redaction_event_room_redaction() {
255 let json = json!({
256 "type": "m.room.redaction",
257 "content": {
258 "redacts": "$redactedevent",
259 },
260 "event_id": "$redactionevent",
261 "origin_server_ts": 1,
262 "sender": "@carl:example.com",
263 });
264
265 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
266 assert_eq!(event.event_id(), "$redactionevent");
267 assert_eq!(event.origin_server_ts().0, uint!(1));
268 assert_eq!(event.sender(), "@carl:example.com");
269 assert_eq!(event.event_type(), TimelineEventType::RoomRedaction);
270 assert_matches!(event, AnyRedactionEvent::RoomRedaction(_));
271 }
272
273 #[test]
274 fn deserialize_any_redaction_event_custom() {
275 let json = json!({
276 "type": "local.dev.custom_type",
277 "content": {},
278 "event_id": "$redactionevent",
279 "origin_server_ts": 1,
280 "sender": "@carl:example.com",
281 });
282
283 let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
284 assert_eq!(event.event_id(), "$redactionevent");
285 assert_eq!(event.origin_server_ts().0, uint!(1));
286 assert_eq!(event.sender(), "@carl:example.com");
287 assert_eq!(event.event_type().to_string(), "local.dev.custom_type");
288 }
289}