Toward abstracting Pdu into trait Event.

Co-authored-by: Jade Ellis <jade@ellis.link>
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2025-04-26 08:24:47 +00:00
committed by Jade Ellis
parent 3d0360bcd6
commit 116f85360f
41 changed files with 842 additions and 886 deletions
+21
View File
@@ -0,0 +1,21 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use super::Event;
use crate::{Result, err};
#[inline]
#[must_use]
pub(super) fn as_value<E: Event>(event: &E) -> JsonValue {
get(event).expect("Failed to represent Event content as JsonValue")
}
#[inline]
pub(super) fn get<T, E>(event: &E) -> Result<T>
where
T: for<'de> Deserialize<'de>,
E: Event,
{
serde_json::from_str(event.content().get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))
}
+219
View File
@@ -0,0 +1,219 @@
use ruma::{
events::{
AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent,
AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, room::member::RoomMemberEventContent,
space::child::HierarchySpaceChildEvent,
},
serde::Raw,
};
use serde_json::json;
use super::{Event, redact};
pub struct Owned<E: Event>(pub(super) E);
pub struct Ref<'a, E: Event>(pub(super) &'a E);
impl<E: Event> From<Owned<E>> for Raw<AnySyncTimelineEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"type": event.event_type(),
});
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyTimelineEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyMessageLikeEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyMessageLikeEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnySyncStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyStrippedStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStrippedStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<HierarchySpaceChildEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<HierarchySpaceChildEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<StateEvent<RoomMemberEventContent>> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<StateEvent<RoomMemberEventContent>> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"redacts": event.redacts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
+86
View File
@@ -0,0 +1,86 @@
use ruma::{
OwnedEventId, RoomVersionId,
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
};
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use super::Event;
/// Copies the `redacts` property of the event to the `content` dict and
/// vice-versa.
///
/// This follows the specification's
/// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property):
///
/// > For backwards-compatibility with older clients, servers should add a
/// > redacts property to the top level of m.room.redaction events in when
/// > serving such events over the Client-Server API.
///
/// > For improved compatibility with newer clients, servers should add a
/// > redacts property to the content of m.room.redaction events in older
/// > room versions when serving such events over the Client-Server API.
#[must_use]
pub(super) fn copy<E: Event>(event: &E) -> (Option<OwnedEventId>, Box<RawJsonValue>) {
if *event.event_type() != TimelineEventType::RoomRedaction {
return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
}
let Ok(mut content) = event.get_content::<RoomRedactionEventContent>() else {
return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
};
if let Some(redacts) = content.redacts {
return (Some(redacts), event.content().to_owned());
}
if let Some(redacts) = event.redacts().map(ToOwned::to_owned) {
content.redacts = Some(redacts);
return (
event.redacts().map(ToOwned::to_owned),
to_raw_value(&content).expect("Must be valid, we only added redacts field"),
);
}
(event.redacts().map(ToOwned::to_owned), event.content().to_owned())
}
#[must_use]
pub(super) fn is_redacted<E: Event>(event: &E) -> bool {
let Some(unsigned) = event.unsigned() else {
return false;
};
let Ok(unsigned) = ExtractRedactedBecause::deserialize(unsigned) else {
return false;
};
unsigned.redacted_because.is_some()
}
#[must_use]
pub(super) fn redacts_id<E: Event>(
event: &E,
room_version: &RoomVersionId,
) -> Option<OwnedEventId> {
use RoomVersionId::*;
if *event.kind() != TimelineEventType::RoomRedaction {
return None;
}
match *room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
event.redacts().map(ToOwned::to_owned),
| _ =>
event
.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts,
}
}
#[derive(Deserialize)]
struct ExtractRedactedBecause {
redacted_because: Option<serde::de::IgnoredAny>,
}
+32
View File
@@ -0,0 +1,32 @@
use ruma::events::{StateEventType, TimelineEventType};
use super::StateKey;
/// Convenience trait for adding event type plus state key to state maps.
pub trait TypeExt {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey);
}
impl TypeExt for StateEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self, state_key.into())
}
}
impl TypeExt for &StateEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.clone(), state_key.into())
}
}
impl TypeExt for TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.into(), state_key.into())
}
}
impl TypeExt for &TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.clone().into(), state_key.into())
}
}