Compare commits

...

3 Commits

Author SHA1 Message Date
timedout 9aacbf2650 feat: Start on PDU builders
ran out of creative juices so this doesn't work but i'll find a way later
2026-07-12 03:51:28 +01:00
timedout b1a3bcfb88 fix: Skip serialization in null fields
feat: Add fallible conversions from common JSON types
2026-07-12 00:25:26 +01:00
timedout 3c2274b9e7 feat: Create versioned PDU types 2026-07-12 00:07:52 +01:00
9 changed files with 636 additions and 22 deletions
+34
View File
@@ -0,0 +1,34 @@
# Roadmap for PDU versioning
Currently, PDUs are one monolithic `PduEvent` struct which applies a one-size-fits-all approach to PDUs, however as we
support a wide range of room versions, this only just about currently works, and will cause more and more problems as
time progresses. This branch will split the `PduEvent` struct into multiple versioned structs that represent the proper
format for appropriate room versions, and will drop the `Event` trait in favour of Ruma's `ruma::state_res::Event`
trait.
This will require a lot of work, and since I forget everything the moment I look out the window, here be a list (waow):
- [x] Split `PduEvent` into multiple versioned structs, implementing `ruma::state_res::Event` for each of them.
- [ ] Refactor out all references to `PduEvent`.
- [ ] Refactor out all references to `conduwuit_core::event::Event` trait in favour of `ruma::state_res::Event`.
- [ ] Centralise PDU struct creation in a way that allows us to greater control the deserialization process.
- Developers can't be trusted to read documentation so we can't store the PDU metadata (things like event ID and
rejection status) in the PDU struct itself, as this may end up being leaked to consumers. Instead, PDU metadata
should be stored seprately from the PDU struct itself, but needs to be strongly associated with it (i.e. fetched
at the same time, always returned together, etc). This may take a form similar to
`StoredPdu<T: Event> { pdu: T, metadata: PduMetadata }`.
- [ ] Stop storing generated data in the `unsigned` field, but instead calculate it on demand(?).
- Storing generated data in the `unsigned` field results in:
- History visibility cannot be enforced on `prev_content` or `redacted_because`.
- `age` is manually calculated but only sometimes.
- `membership` is not provided at all.
- `prev_content` can leak redacted state event content ([#1103]).
- `prev_content` cannot easily be populated for backfilled events, leaving them contextless.
- It is easy to accidentally leak `transaction_id`.
Once `PduEvent` is gone, we can then do the following lovely things:
- Drop our local state resolution logic and event authorisation logic and instead use Ruma's, which naturally receives
more love and attention. Our current `Event` trait and `PduEvent` implementations are incompatible with what Ruma
needs, and do not store the appropriate information required.
- There's something else but I forgot just trust me
+11 -4
View File
@@ -1,9 +1,11 @@
use std::collections::HashSet;
use assign::assign;
use axum::extract::State;
use conduwuit::{
Err, Pdu, Result, debug_info, debug_warn, err,
matrix::{event::gen_event_id, pdu::PartialPdu},
pdu::{AnyPDU, metadata::PduMetadata, new_pdu},
utils::{self, FutureBoolExt, future::ReadyEqExt},
warn,
};
@@ -216,7 +218,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
room_id: &RoomId,
reason: Option<String>,
mut servers: HashSet<OwnedServerName, S>,
) -> Result<Pdu> {
) -> Result<AnyPDU> {
let mut make_leave_response_and_server =
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
@@ -406,9 +408,14 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.outlier
.add_pdu_outlier(&event_id, &leave_event);
let leave_pdu = Pdu::from_id_val(&event_id, leave_event).map_err(|e| {
err!(BadServerResponse("Invalid leave PDU received during federated leave: {e:?}"))
})?;
let leave_pdu = new_pdu(
room_version_rules.into(),
PduMetadata::new(event_id),
serde_json::to_value(leave_event)?,
)?;
// let leave_pdu = Pdu::from_id_val(&event_id, leave_event).map_err(|e| {
// err!(BadServerResponse("Invalid leave PDU received during federated leave:
// {e:?}")) })?;
Ok(leave_pdu)
}
+1 -3
View File
@@ -143,8 +143,6 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
fn into_pdu(self) -> Pdu;
fn is_owned(&self) -> bool;
//
// Canonical properties
//
@@ -182,12 +180,12 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
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() }
}
+195 -12
View File
@@ -1,19 +1,25 @@
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;
use assign::assign;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, UInt, UserId, events::TimelineEventType,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerSignatures, UInt, UserId, event_id,
events::TimelineEventType, room_version_rules::RoomVersionRules, state_res::Event,
};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::value::RawValue as RawJsonValue;
pub use self::{
@@ -23,10 +29,193 @@
partial::PartialPdu,
raw_id::*,
};
use super::{Event, StateKey};
use crate::Result;
use super::StateKey;
use crate::{
Err, Result,
pdu::{hashes::Hashes, metadata::PduMetadata},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EventFormatVersion {
/// V1 represents the PDU format for rooms v1 and v2.
V1,
/// V2 represents the PDU format for rooms v3-v11.
V2,
/// V3 represents the PDU format for rooms v12+.
V3,
}
impl From<RoomVersionRules> for EventFormatVersion {
fn from(rules: RoomVersionRules) -> Self {
// TODO: this is hacky because ruma doesn't actually define event versions in an
// enum, but does define consts with the appropriate rules.
if rules.event_format.require_event_id {
Self::V1
} else if rules.event_format.require_room_create_room_id {
Self::V2
} else {
Self::V3
}
}
}
/// Trait that allows conversion from a generic PDU to a concrete PDU type.
pub trait ConcretePDU: ruma::state_res::Event<Id = OwnedEventId> {
/// The concrete type for this PDU (`v2::PDU`, `v3::PDU`, etc)
type Concrete;
/// Get a reference to the concrete PDU type.
fn to_concrete(&self) -> &Self::Concrete;
/// Get the value of the concrete PDU type, consuming.
fn into_concrete(self) -> Self::Concrete;
}
pub fn new_pdu<E, C, P, JSON>(
format: EventFormatVersion,
metadata: PduMetadata,
json: serde_json::Value,
) -> Result<E>
where
E: ConcretePDU<Concrete = C>,
JSON: DeserializeOwned,
{
match format {
| EventFormatVersion::V2 =>
Ok(assign!(v2::PDU::try_from(json)?, {internal_metadata: metadata})),
| EventFormatVersion::V3 =>
Ok(assign!(v3::PDU::try_from(json)?, {internal_metadata: metadata})),
| _ => Err!("Unsupported event format: {format:?}"),
}
}
/// A builder type that specifies every field present on any PDU type, allowing
/// it to be used to create any PDU version with the appropriate function.
///
/// See `create_pdu`.
#[derive(Clone, Debug, Default)]
pub struct CommonPDUBuilder {
pub auth_events: Option<Vec<OwnedEventId>>,
pub content: Option<Box<RawJsonValue>>,
pub depth: Option<UInt>,
pub hashes: Option<Hashes>,
pub origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
pub prev_events: Option<Vec<OwnedEventId>>,
pub redacts: Option<OwnedEventId>,
pub room_id: Option<OwnedRoomId>,
pub sender: Option<OwnedUserId>,
pub signatures: Option<ServerSignatures>,
pub state_key: Option<String>,
pub event_type: Option<TimelineEventType>,
pub unsigned: Option<Box<RawJsonValue>>,
pub internal_metadata: Option<PduMetadata>,
}
/// Creates a new PDU based on the given version and builder.
///
/// ## Example
///
/// ```rust
/// use conduwuit_core::pdu::{CommonPDUBuilder, create_pdu}
///
/// let room_version_rules = RoomVersionRules::V10; // This usually comes dynamically.
/// let builder = CommonPDUBuilder {
/// auth_events: Some(vec![event_id!("$auth_event_id").to_owned()]),
/// content: Some(Box::new(serde_json::json!({"key": "value"}))),
/// depth: Some(1),
/// origin_server_ts: Some(MilliSecondsSinceUnixEpoch(123456789)),
/// prev_events: Some(vec![event_id!("$prev_event_id").to_owned()]),
/// room_id: Some(room_id!("!room_id").to_owned()),
/// sender: Some(user_id!("@sender:example.com").to_owned()),
/// event_type: Some(TimelineEventType::RoomMessage),
/// ..Default::default()
/// };
/// let pdu = create_pdu(
/// room_version_rules.into(),
/// builder,
/// ).expect("PDU creation must succeed");
/// ```
pub fn create_pdu<C>(version: EventFormatVersion, builder: CommonPDUBuilder) -> Result<C> {
match version {
| EventFormatVersion::V2 => Ok(create_v2_pdu(builder)),
| EventFormatVersion::V3 => Ok(create_v3_pdu(builder)),
| _ => Err!("Unsupported event format: {version:?}"),
}
}
fn create_v2_pdu<C>(builder: CommonPDUBuilder) -> v2::PDU {
v2::PDU {
auth_events: builder
.auth_events
.expect("auth_events must be provided for a V2 PDU"),
content: builder
.content
.expect("content must be provided for a V2 PDU"),
depth: builder.depth.expect("depth must be provided for a V2 PDU"),
hashes: builder.hashes.unwrap_or_default(), /* We allow default here since we might not
* have hashed yet */
origin_server_ts: builder
.origin_server_ts
.expect("origin_server_ts must be provided for a V2 PDU"),
prev_events: builder
.prev_events
.expect("prev_events must be provided for a V2 PDU"),
redacts: builder.redacts,
room_id: builder
.room_id
.expect("room_id must be provided for a V2 PDU"),
sender: builder
.sender
.expect("sender must be provided for a V2 PDU"),
signatures: builder.signatures.unwrap_or_default(), /* We allow default here since we
* might not have signed yet */
state_key: builder.state_key,
event_type: builder
.event_type
.expect("event_type must be provided for a V2 PDU"),
unsigned: builder.unsigned,
internal_metadata: builder
.internal_metadata
.unwrap_or_else(|| PduMetadata::new(event_id!("$unknown").to_owned())),
}
}
fn create_v3_pdu(builder: CommonPDUBuilder) -> v3::PDU {
v3::PDU {
auth_events: builder
.auth_events
.expect("auth_events must be provided for a V3 PDU"),
content: builder
.content
.expect("content must be provided for a V3 PDU"),
depth: builder.depth.expect("depth must be provided for a V3 PDU"),
hashes: builder.hashes.unwrap_or_default(), /* We allow default here since we might not
* have hashed yet */
origin_server_ts: builder
.origin_server_ts
.expect("origin_server_ts must be provided for a V3 PDU"),
prev_events: builder
.prev_events
.expect("prev_events must be provided for a V3 PDU"),
room_id: builder.room_id,
sender: builder
.sender
.expect("sender must be provided for a V3 PDU"),
signatures: builder.signatures.unwrap_or_default(), /* We allow default here since we
* might not have signed yet */
state_key: builder.state_key,
event_type: builder
.event_type
.expect("event_type must be provided for a V3 PDU"),
unsigned: builder.unsigned,
internal_metadata: builder
.internal_metadata
.unwrap_or_else(|| PduMetadata::new(event_id!("$unknown").to_owned())),
}
}
/// Persistent Data Unit (Event)
#[deprecated(note = "Use `v2::PDU` or `v3::PDU` or `ruma::state_res::Event` instead")]
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Pdu {
pub event_id: OwnedEventId,
@@ -85,7 +274,7 @@ pub fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result<
}
}
impl Event for Pdu {
impl crate::Event for Pdu {
#[inline]
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.auth_events.iter().map(AsRef::as_ref)
@@ -151,12 +340,9 @@ fn as_pdu(&self) -> &Pdu { self }
#[inline]
fn into_pdu(self) -> Pdu { self }
#[inline]
fn is_owned(&self) -> bool { true }
}
impl Event for &Pdu {
impl crate::Event for &Pdu {
#[inline]
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.auth_events.iter().map(AsRef::as_ref)
@@ -219,9 +405,6 @@ fn as_pdu(&self) -> &Pdu { self }
#[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
+15
View File
@@ -0,0 +1,15 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// The content hash map for a PDU.
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct Hashes {
/// The SHA256 content hash.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub sha256: String,
/// Any other hashes present, if any.
#[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
pub other: HashMap<String, String>,
}
+16
View File
@@ -0,0 +1,16 @@
use ruma::{EventId, 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,
}
impl PduMetadata {
/// Creates a new PduMetadata instance with the minimum required fields.
pub fn new(event_id: OwnedEventId) -> Self { Self { event_id, rejected: false } }
}
+174
View File
@@ -0,0 +1,174 @@
use ruma::{
CanonicalJsonObject, 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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub unsigned: Option<Box<RawJsonValue>>,
/// Internal metadata that is important to the PDU but should not be
/// serialized into the final event.
///
/// This field MUST NOT be sent to clients or federation.
pub internal_metadata: PduMetadata,
}
impl PDU {
#[must_use]
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
}
impl crate::pdu::ConcretePDU for PDU {
type Concrete = PDU;
fn to_concrete(&self) -> &Self::Concrete { &self }
fn into_concrete(self) -> Self::Concrete { self }
}
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 crate::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 }
}
impl TryFrom<&RawJsonValue> for PDU {
type Error = crate::Error;
fn try_from(value: &RawJsonValue) -> Result<Self, Self::Error> {
serde_json::from_str(value.get()).map_err(Into::into)
}
}
impl TryFrom<Box<RawJsonValue>> for PDU {
type Error = crate::Error;
fn try_from(value: Box<RawJsonValue>) -> Result<Self, Self::Error> {
Self::try_from(value.as_ref())
}
}
impl TryFrom<serde_json::Value> for PDU {
type Error = crate::Error;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
serde_json::from_value(value).map_err(Into::into)
}
}
impl TryFrom<CanonicalJsonObject> for PDU {
type Error = crate::Error;
fn try_from(value: CanonicalJsonObject) -> Result<Self, Self::Error> {
serde_json::from_value(serde_json::to_value(value)?).map_err(Into::into)
}
}
+187
View File
@@ -0,0 +1,187 @@
use ruma::{
CanonicalJsonObject, 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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub unsigned: Option<Box<RawJsonValue>>,
/// Internal metadata that is important to the PDU but should not be
/// serialized into the final event.
///
/// This field MUST NOT be sent to clients or federation.
pub internal_metadata: PduMetadata,
}
impl PDU {
#[must_use]
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
}
impl crate::pdu::ConcretePDU<PDU> for PDU {
fn to_concrete(&self) -> &PDU { &self }
fn into_concrete(self) -> PDU { self }
}
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 crate::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 }
}
impl TryFrom<&RawJsonValue> for PDU {
type Error = crate::Error;
fn try_from(value: &RawJsonValue) -> Result<Self, Self::Error> {
serde_json::from_str(value.get()).map_err(Into::into)
}
}
impl TryFrom<Box<RawJsonValue>> for PDU {
type Error = crate::Error;
fn try_from(value: Box<RawJsonValue>) -> Result<Self, Self::Error> {
Self::try_from(value.as_ref())
}
}
impl TryFrom<serde_json::Value> for PDU {
type Error = crate::Error;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
serde_json::from_value(value).map_err(Into::into)
}
}
impl TryFrom<CanonicalJsonObject> for PDU {
type Error = crate::Error;
fn try_from(value: CanonicalJsonObject) -> Result<Self, Self::Error> {
serde_json::from_value(serde_json::to_value(value)?).map_err(Into::into)
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
use std::{cmp, collections::HashMap};
use conduwuit::{smallstr::SmallString, trace};
use conduwuit::{pdu::AnyPDU, smallstr::SmallString, trace};
use conduwuit_core::{
Err, Error, Result, err,
matrix::{
@@ -84,7 +84,7 @@ pub async fn create_event(
sender: &UserId,
room_id: Option<&RoomId>,
_mutex_lock: &RoomMutexGuard,
) -> Result<(PduEvent, RoomVersionRules)> {
) -> Result<AnyPDU> {
let PartialPdu {
event_type,
content,
@@ -282,7 +282,7 @@ pub async fn create_hash_and_sign_event(
sender: &UserId,
room_id: Option<&RoomId>,
mutex_lock: &RoomMutexGuard,
) -> Result<(PduEvent, CanonicalJsonObject)> {
) -> Result<AnyPDU> {
if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user")));
}