1use ruma::{
2 OwnedEventId,
3 events::{
4 AnySyncStateEvent, AnySyncTimelineEvent, PossiblyRedactedStateEventContent, RedactContent,
5 RedactedStateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
6 StrippedStateEvent, SyncStateEvent,
7 room::{
8 create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
9 member::PossiblyRedactedRoomMemberEventContent,
10 },
11 },
12 room_version_rules::RedactionRules,
13 serde::Raw,
14};
15use serde::{Deserialize, Serialize};
16use tracing::{error, warn};
17
18use crate::room::RoomCreateWithCreatorEventContent;
19
20#[derive(Clone, Debug, Deserialize, Serialize)]
26#[serde(
27 bound(serialize = "C: Serialize + Clone"),
28 from = "MinimalStateEventSerdeHelper<C>",
29 into = "MinimalStateEventSerdeHelper<C>"
30)]
31pub struct MinimalStateEvent<C: PossiblyRedactedStateEventContent + RedactContent> {
32 pub content: C,
34 pub event_id: Option<OwnedEventId>,
36}
37
38impl<C> MinimalStateEvent<C>
39where
40 C: PossiblyRedactedStateEventContent + RedactContent,
41 C::Redacted: Into<C>,
42{
43 pub fn redact(&mut self, rules: &RedactionRules)
47 where
48 C: Clone,
49 {
50 self.content = self.content.clone().redact(rules).into()
51 }
52}
53
54#[derive(Serialize, Deserialize)]
56enum MinimalStateEventSerdeHelper<C> {
57 Original(MinimalStateEventSerdeHelperInner<C>),
59 Redacted(MinimalStateEventSerdeHelperInner<C>),
61 PossiblyRedacted(MinimalStateEventSerdeHelperInner<C>),
63}
64
65impl<C> From<MinimalStateEventSerdeHelper<C>> for MinimalStateEvent<C>
66where
67 C: PossiblyRedactedStateEventContent + RedactContent,
68{
69 fn from(value: MinimalStateEventSerdeHelper<C>) -> Self {
70 match value {
71 MinimalStateEventSerdeHelper::Original(event) => event,
72 MinimalStateEventSerdeHelper::Redacted(event) => event,
73 MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event,
74 }
75 .into()
76 }
77}
78
79impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelper<C>
80where
81 C: PossiblyRedactedStateEventContent + RedactContent,
82{
83 fn from(value: MinimalStateEvent<C>) -> Self {
84 Self::PossiblyRedacted(value.into())
85 }
86}
87
88#[derive(Serialize, Deserialize)]
89struct MinimalStateEventSerdeHelperInner<C> {
90 content: C,
91 event_id: Option<OwnedEventId>,
92}
93
94impl<C> From<MinimalStateEventSerdeHelperInner<C>> for MinimalStateEvent<C>
95where
96 C: PossiblyRedactedStateEventContent + RedactContent,
97{
98 fn from(value: MinimalStateEventSerdeHelperInner<C>) -> Self {
99 let MinimalStateEventSerdeHelperInner { content, event_id } = value;
100 Self { content, event_id }
101 }
102}
103
104impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelperInner<C>
105where
106 C: PossiblyRedactedStateEventContent + RedactContent,
107{
108 fn from(value: MinimalStateEvent<C>) -> Self {
109 let MinimalStateEvent { content, event_id } = value;
110 Self { content, event_id }
111 }
112}
113
114pub type MinimalRoomMemberEvent = MinimalStateEvent<PossiblyRedactedRoomMemberEventContent>;
116
117impl<C1, C2> From<SyncStateEvent<C1>> for MinimalStateEvent<C2>
118where
119 C1: StaticStateEventContent + RedactContent + Into<C2>,
120 C1::Redacted: RedactedStateEventContent + Into<C2>,
121 C2: PossiblyRedactedStateEventContent + RedactContent,
122{
123 fn from(ev: SyncStateEvent<C1>) -> Self {
124 match ev {
125 SyncStateEvent::Original(ev) => {
126 Self { content: ev.content.into(), event_id: Some(ev.event_id) }
127 }
128 SyncStateEvent::Redacted(ev) => {
129 Self { content: ev.content.into(), event_id: Some(ev.event_id) }
130 }
131 }
132 }
133}
134
135impl<C1, C2> From<&SyncStateEvent<C1>> for MinimalStateEvent<C2>
136where
137 C1: Clone + StaticStateEventContent + RedactContent + Into<C2>,
138 C1::Redacted: Clone + RedactedStateEventContent + Into<C2>,
139 C2: PossiblyRedactedStateEventContent + RedactContent,
140{
141 fn from(ev: &SyncStateEvent<C1>) -> Self {
142 match ev {
143 SyncStateEvent::Original(ev) => {
144 Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
145 }
146 SyncStateEvent::Redacted(ev) => {
147 Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
148 }
149 }
150 }
151}
152
153impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
154 fn from(ev: &SyncRoomCreateEvent) -> Self {
155 match ev {
156 SyncStateEvent::Original(ev) => Self {
157 content: RoomCreateWithCreatorEventContent::from_event_content(
158 ev.content.clone(),
159 ev.sender.clone(),
160 ),
161 event_id: Some(ev.event_id.clone()),
162 },
163 SyncStateEvent::Redacted(ev) => Self {
164 content: RoomCreateWithCreatorEventContent::from_event_content(
165 ev.content.clone(),
166 ev.sender.clone(),
167 ),
168 event_id: Some(ev.event_id.clone()),
169 },
170 }
171 }
172}
173
174impl<C> From<StrippedStateEvent<C>> for MinimalStateEvent<C>
175where
176 C: PossiblyRedactedStateEventContent + RedactContent,
177{
178 fn from(event: StrippedStateEvent<C>) -> Self {
179 Self { content: event.content, event_id: None }
180 }
181}
182
183impl<C> From<&StrippedStateEvent<C>> for MinimalStateEvent<C>
184where
185 C: Clone + PossiblyRedactedStateEventContent + RedactContent,
186{
187 fn from(event: &StrippedStateEvent<C>) -> Self {
188 Self { content: event.content.clone(), event_id: None }
189 }
190}
191
192impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
193 fn from(event: &StrippedRoomCreateEvent) -> Self {
194 let content = RoomCreateWithCreatorEventContent::from_event_content(
195 event.content.clone(),
196 event.sender.clone(),
197 );
198 Self { content, event_id: None }
199 }
200}
201
202#[derive(Debug, Clone)]
208pub struct RawSyncStateEventWithKeys {
209 pub raw: Raw<AnySyncStateEvent>,
211 pub event_type: StateEventType,
213 pub state_key: String,
215 cached_event: Option<Result<AnySyncStateEvent, ()>>,
217}
218
219impl RawSyncStateEventWithKeys {
220 pub fn try_from_raw_state_event(raw: Raw<AnySyncStateEvent>) -> Option<Self> {
225 let StateEventWithKeysDeHelper { event_type, state_key } =
226 match raw.deserialize_as_unchecked() {
227 Ok(fields) => fields,
228 Err(error) => {
229 warn!(?error, "Couldn't deserialize type and state key of state event");
230 return None;
231 }
232 };
233
234 let Some(state_key) = state_key else {
236 warn!(
237 ?event_type,
238 "Couldn't deserialize type and state key of state event: missing state key"
239 );
240 return None;
241 };
242
243 Some(Self { raw, event_type, state_key, cached_event: None })
244 }
245
246 pub fn try_from_raw_timeline_event(raw: &Raw<AnySyncTimelineEvent>) -> Option<Self> {
252 let StateEventWithKeysDeHelper { event_type, state_key } = match raw
253 .deserialize_as_unchecked()
254 {
255 Ok(fields) => fields,
256 Err(error) => {
257 warn!(?error, "Couldn't deserialize type and optional state key of timeline event");
258 return None;
259 }
260 };
261
262 Some(Self {
264 event_type,
265 state_key: state_key?,
266 raw: raw.clone().cast_unchecked(),
267 cached_event: None,
268 })
269 }
270
271 pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
285 where
286 F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
287 C: StaticEventContent + StaticStateEventContent + RedactContent,
288 C::Redacted: RedactedStateEventContent,
289 {
290 let any_event = self
291 .cached_event
292 .get_or_insert_with(|| {
293 self.raw.deserialize().map_err(|error| {
294 warn!(event_type = ?C::TYPE, ?error, "Couldn't deserialize state event");
295 })
296 })
297 .as_ref()
298 .ok()?;
299
300 let event = as_variant_fn(any_event);
301
302 if event.is_none() {
303 error!(
305 expected_event_type = ?C::TYPE,
306 actual_event_type = ?any_event.event_type().to_string(),
307 "Couldn't deserialize state event: unexpected type",
308 );
309 }
310
311 event
312 }
313
314 pub(crate) fn set_cached_event(&mut self, event: AnySyncStateEvent) {
321 self.cached_event = Some(Ok(event));
322 }
323}
324
325#[derive(Deserialize)]
327struct StateEventWithKeysDeHelper {
328 #[serde(rename = "type")]
329 event_type: StateEventType,
330 state_key: Option<String>,
333}
334
335#[cfg(test)]
336mod tests {
337 use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
338
339 use super::MinimalStateEvent;
340
341 #[test]
342 fn test_backward_compatible_deserialize_minimal_state_event() {
343 let event_id = event_id!("$event");
344
345 let event =
347 serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
348 r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
349 )
350 .unwrap();
351 assert_eq!(event.content.name.as_deref(), Some("My Room"));
352 assert_eq!(event.event_id.as_deref(), Some(event_id));
353
354 let event =
355 serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
356 r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
357 )
358 .unwrap();
359 assert_eq!(event.content.name, None);
360 assert_eq!(event.event_id.as_deref(), Some(event_id));
361
362 let event =
364 serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
365 r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
366 )
367 .unwrap();
368 assert_eq!(event.content.name.as_deref(), Some("My Room"));
369 assert_eq!(event.event_id.as_deref(), Some(event_id));
370
371 let event =
372 serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
373 r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
374 )
375 .unwrap();
376 assert_eq!(event.content.name, None);
377 assert_eq!(event.event_id.as_deref(), Some(event_id));
378 }
379}