1use std::{cmp::Ordering, ops::Deref};
6
7use ruma_common::{
8 MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedServerName, OwnedSpaceChildOrder, OwnedUserId,
9 RoomId, SpaceChildOrder,
10 serde::{JsonCastable, JsonObject},
11};
12use ruma_macros::{Event, EventContent};
13use serde::{Deserialize, Serialize};
14
15use crate::{StateEvent, SyncStateEvent};
16
17#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
25#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
26#[ruma_event(type = "m.space.child", kind = State, state_key_type = OwnedRoomId)]
27pub struct SpaceChildEventContent {
28 pub via: Vec<OwnedServerName>,
30
31 #[serde(
43 default,
44 deserialize_with = "ruma_common::serde::default_on_error",
45 skip_serializing_if = "Option::is_none"
46 )]
47 pub order: Option<OwnedSpaceChildOrder>,
48
49 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
58 pub suggested: bool,
59}
60
61impl SpaceChildEventContent {
62 pub fn new(via: Vec<OwnedServerName>) -> Self {
64 Self { via, order: None, suggested: false }
65 }
66}
67
68impl PossiblyRedactedSpaceChildEventContent {
69 pub fn is_valid(&self) -> bool {
77 self.via.is_some()
78 }
79}
80
81#[derive(Clone, Debug, Event)]
84#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
85pub struct HierarchySpaceChildEvent {
86 pub content: SpaceChildEventContent,
88
89 pub sender: OwnedUserId,
91
92 pub state_key: OwnedRoomId,
94
95 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
97}
98
99impl PartialEq for HierarchySpaceChildEvent {
100 fn eq(&self, other: &Self) -> bool {
101 self.space_child_ord_fields().eq(&other.space_child_ord_fields())
102 }
103}
104
105impl Eq for HierarchySpaceChildEvent {}
106
107impl Ord for HierarchySpaceChildEvent {
108 fn cmp(&self, other: &Self) -> Ordering {
109 self.space_child_ord_fields().cmp(&other.space_child_ord_fields())
110 }
111}
112
113impl PartialOrd for HierarchySpaceChildEvent {
114 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115 Some(self.cmp(other))
116 }
117}
118
119impl JsonCastable<HierarchySpaceChildEvent> for SpaceChildEvent {}
120
121impl JsonCastable<HierarchySpaceChildEvent> for OriginalSpaceChildEvent {}
122
123impl JsonCastable<HierarchySpaceChildEvent> for SyncSpaceChildEvent {}
124
125impl JsonCastable<HierarchySpaceChildEvent> for OriginalSyncSpaceChildEvent {}
126
127impl JsonCastable<JsonObject> for HierarchySpaceChildEvent {}
128
129pub trait SpaceChildOrd {
137 #[doc(hidden)]
138 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_>;
139
140 fn cmp_space_child(&self, other: &impl SpaceChildOrd) -> Ordering {
145 self.space_child_ord_fields().cmp(&other.space_child_ord_fields())
146 }
147}
148
149#[doc(hidden)]
154#[derive(PartialEq, Eq)]
155pub struct SpaceChildOrdFields<'a> {
156 order: Option<&'a SpaceChildOrder>,
157 origin_server_ts: MilliSecondsSinceUnixEpoch,
158 state_key: &'a RoomId,
159}
160
161impl<'a> SpaceChildOrdFields<'a> {
162 fn new(
166 order: Option<&'a SpaceChildOrder>,
167 origin_server_ts: MilliSecondsSinceUnixEpoch,
168 state_key: &'a RoomId,
169 ) -> Self {
170 Self { order, origin_server_ts, state_key }
171 }
172}
173
174impl<'a> Ord for SpaceChildOrdFields<'a> {
175 fn cmp(&self, other: &Self) -> Ordering {
176 match (self.order, other.order) {
177 (Some(_), None) => Ordering::Less,
179 (None, Some(_)) => Ordering::Greater,
180 (Some(self_order), Some(other_order)) => self_order.cmp(other_order),
181 (None, None) => Ordering::Equal,
182 }
183 .then_with(|| self.origin_server_ts.cmp(&other.origin_server_ts))
184 .then_with(|| self.state_key.cmp(other.state_key))
185 }
186}
187
188impl<'a> PartialOrd for SpaceChildOrdFields<'a> {
189 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
190 Some(self.cmp(other))
191 }
192}
193
194impl<T> SpaceChildOrd for &T
195where
196 T: SpaceChildOrd,
197{
198 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
199 (*self).space_child_ord_fields()
200 }
201}
202
203impl SpaceChildOrd for OriginalSpaceChildEvent {
204 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
205 SpaceChildOrdFields::new(
206 self.content.order.as_deref(),
207 self.origin_server_ts,
208 &self.state_key,
209 )
210 }
211}
212
213impl SpaceChildOrd for RedactedSpaceChildEvent {
214 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
215 SpaceChildOrdFields::new(None, self.origin_server_ts, &self.state_key)
216 }
217}
218
219impl SpaceChildOrd for SpaceChildEvent {
220 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
221 match self {
222 StateEvent::Original(original) => original.space_child_ord_fields(),
223 StateEvent::Redacted(redacted) => redacted.space_child_ord_fields(),
224 }
225 }
226}
227
228impl SpaceChildOrd for OriginalSyncSpaceChildEvent {
229 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
230 SpaceChildOrdFields::new(
231 self.content.order.as_deref(),
232 self.origin_server_ts,
233 &self.state_key,
234 )
235 }
236}
237
238impl SpaceChildOrd for RedactedSyncSpaceChildEvent {
239 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
240 SpaceChildOrdFields::new(None, self.origin_server_ts, &self.state_key)
241 }
242}
243
244impl SpaceChildOrd for SyncSpaceChildEvent {
245 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
246 match self {
247 SyncStateEvent::Original(original) => original.space_child_ord_fields(),
248 SyncStateEvent::Redacted(redacted) => redacted.space_child_ord_fields(),
249 }
250 }
251}
252
253impl SpaceChildOrd for HierarchySpaceChildEvent {
254 fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
255 SpaceChildOrdFields::new(
256 self.content.order.as_deref(),
257 self.origin_server_ts,
258 &self.state_key,
259 )
260 }
261}
262
263#[derive(Debug, Clone)]
270#[allow(clippy::exhaustive_structs)]
271pub struct SpaceChildOrdHelper<T: SpaceChildOrd>(pub T);
272
273impl<T: SpaceChildOrd> PartialEq for SpaceChildOrdHelper<T> {
274 fn eq(&self, other: &Self) -> bool {
275 self.0.space_child_ord_fields().eq(&other.0.space_child_ord_fields())
276 }
277}
278
279impl<T: SpaceChildOrd> Eq for SpaceChildOrdHelper<T> {}
280
281impl<T: SpaceChildOrd> Ord for SpaceChildOrdHelper<T> {
282 fn cmp(&self, other: &Self) -> Ordering {
283 self.0.cmp_space_child(&other.0)
284 }
285}
286
287impl<T: SpaceChildOrd> PartialOrd for SpaceChildOrdHelper<T> {
288 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
289 Some(self.cmp(other))
290 }
291}
292
293impl<T: SpaceChildOrd> Deref for SpaceChildOrdHelper<T> {
294 type Target = T;
295
296 fn deref(&self) -> &Self::Target {
297 &self.0
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use std::{collections::BTreeSet, iter::repeat_n};
304
305 use js_int::{UInt, uint};
306 use ruma_common::{
307 MilliSecondsSinceUnixEpoch, OwnedRoomId, SpaceChildOrder,
308 canonical_json::assert_to_canonical_json_eq, owned_room_id, owned_server_name,
309 owned_user_id, server_name,
310 };
311 use serde_json::{from_value as from_json_value, json};
312
313 use super::{
314 HierarchySpaceChildEvent, SpaceChildEventContent, SpaceChildOrd, SpaceChildOrdHelper,
315 };
316
317 #[test]
318 fn space_child_serialization() {
319 let content = SpaceChildEventContent {
320 via: vec![owned_server_name!("example.com")],
321 order: Some(SpaceChildOrder::parse("uwu").unwrap()),
322 suggested: false,
323 };
324
325 assert_to_canonical_json_eq!(
326 content,
327 json!({
328 "via": ["example.com"],
329 "order": "uwu",
330 }),
331 );
332 }
333
334 #[test]
335 fn space_child_empty_serialization() {
336 let content = SpaceChildEventContent { via: vec![], order: None, suggested: false };
337
338 assert_to_canonical_json_eq!(content, json!({ "via": [] }));
339 }
340
341 #[test]
342 fn space_child_content_deserialization_order() {
343 let via = server_name!("localhost");
344
345 let json = json!({
347 "order": "aaa",
348 "via": [via],
349 });
350 let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
351 assert_eq!(content.order.unwrap(), "aaa");
352 assert!(!content.suggested);
353 assert_eq!(content.via, &[via]);
354
355 let json = json!({
357 "order": 2,
358 "via": [via],
359 });
360 let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
361 assert_eq!(content.order, None);
362 assert!(!content.suggested);
363 assert_eq!(content.via, &[via]);
364
365 let json = json!({
367 "order": "",
368 "via": [via],
369 });
370 let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
371 assert_eq!(content.order.unwrap(), "");
372 assert!(!content.suggested);
373 assert_eq!(content.via, &[via]);
374
375 let order = repeat_n('a', 60).collect::<String>();
377 let json = json!({
378 "order": order,
379 "via": [via],
380 });
381 let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
382 assert_eq!(content.order, None);
383 assert!(!content.suggested);
384 assert_eq!(content.via, &[via]);
385
386 let json = json!({
388 "order": "🔝",
389 "via": [via],
390 });
391 let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
392 assert_eq!(content.order, None);
393 assert!(!content.suggested);
394 assert_eq!(content.via, &[via]);
395 }
396
397 #[test]
398 fn hierarchy_space_child_deserialization() {
399 let json = json!({
400 "content": {
401 "via": [
402 "example.org"
403 ]
404 },
405 "origin_server_ts": 1_629_413_349,
406 "sender": "@alice:example.org",
407 "state_key": "!a:example.org",
408 "type": "m.space.child"
409 });
410
411 let ev = from_json_value::<HierarchySpaceChildEvent>(json).unwrap();
412 assert_eq!(ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1_629_413_349)));
413 assert_eq!(ev.sender, "@alice:example.org");
414 assert_eq!(ev.state_key, "!a:example.org");
415 assert_eq!(ev.content.via, ["example.org"]);
416 assert_eq!(ev.content.order, None);
417 assert!(!ev.content.suggested);
418 }
419
420 fn hierarchy_space_child_event(
422 state_key: OwnedRoomId,
423 order: Option<&str>,
424 origin_server_ts: UInt,
425 ) -> HierarchySpaceChildEvent {
426 let mut content = SpaceChildEventContent::new(vec![owned_server_name!("example.org")]);
427 content.order = order.and_then(|order| SpaceChildOrder::parse(order).ok());
428
429 HierarchySpaceChildEvent {
430 content,
431 sender: owned_user_id!("@alice:example.org"),
432 state_key,
433 origin_server_ts: MilliSecondsSinceUnixEpoch(origin_server_ts),
434 }
435 }
436
437 #[test]
438 fn space_child_ord_spec_example() {
439 let child_a = hierarchy_space_child_event(
441 owned_room_id!("!a:example.org"),
442 Some("aaaa"),
443 uint!(1_640_141_000),
444 );
445 let child_b = hierarchy_space_child_event(
446 owned_room_id!("!b:example.org"),
447 Some(" "),
448 uint!(1_640_341_000),
449 );
450 let child_c = hierarchy_space_child_event(
451 owned_room_id!("!c:example.org"),
452 Some("first"),
453 uint!(1_640_841_000),
454 );
455 let child_d = hierarchy_space_child_event(
456 owned_room_id!("!d:example.org"),
457 None,
458 uint!(1_640_741_000),
459 );
460 let child_e = hierarchy_space_child_event(
461 owned_room_id!("!e:example.org"),
462 None,
463 uint!(1_640_641_000),
464 );
465
466 let events =
467 [child_a.clone(), child_b.clone(), child_c.clone(), child_d.clone(), child_e.clone()];
468
469 let mut sorted_events = events.clone();
471 sorted_events.sort_by(SpaceChildOrd::cmp_space_child);
472 assert_eq!(sorted_events[0].state_key, child_b.state_key);
473 assert_eq!(sorted_events[1].state_key, child_a.state_key);
474 assert_eq!(sorted_events[2].state_key, child_c.state_key);
475 assert_eq!(sorted_events[3].state_key, child_e.state_key);
476 assert_eq!(sorted_events[4].state_key, child_d.state_key);
477
478 let sorted_events = events.clone().into_iter().collect::<BTreeSet<_>>();
480 let mut iter = sorted_events.iter();
481 assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
482 assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
483 assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
484 assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
485 assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
486
487 let sorted_events = events.into_iter().map(SpaceChildOrdHelper).collect::<BTreeSet<_>>();
489 let mut iter = sorted_events.iter();
490 assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
491 assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
492 assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
493 assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
494 assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
495 }
496
497 #[test]
498 fn space_child_ord_other_example() {
499 let child_a = hierarchy_space_child_event(
501 owned_room_id!("!a:example.org"),
502 Some("🔝"),
503 uint!(1_640_141_000),
504 );
505 let child_b = hierarchy_space_child_event(
506 owned_room_id!("!b:example.org"),
507 Some(" "),
508 uint!(1_640_341_000),
509 );
510 let child_c = hierarchy_space_child_event(
511 owned_room_id!("!c:example.org"),
512 None,
513 uint!(1_640_841_000),
514 );
515 let child_d = hierarchy_space_child_event(
516 owned_room_id!("!d:example.org"),
517 None,
518 uint!(1_640_741_000),
519 );
520 let child_e = hierarchy_space_child_event(
521 owned_room_id!("!e:example.org"),
522 None,
523 uint!(1_640_741_000),
524 );
525
526 let mut events =
527 [child_a.clone(), child_b.clone(), child_c.clone(), child_d.clone(), child_e.clone()];
528
529 events.sort_by(SpaceChildOrd::cmp_space_child);
530
531 let mut sorted_events = events.clone();
533 sorted_events.sort_by(SpaceChildOrd::cmp_space_child);
534 assert_eq!(sorted_events[0].state_key, child_b.state_key);
535 assert_eq!(sorted_events[1].state_key, child_a.state_key);
536 assert_eq!(sorted_events[2].state_key, child_d.state_key);
537 assert_eq!(sorted_events[3].state_key, child_e.state_key);
538 assert_eq!(sorted_events[4].state_key, child_c.state_key);
539
540 let sorted_events = events.clone().into_iter().collect::<BTreeSet<_>>();
542 let mut iter = sorted_events.iter();
543 assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
544 assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
545 assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
546 assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
547 assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
548
549 let sorted_events = events.into_iter().map(SpaceChildOrdHelper).collect::<BTreeSet<_>>();
551 let mut iter = sorted_events.iter();
552 assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
553 assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
554 assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
555 assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
556 assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
557 }
558}