feat: Create versioned PDU types

This commit is contained in:
timedout committed 2026-07-12 00:07:52 +01:00
1 parent 6858e1b893
commit 3c2274b9e7
6 files changed
+299 -9

No files matched your search

+1 -3
View File
@@ -143,8 +143,6 @@ pub trait Event: Clone + Debug {
fn into_pdu(self) -> Pdu;
fn is_owned(&self) -> bool;
//
// Canonical properties
//
@@ -182,12 +180,12 @@ pub trait Event: Clone + Debug {
fn state_key(&self) -> Option<&str>;
/// The event type.
// #[deprecated]
fn kind(&self) -> &TimelineEventType;
/// Metadata container; peer-trusted only.
fn unsigned(&self) -> Option<&RawJsonValue>;
//#[deprecated]
#[inline]
fn event_type(&self) -> &TimelineEventType { self.kind() }
}
+4 -6
View File
@@ -1,11 +1,15 @@
mod count;
pub mod hashes;
mod id;
pub mod metadata;
mod partial;
mod raw_id;
mod redact;
#[cfg(test)]
mod tests;
mod unsigned;
pub mod v2;
pub mod v3;
use std::cmp::Ordering;
@@ -151,9 +155,6 @@ impl Event for Pdu {
#[inline]
fn into_pdu(self) -> Pdu { self }
#[inline]
fn is_owned(&self) -> bool { true }
}
impl Event for &Pdu {
@@ -219,9 +220,6 @@ impl Event for &Pdu {
#[inline]
fn into_pdu(self) -> Pdu { self.clone() }
#[inline]
fn is_owned(&self) -> bool { false }
}
/// Prevent derived equality which wouldn't limit itself to event_id
+14
View File
@@ -0,0 +1,14 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// The content hash map for a PDU.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Hashes {
/// The SHA256 content hash.
pub sha256: String,
/// Any other hashes present, if any.
#[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
pub other: HashMap<String, String>,
}
+11
View File
@@ -0,0 +1,11 @@
use ruma::OwnedEventId;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PduMetadata {
/// This event's calculated event ID.
pub event_id: OwnedEventId,
/// Indicates whether the event has been rejected under auth rules.
pub rejected: bool,
}
+127
View File
@@ -0,0 +1,127 @@
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
ServerSignatures, UInt, UserId, events::TimelineEventType,
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::{
Pdu,
pdu::{hashes::Hashes, metadata::PduMetadata},
};
/// Represents a persistent data unit (PDU) version 2, introduced in room
/// version 3, up until room version 11.
///
/// Spec: https://spec.matrix.org/v1.19/rooms/v3/#event-format
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PDU {
/// The event IDs which authorise this event. Between 0 and 10 entries.
pub auth_events: Vec<OwnedEventId>,
/// This event's content.
pub content: Box<RawJsonValue>,
/// The depth of this event.
pub depth: UInt,
/// The content hashes for this event.
pub hashes: Hashes,
/// Timestamp in milliseconds on origin homeserver when this event was
/// created.
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
/// The event IDs which immediately precede this event. Between 0 and 20
/// entries.
pub prev_events: Vec<OwnedEventId>,
/// For redaction events, the ID of the event being redacted.
///
/// Not used in v11.
pub redacts: Option<OwnedEventId>,
/// The room in which this event resides.
pub room_id: OwnedRoomId,
/// The user who sent this event.
pub sender: OwnedUserId,
/// The signatures on this event.
pub signatures: ServerSignatures,
/// The state key for this PDU, if any.
pub state_key: Option<String>,
/// The type of this PDU.
#[serde(rename = "type")]
pub event_type: TimelineEventType,
/// Additional data added by the origin server but not covered by the
/// signatures.
///
/// This should be `None` when receiving from and transmitting over
/// federation.
pub unsigned: Option<Box<RawJsonValue>>,
pub internal_metadata: PduMetadata,
}
impl PDU {
#[must_use]
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
}
impl ruma::state_res::Event for PDU {
type Id = OwnedEventId;
fn event_id(&self) -> &Self::Id { &self.internal_metadata.event_id }
fn room_id(&self) -> Option<&RoomId> { Some(&self.room_id) }
fn sender(&self) -> &UserId { &self.sender }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts }
fn event_type(&self) -> &TimelineEventType { &self.event_type }
fn content(&self) -> &RawJsonValue { self.content.as_ref() }
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
Box::new(self.prev_events.iter())
}
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
Box::new(self.auth_events.iter())
}
fn redacts(&self) -> Option<&Self::Id> { self.redacts.as_ref() }
fn rejected(&self) -> bool { self.internal_metadata().rejected }
}
impl super::Event for PDU {
fn as_pdu(&self) -> &Pdu { todo!() }
fn into_pdu(self) -> Pdu { todo!() }
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.auth_events.iter().map(AsRef::as_ref)
}
fn content(&self) -> &RawJsonValue { self.content.as_ref() }
fn event_id(&self) -> &EventId { &self.internal_metadata.event_id }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts }
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.prev_events.iter().map(AsRef::as_ref)
}
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
fn room_id(&self) -> Option<&RoomId> { Some(&self.room_id) }
fn room_id_or_hash(&self) -> OwnedRoomId { self.room_id.clone() }
fn sender(&self) -> &UserId { &self.sender }
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
fn kind(&self) -> &TimelineEventType { self.event_type() }
fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() }
fn event_type(&self) -> &TimelineEventType { &self.event_type }
}
+142
View File
@@ -0,0 +1,142 @@
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
ServerSignatures, UInt, UserId,
events::{StateEventType, TimelineEventType},
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::{
Pdu,
pdu::{hashes::Hashes, metadata::PduMetadata},
};
/// Represents a persistent data unit (PDU) version 3, introduced in room
/// version 12.
///
/// Spec: https://spec.matrix.org/v1.19/rooms/v12/#event-format
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PDU {
/// The event IDs which authorise this event. Between 0 and 10 entries.
pub auth_events: Vec<OwnedEventId>,
/// This event's content.
pub content: Box<RawJsonValue>,
/// The depth of this event.
pub depth: UInt,
/// The content hashes for this event.
pub hashes: Hashes,
/// Timestamp in milliseconds on origin homeserver when this event was
/// created.
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
/// The event IDs which immediately precede this event. Between 0 and 20
/// entries.
pub prev_events: Vec<OwnedEventId>,
/// The room in which this event resides.
/// May be None if the event is a create event.
pub room_id: Option<OwnedRoomId>,
/// The user who sent this event.
pub sender: OwnedUserId,
/// The signatures on this event.
pub signatures: ServerSignatures,
/// The state key for this PDU, if any.
pub state_key: Option<String>,
/// The type of this PDU.
#[serde(rename = "type")]
pub event_type: TimelineEventType,
/// Additional data added by the origin server but not covered by the
/// signatures.
///
/// This should be `None` when receiving from and transmitting over
/// federation.
pub unsigned: Option<Box<RawJsonValue>>,
pub internal_metadata: PduMetadata,
}
impl PDU {
#[must_use]
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
}
impl ruma::state_res::Event for PDU {
type Id = OwnedEventId;
fn event_id(&self) -> &Self::Id { &self.internal_metadata.event_id }
fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() }
fn sender(&self) -> &UserId { &self.sender }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts }
fn event_type(&self) -> &TimelineEventType { &self.event_type }
fn content(&self) -> &RawJsonValue { self.content.as_ref() }
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
Box::new(self.prev_events.iter())
}
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
Box::new(self.auth_events.iter())
}
fn redacts(&self) -> Option<&Self::Id> { None }
fn rejected(&self) -> bool { self.internal_metadata().rejected }
}
impl super::Event for PDU {
fn as_pdu(&self) -> &Pdu { todo!() }
fn into_pdu(self) -> Pdu { todo!() }
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.auth_events.iter().map(AsRef::as_ref)
}
fn content(&self) -> &RawJsonValue { self.content.as_ref() }
fn event_id(&self) -> &EventId { &self.internal_metadata.event_id }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts }
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.prev_events.iter().map(AsRef::as_ref)
}
fn redacts(&self) -> Option<&EventId> { None }
fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() }
fn room_id_or_hash(&self) -> OwnedRoomId {
if self.event_type == StateEventType::RoomCreate.into()
&& self.state_key().is_some_and(str::is_empty)
{
// Calculate the room ID for a create event.
RoomId::new_v2(
self.internal_metadata()
.event_id
.as_str()
.strip_prefix("$")
.expect("event ID must start with $ sigil"),
)
.expect("must be able to create a room ID from create event reference hash")
} else {
// Otherwise, all other events have a room ID field.
self.room_id.clone().unwrap()
}
}
fn sender(&self) -> &UserId { &self.sender }
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
fn kind(&self) -> &TimelineEventType { self.event_type() }
fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() }
fn event_type(&self) -> &TimelineEventType { &self.event_type }
}