use std::{cmp::Ordering, ops::Deref};
use ruma_common::{
MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedServerName, OwnedSpaceChildOrder, OwnedUserId,
RoomId, SpaceChildOrder,
serde::{JsonCastable, JsonObject},
};
use ruma_macros::{Event, EventContent};
use serde::{Deserialize, Serialize};
use crate::{StateEvent, SyncStateEvent};
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_event(type = "m.space.child", kind = State, state_key_type = OwnedRoomId)]
pub struct SpaceChildEventContent {
pub via: Vec<OwnedServerName>,
#[serde(
default,
deserialize_with = "ruma_common::serde::default_on_error",
skip_serializing_if = "Option::is_none"
)]
pub order: Option<OwnedSpaceChildOrder>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub suggested: bool,
}
impl SpaceChildEventContent {
pub fn new(via: Vec<OwnedServerName>) -> Self {
Self { via, order: None, suggested: false }
}
}
impl PossiblyRedactedSpaceChildEventContent {
pub fn is_valid(&self) -> bool {
self.via.is_some()
}
}
#[derive(Clone, Debug, Event)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct HierarchySpaceChildEvent {
pub content: SpaceChildEventContent,
pub sender: OwnedUserId,
pub state_key: OwnedRoomId,
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
}
impl PartialEq for HierarchySpaceChildEvent {
fn eq(&self, other: &Self) -> bool {
self.space_child_ord_fields().eq(&other.space_child_ord_fields())
}
}
impl Eq for HierarchySpaceChildEvent {}
impl Ord for HierarchySpaceChildEvent {
fn cmp(&self, other: &Self) -> Ordering {
self.space_child_ord_fields().cmp(&other.space_child_ord_fields())
}
}
impl PartialOrd for HierarchySpaceChildEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl JsonCastable<HierarchySpaceChildEvent> for SpaceChildEvent {}
impl JsonCastable<HierarchySpaceChildEvent> for OriginalSpaceChildEvent {}
impl JsonCastable<HierarchySpaceChildEvent> for SyncSpaceChildEvent {}
impl JsonCastable<HierarchySpaceChildEvent> for OriginalSyncSpaceChildEvent {}
impl JsonCastable<JsonObject> for HierarchySpaceChildEvent {}
pub trait SpaceChildOrd {
#[doc(hidden)]
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_>;
fn cmp_space_child(&self, other: &impl SpaceChildOrd) -> Ordering {
self.space_child_ord_fields().cmp(&other.space_child_ord_fields())
}
}
#[doc(hidden)]
#[derive(PartialEq, Eq)]
pub struct SpaceChildOrdFields<'a> {
order: Option<&'a SpaceChildOrder>,
origin_server_ts: MilliSecondsSinceUnixEpoch,
state_key: &'a RoomId,
}
impl<'a> SpaceChildOrdFields<'a> {
fn new(
order: Option<&'a SpaceChildOrder>,
origin_server_ts: MilliSecondsSinceUnixEpoch,
state_key: &'a RoomId,
) -> Self {
Self { order, origin_server_ts, state_key }
}
}
impl<'a> Ord for SpaceChildOrdFields<'a> {
fn cmp(&self, other: &Self) -> Ordering {
match (self.order, other.order) {
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(Some(self_order), Some(other_order)) => self_order.cmp(other_order),
(None, None) => Ordering::Equal,
}
.then_with(|| self.origin_server_ts.cmp(&other.origin_server_ts))
.then_with(|| self.state_key.cmp(other.state_key))
}
}
impl<'a> PartialOrd for SpaceChildOrdFields<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> SpaceChildOrd for &T
where
T: SpaceChildOrd,
{
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
(*self).space_child_ord_fields()
}
}
impl SpaceChildOrd for OriginalSpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
SpaceChildOrdFields::new(
self.content.order.as_deref(),
self.origin_server_ts,
&self.state_key,
)
}
}
impl SpaceChildOrd for RedactedSpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
SpaceChildOrdFields::new(None, self.origin_server_ts, &self.state_key)
}
}
impl SpaceChildOrd for SpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
match self {
StateEvent::Original(original) => original.space_child_ord_fields(),
StateEvent::Redacted(redacted) => redacted.space_child_ord_fields(),
}
}
}
impl SpaceChildOrd for OriginalSyncSpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
SpaceChildOrdFields::new(
self.content.order.as_deref(),
self.origin_server_ts,
&self.state_key,
)
}
}
impl SpaceChildOrd for RedactedSyncSpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
SpaceChildOrdFields::new(None, self.origin_server_ts, &self.state_key)
}
}
impl SpaceChildOrd for SyncSpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
match self {
SyncStateEvent::Original(original) => original.space_child_ord_fields(),
SyncStateEvent::Redacted(redacted) => redacted.space_child_ord_fields(),
}
}
}
impl SpaceChildOrd for HierarchySpaceChildEvent {
fn space_child_ord_fields(&self) -> SpaceChildOrdFields<'_> {
SpaceChildOrdFields::new(
self.content.order.as_deref(),
self.origin_server_ts,
&self.state_key,
)
}
}
#[derive(Debug, Clone)]
#[allow(clippy::exhaustive_structs)]
pub struct SpaceChildOrdHelper<T: SpaceChildOrd>(pub T);
impl<T: SpaceChildOrd> PartialEq for SpaceChildOrdHelper<T> {
fn eq(&self, other: &Self) -> bool {
self.0.space_child_ord_fields().eq(&other.0.space_child_ord_fields())
}
}
impl<T: SpaceChildOrd> Eq for SpaceChildOrdHelper<T> {}
impl<T: SpaceChildOrd> Ord for SpaceChildOrdHelper<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp_space_child(&other.0)
}
}
impl<T: SpaceChildOrd> PartialOrd for SpaceChildOrdHelper<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: SpaceChildOrd> Deref for SpaceChildOrdHelper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use std::{collections::BTreeSet, iter::repeat_n};
use js_int::{UInt, uint};
use ruma_common::{
MilliSecondsSinceUnixEpoch, OwnedRoomId, SpaceChildOrder,
canonical_json::assert_to_canonical_json_eq, owned_room_id, owned_server_name,
owned_user_id, server_name,
};
use serde_json::{from_value as from_json_value, json};
use super::{
HierarchySpaceChildEvent, SpaceChildEventContent, SpaceChildOrd, SpaceChildOrdHelper,
};
#[test]
fn space_child_serialization() {
let content = SpaceChildEventContent {
via: vec![owned_server_name!("example.com")],
order: Some(SpaceChildOrder::parse("uwu").unwrap()),
suggested: false,
};
assert_to_canonical_json_eq!(
content,
json!({
"via": ["example.com"],
"order": "uwu",
}),
);
}
#[test]
fn space_child_empty_serialization() {
let content = SpaceChildEventContent { via: vec![], order: None, suggested: false };
assert_to_canonical_json_eq!(content, json!({ "via": [] }));
}
#[test]
fn space_child_content_deserialization_order() {
let via = server_name!("localhost");
let json = json!({
"order": "aaa",
"via": [via],
});
let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
assert_eq!(content.order.unwrap(), "aaa");
assert!(!content.suggested);
assert_eq!(content.via, &[via]);
let json = json!({
"order": 2,
"via": [via],
});
let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
assert_eq!(content.order, None);
assert!(!content.suggested);
assert_eq!(content.via, &[via]);
let json = json!({
"order": "",
"via": [via],
});
let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
assert_eq!(content.order.unwrap(), "");
assert!(!content.suggested);
assert_eq!(content.via, &[via]);
let order = repeat_n('a', 60).collect::<String>();
let json = json!({
"order": order,
"via": [via],
});
let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
assert_eq!(content.order, None);
assert!(!content.suggested);
assert_eq!(content.via, &[via]);
let json = json!({
"order": "🔝",
"via": [via],
});
let content = from_json_value::<SpaceChildEventContent>(json).unwrap();
assert_eq!(content.order, None);
assert!(!content.suggested);
assert_eq!(content.via, &[via]);
}
#[test]
fn hierarchy_space_child_deserialization() {
let json = json!({
"content": {
"via": [
"example.org"
]
},
"origin_server_ts": 1_629_413_349,
"sender": "@alice:example.org",
"state_key": "!a:example.org",
"type": "m.space.child"
});
let ev = from_json_value::<HierarchySpaceChildEvent>(json).unwrap();
assert_eq!(ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1_629_413_349)));
assert_eq!(ev.sender, "@alice:example.org");
assert_eq!(ev.state_key, "!a:example.org");
assert_eq!(ev.content.via, ["example.org"]);
assert_eq!(ev.content.order, None);
assert!(!ev.content.suggested);
}
fn hierarchy_space_child_event(
state_key: OwnedRoomId,
order: Option<&str>,
origin_server_ts: UInt,
) -> HierarchySpaceChildEvent {
let mut content = SpaceChildEventContent::new(vec![owned_server_name!("example.org")]);
content.order = order.and_then(|order| SpaceChildOrder::parse(order).ok());
HierarchySpaceChildEvent {
content,
sender: owned_user_id!("@alice:example.org"),
state_key,
origin_server_ts: MilliSecondsSinceUnixEpoch(origin_server_ts),
}
}
#[test]
fn space_child_ord_spec_example() {
let child_a = hierarchy_space_child_event(
owned_room_id!("!a:example.org"),
Some("aaaa"),
uint!(1_640_141_000),
);
let child_b = hierarchy_space_child_event(
owned_room_id!("!b:example.org"),
Some(" "),
uint!(1_640_341_000),
);
let child_c = hierarchy_space_child_event(
owned_room_id!("!c:example.org"),
Some("first"),
uint!(1_640_841_000),
);
let child_d = hierarchy_space_child_event(
owned_room_id!("!d:example.org"),
None,
uint!(1_640_741_000),
);
let child_e = hierarchy_space_child_event(
owned_room_id!("!e:example.org"),
None,
uint!(1_640_641_000),
);
let events =
[child_a.clone(), child_b.clone(), child_c.clone(), child_d.clone(), child_e.clone()];
let mut sorted_events = events.clone();
sorted_events.sort_by(SpaceChildOrd::cmp_space_child);
assert_eq!(sorted_events[0].state_key, child_b.state_key);
assert_eq!(sorted_events[1].state_key, child_a.state_key);
assert_eq!(sorted_events[2].state_key, child_c.state_key);
assert_eq!(sorted_events[3].state_key, child_e.state_key);
assert_eq!(sorted_events[4].state_key, child_d.state_key);
let sorted_events = events.clone().into_iter().collect::<BTreeSet<_>>();
let mut iter = sorted_events.iter();
assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
let sorted_events = events.into_iter().map(SpaceChildOrdHelper).collect::<BTreeSet<_>>();
let mut iter = sorted_events.iter();
assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
}
#[test]
fn space_child_ord_other_example() {
let child_a = hierarchy_space_child_event(
owned_room_id!("!a:example.org"),
Some("🔝"),
uint!(1_640_141_000),
);
let child_b = hierarchy_space_child_event(
owned_room_id!("!b:example.org"),
Some(" "),
uint!(1_640_341_000),
);
let child_c = hierarchy_space_child_event(
owned_room_id!("!c:example.org"),
None,
uint!(1_640_841_000),
);
let child_d = hierarchy_space_child_event(
owned_room_id!("!d:example.org"),
None,
uint!(1_640_741_000),
);
let child_e = hierarchy_space_child_event(
owned_room_id!("!e:example.org"),
None,
uint!(1_640_741_000),
);
let mut events =
[child_a.clone(), child_b.clone(), child_c.clone(), child_d.clone(), child_e.clone()];
events.sort_by(SpaceChildOrd::cmp_space_child);
let mut sorted_events = events.clone();
sorted_events.sort_by(SpaceChildOrd::cmp_space_child);
assert_eq!(sorted_events[0].state_key, child_b.state_key);
assert_eq!(sorted_events[1].state_key, child_a.state_key);
assert_eq!(sorted_events[2].state_key, child_d.state_key);
assert_eq!(sorted_events[3].state_key, child_e.state_key);
assert_eq!(sorted_events[4].state_key, child_c.state_key);
let sorted_events = events.clone().into_iter().collect::<BTreeSet<_>>();
let mut iter = sorted_events.iter();
assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
let sorted_events = events.into_iter().map(SpaceChildOrdHelper).collect::<BTreeSet<_>>();
let mut iter = sorted_events.iter();
assert_eq!(iter.next().unwrap().state_key, child_b.state_key);
assert_eq!(iter.next().unwrap().state_key, child_a.state_key);
assert_eq!(iter.next().unwrap().state_key, child_d.state_key);
assert_eq!(iter.next().unwrap().state_key, child_e.state_key);
assert_eq!(iter.next().unwrap().state_key, child_c.state_key);
}
}