1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
use js_int::Int;
use ruma_common::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
serde::{CanBeEmpty, Raw},
};
use serde::{Deserialize, de::DeserializeOwned};
use super::{
MessageLikeEventContent, OriginalSyncMessageLikeEvent, PossiblyRedactedStateEventContent,
relation::{BundledMessageLikeRelations, BundledStateRelations},
room::redaction::RoomRedactionEventContent,
};
use crate::TimelineEventType;
mod redacted_because_serde;
/// Extra information about a message event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Deserialize)]
#[serde(bound = "OriginalSyncMessageLikeEvent<C>: DeserializeOwned")]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct MessageLikeUnsigned<C: MessageLikeEventContent> {
/// The time in milliseconds that has elapsed since the event was sent.
///
/// This field is generated by the local homeserver, and may be incorrect if the local time on
/// at least one of the two servers is out of sync, which can cause the age to either be
/// negative or greater than it actually is.
pub age: Option<Int>,
/// The client-supplied transaction ID, if the client being given the event is the same one
/// which sent it.
pub transaction_id: Option<OwnedTransactionId>,
/// [Bundled aggregations] of related child events.
///
/// [Bundled aggregations]: https://spec.matrix.org/v1.18/client-server-api/#aggregations-of-child-events
#[serde(rename = "m.relations", default)]
pub relations: BundledMessageLikeRelations<OriginalSyncMessageLikeEvent<C>>,
}
impl<C: MessageLikeEventContent> MessageLikeUnsigned<C> {
/// Create a new `Unsigned` with fields set to `None`.
pub fn new() -> Self {
Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
}
}
impl<C: MessageLikeEventContent> Default for MessageLikeUnsigned<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: MessageLikeEventContent> CanBeEmpty for MessageLikeUnsigned<C> {
/// Whether this unsigned data is empty (all fields are `None`).
///
/// This method is used to determine whether to skip serializing the `unsigned` field in room
/// events. Do not use it to determine whether an incoming `unsigned` field was present - it
/// could still have been present but contained none of the known fields.
fn is_empty(&self) -> bool {
self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
}
}
/// Extra information about a state event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct StateUnsigned<C: PossiblyRedactedStateEventContent> {
/// The time in milliseconds that has elapsed since the event was sent.
///
/// This field is generated by the local homeserver, and may be incorrect if the local time on
/// at least one of the two servers is out of sync, which can cause the age to either be
/// negative or greater than it actually is.
pub age: Option<Int>,
/// The client-supplied transaction ID, if the client being given the event is the same one
/// which sent it.
pub transaction_id: Option<OwnedTransactionId>,
/// Optional previous content of the event.
pub prev_content: Option<C>,
/// [Bundled aggregations] of related child events.
///
/// [Bundled aggregations]: https://spec.matrix.org/v1.18/client-server-api/#aggregations-of-child-events
#[serde(rename = "m.relations", default)]
pub relations: BundledStateRelations,
}
impl<C: PossiblyRedactedStateEventContent> StateUnsigned<C> {
/// Create a new `Unsigned` with fields set to `None`.
pub fn new() -> Self {
Self { age: None, transaction_id: None, prev_content: None, relations: Default::default() }
}
}
impl<C: PossiblyRedactedStateEventContent> CanBeEmpty for StateUnsigned<C> {
/// Whether this unsigned data is empty (all fields are `None`).
///
/// This method is used to determine whether to skip serializing the `unsigned` field in room
/// events. Do not use it to determine whether an incoming `unsigned` field was present - it
/// could still have been present but contained none of the known fields.
fn is_empty(&self) -> bool {
self.age.is_none()
&& self.transaction_id.is_none()
&& self.prev_content.is_none()
&& self.relations.is_empty()
}
}
impl<C: PossiblyRedactedStateEventContent> Default for StateUnsigned<C> {
fn default() -> Self {
Self::new()
}
}
/// Extra information about a redacted event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RedactedUnsigned {
/// The event that redacted this event, if any.
pub redacted_because: Raw<AnyRedactionEvent>,
}
impl RedactedUnsigned {
/// Create a new `RedactedUnsigned` with the given redaction event.
pub fn new(redacted_because: Raw<AnyRedactionEvent>) -> Self {
Self { redacted_because }
}
}
/// Any event that can redact another event, i.e. an event that can be found in
/// `unsigned.redacted_because`.
#[derive(Clone, Debug)]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)]
pub enum AnyRedactionEvent {
/// m.room.redaction
RoomRedaction(UnsignedRoomRedactionEvent),
/// m.room.member
#[cfg(feature = "unstable-msc4293")]
RoomMember(super::room::member::SyncRoomMemberEvent),
#[doc(hidden)]
_Custom(CustomRedactionEvent),
}
impl AnyRedactionEvent {
/// Returns the `type` of this event.
pub fn event_type(&self) -> TimelineEventType {
match self {
Self::RoomRedaction(_) => TimelineEventType::RoomRedaction,
#[cfg(feature = "unstable-msc4293")]
Self::RoomMember(_) => TimelineEventType::RoomMember,
Self::_Custom(e) => TimelineEventType::from(&*e.event_type),
}
}
/// Returns the `origin_server_ts` of this event.
pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
match self {
Self::RoomRedaction(e) => e.origin_server_ts,
#[cfg(feature = "unstable-msc4293")]
Self::RoomMember(e) => e.origin_server_ts(),
Self::_Custom(e) => e.origin_server_ts,
}
}
/// Returns the `event_id` of this event.
pub fn event_id(&self) -> &EventId {
match self {
Self::RoomRedaction(e) => &e.event_id,
#[cfg(feature = "unstable-msc4293")]
Self::RoomMember(e) => e.event_id(),
Self::_Custom(e) => &e.event_id,
}
}
/// Returns the `sender` of this event.
pub fn sender(&self) -> &UserId {
match self {
Self::RoomRedaction(e) => &e.sender,
#[cfg(feature = "unstable-msc4293")]
Self::RoomMember(e) => e.sender(),
Self::_Custom(e) => &e.sender,
}
}
}
/// An `m.room.redaction` event as found in `unsigned.redacted_because`.
///
/// While servers usually send this with the `redacts` field (unless nested), the ID of the event
/// being redacted is known from context wherever this type is used, so it's not reflected as a
/// field here.
///
/// It is intentionally not possible to create an instance of this type other than through `Clone`
/// or `Deserialize`.
#[derive(Clone, Debug, Deserialize)]
#[non_exhaustive]
pub struct UnsignedRoomRedactionEvent {
/// Data specific to the event type.
pub content: RoomRedactionEventContent,
/// The globally unique event identifier for the user who sent the event.
pub event_id: OwnedEventId,
/// The fully-qualified ID of the user who sent this event.
pub sender: OwnedUserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
/// Additional key-value pairs not signed by the homeserver.
#[serde(default)]
pub unsigned: MessageLikeUnsigned<RoomRedactionEventContent>,
}
/// A custom redaction event.
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct CustomRedactionEvent {
/// The type of the event
event_type: Box<str>,
/// The globally unique event identifier for the user who sent the event.
event_id: OwnedEventId,
/// The fully-qualified ID of the user who sent this event.
sender: OwnedUserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
origin_server_ts: MilliSecondsSinceUnixEpoch,
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use js_int::uint;
use serde_json::{from_value as from_json_value, json};
use super::AnyRedactionEvent;
use crate::TimelineEventType;
#[test]
fn deserialize_any_redaction_event_room_redaction() {
let json = json!({
"type": "m.room.redaction",
"content": {
"redacts": "$redactedevent",
},
"event_id": "$redactionevent",
"origin_server_ts": 1,
"sender": "@carl:example.com",
});
let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
assert_eq!(event.event_id(), "$redactionevent");
assert_eq!(event.origin_server_ts().0, uint!(1));
assert_eq!(event.sender(), "@carl:example.com");
assert_eq!(event.event_type(), TimelineEventType::RoomRedaction);
assert_matches!(event, AnyRedactionEvent::RoomRedaction(_));
}
#[test]
fn deserialize_any_redaction_event_custom() {
let json = json!({
"type": "local.dev.custom_type",
"content": {},
"event_id": "$redactionevent",
"origin_server_ts": 1,
"sender": "@carl:example.com",
});
let event = from_json_value::<AnyRedactionEvent>(json).unwrap();
assert_eq!(event.event_id(), "$redactionevent");
assert_eq!(event.origin_server_ts().0, uint!(1));
assert_eq!(event.sender(), "@carl:example.com");
assert_eq!(event.event_type().to_string(), "local.dev.custom_type");
}
}