move core::pdu and core::state_res into core::matrix::

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2025-04-04 03:30:13 +00:00
parent 4e5b87d0cd
commit 532dfd004d
91 changed files with 266 additions and 205 deletions

View file

@ -0,0 +1,72 @@
use std::collections::BTreeMap;
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId,
events::{EventContent, MessageLikeEventType, StateEventType, TimelineEventType},
};
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use super::StateKey;
/// Build the start of a PDU in order to add it to the Database.
#[derive(Debug, Deserialize)]
pub struct Builder {
#[serde(rename = "type")]
pub event_type: TimelineEventType,
pub content: Box<RawJsonValue>,
pub unsigned: Option<Unsigned>,
pub state_key: Option<StateKey>,
pub redacts: Option<OwnedEventId>,
/// For timestamped messaging, should only be used for appservices.
/// Will be set to current time if None
pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
}
type Unsigned = BTreeMap<String, serde_json::Value>;
impl Builder {
pub fn state<S, T>(state_key: S, content: &T) -> Self
where
T: EventContent<EventType = StateEventType>,
S: Into<StateKey>,
{
Self {
event_type: content.event_type().into(),
content: to_raw_value(content)
.expect("Builder failed to serialize state event content to RawValue"),
state_key: Some(state_key.into()),
..Self::default()
}
}
pub fn timeline<T>(content: &T) -> Self
where
T: EventContent<EventType = MessageLikeEventType>,
{
Self {
event_type: content.event_type().into(),
content: to_raw_value(content)
.expect("Builder failed to serialize timeline event content to RawValue"),
..Self::default()
}
}
}
impl Default for Builder {
fn default() -> Self {
Self {
event_type: "m.room.message".into(),
content: Box::<RawJsonValue>::default(),
unsigned: None,
state_key: None,
redacts: None,
timestamp: None,
}
}
}

View file

@ -0,0 +1,20 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use crate::{Result, err, implement};
#[must_use]
#[implement(super::Pdu)]
pub fn get_content_as_value(&self) -> JsonValue {
self.get_content()
.expect("pdu content must be a valid JSON value")
}
#[implement(super::Pdu)]
pub fn get_content<T>(&self) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
serde_json::from_str(self.content.get())
.map_err(|e| err!(Database("Failed to deserialize pdu content into type: {e}")))
}

View file

@ -0,0 +1,174 @@
#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::as_conversions)]
use std::{cmp::Ordering, fmt, fmt::Display, str::FromStr};
use ruma::api::Direction;
use crate::{Error, Result, err};
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub enum Count {
Normal(u64),
Backfilled(i64),
}
impl Count {
#[inline]
#[must_use]
pub fn from_unsigned(unsigned: u64) -> Self { Self::from_signed(unsigned as i64) }
#[inline]
#[must_use]
pub fn from_signed(signed: i64) -> Self {
match signed {
| i64::MIN..=0 => Self::Backfilled(signed),
| _ => Self::Normal(signed as u64),
}
}
#[inline]
#[must_use]
pub fn into_unsigned(self) -> u64 {
self.debug_assert_valid();
match self {
| Self::Normal(i) => i,
| Self::Backfilled(i) => i as u64,
}
}
#[inline]
#[must_use]
pub fn into_signed(self) -> i64 {
self.debug_assert_valid();
match self {
| Self::Normal(i) => i as i64,
| Self::Backfilled(i) => i,
}
}
#[inline]
#[must_use]
pub fn into_normal(self) -> Self {
self.debug_assert_valid();
match self {
| Self::Normal(i) => Self::Normal(i),
| Self::Backfilled(_) => Self::Normal(0),
}
}
#[inline]
pub fn checked_inc(self, dir: Direction) -> Result<Self, Error> {
match dir {
| Direction::Forward => self.checked_add(1),
| Direction::Backward => self.checked_sub(1),
}
}
#[inline]
pub fn checked_add(self, add: u64) -> Result<Self, Error> {
Ok(match self {
| Self::Normal(i) => Self::Normal(
i.checked_add(add)
.ok_or_else(|| err!(Arithmetic("Count::Normal overflow")))?,
),
| Self::Backfilled(i) => Self::Backfilled(
i.checked_add(add as i64)
.ok_or_else(|| err!(Arithmetic("Count::Backfilled overflow")))?,
),
})
}
#[inline]
pub fn checked_sub(self, sub: u64) -> Result<Self, Error> {
Ok(match self {
| Self::Normal(i) => Self::Normal(
i.checked_sub(sub)
.ok_or_else(|| err!(Arithmetic("Count::Normal underflow")))?,
),
| Self::Backfilled(i) => Self::Backfilled(
i.checked_sub(sub as i64)
.ok_or_else(|| err!(Arithmetic("Count::Backfilled underflow")))?,
),
})
}
#[inline]
#[must_use]
pub fn saturating_inc(self, dir: Direction) -> Self {
match dir {
| Direction::Forward => self.saturating_add(1),
| Direction::Backward => self.saturating_sub(1),
}
}
#[inline]
#[must_use]
pub fn saturating_add(self, add: u64) -> Self {
match self {
| Self::Normal(i) => Self::Normal(i.saturating_add(add)),
| Self::Backfilled(i) => Self::Backfilled(i.saturating_add(add as i64)),
}
}
#[inline]
#[must_use]
pub fn saturating_sub(self, sub: u64) -> Self {
match self {
| Self::Normal(i) => Self::Normal(i.saturating_sub(sub)),
| Self::Backfilled(i) => Self::Backfilled(i.saturating_sub(sub as i64)),
}
}
#[inline]
#[must_use]
pub const fn min() -> Self { Self::Backfilled(i64::MIN) }
#[inline]
#[must_use]
pub const fn max() -> Self { Self::Normal(i64::MAX as u64) }
#[inline]
pub(crate) fn debug_assert_valid(&self) {
if let Self::Backfilled(i) = self {
debug_assert!(*i <= 0, "Backfilled sequence must be negative");
}
}
}
impl Display for Count {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.debug_assert_valid();
match self {
| Self::Normal(i) => write!(f, "{i}"),
| Self::Backfilled(i) => write!(f, "{i}"),
}
}
}
impl From<i64> for Count {
#[inline]
fn from(signed: i64) -> Self { Self::from_signed(signed) }
}
impl From<u64> for Count {
#[inline]
fn from(unsigned: u64) -> Self { Self::from_unsigned(unsigned) }
}
impl FromStr for Count {
type Err = Error;
fn from_str(token: &str) -> Result<Self, Self::Err> { Ok(Self::from_signed(token.parse()?)) }
}
impl PartialOrd for Count {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Count {
fn cmp(&self, other: &Self) -> Ordering { self.into_signed().cmp(&other.into_signed()) }
}
impl Default for Count {
fn default() -> Self { Self::Normal(0) }
}

View file

@ -0,0 +1,31 @@
use ruma::{CanonicalJsonObject, OwnedEventId, RoomVersionId};
use serde_json::value::RawValue as RawJsonValue;
use crate::{Result, err};
/// Generates a correct eventId for the incoming pdu.
///
/// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap<String,
/// CanonicalJsonValue>`.
pub fn gen_event_id_canonical_json(
pdu: &RawJsonValue,
room_version_id: &RoomVersionId,
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
let value: CanonicalJsonObject = serde_json::from_str(pdu.get())
.map_err(|e| err!(BadServerResponse(warn!("Error parsing incoming event: {e:?}"))))?;
let event_id = gen_event_id(&value, room_version_id)?;
Ok((event_id, value))
}
/// Generates a correct eventId for the incoming pdu.
pub fn gen_event_id(
value: &CanonicalJsonObject,
room_version_id: &RoomVersionId,
) -> Result<OwnedEventId> {
let reference_hash = ruma::signatures::reference_hash(value, room_version_id)?;
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
Ok(event_id)
}

View file

@ -0,0 +1,90 @@
use ruma::api::client::filter::{RoomEventFilter, UrlFilter};
use serde_json::Value;
use crate::{implement, is_equal_to};
#[implement(super::Pdu)]
#[must_use]
pub fn matches(&self, filter: &RoomEventFilter) -> bool {
if !self.matches_sender(filter) {
return false;
}
if !self.matches_room(filter) {
return false;
}
if !self.matches_type(filter) {
return false;
}
if !self.matches_url(filter) {
return false;
}
true
}
#[implement(super::Pdu)]
fn matches_room(&self, filter: &RoomEventFilter) -> bool {
if filter.not_rooms.contains(&self.room_id) {
return false;
}
if let Some(rooms) = filter.rooms.as_ref() {
if !rooms.contains(&self.room_id) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_sender(&self, filter: &RoomEventFilter) -> bool {
if filter.not_senders.contains(&self.sender) {
return false;
}
if let Some(senders) = filter.senders.as_ref() {
if !senders.contains(&self.sender) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_type(&self, filter: &RoomEventFilter) -> bool {
let event_type = &self.kind.to_cow_str();
if filter.not_types.iter().any(is_equal_to!(event_type)) {
return false;
}
if let Some(types) = filter.types.as_ref() {
if !types.iter().any(is_equal_to!(event_type)) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_url(&self, filter: &RoomEventFilter) -> bool {
let Some(url_filter) = filter.url_filter.as_ref() else {
return true;
};
//TODO: might be better to use Ruma's Raw rather than serde here
let url = serde_json::from_str::<Value>(self.content.get())
.expect("parsing content JSON failed")
.get("url")
.is_some_and(Value::is_string);
match url_filter {
| UrlFilter::EventsWithUrl => url,
| UrlFilter::EventsWithoutUrl => !url,
}
}

22
src/core/matrix/pdu/id.rs Normal file
View file

@ -0,0 +1,22 @@
use super::{Count, RawId};
use crate::utils::u64_from_u8x8;
pub type ShortRoomId = ShortId;
pub type ShortEventId = ShortId;
pub type ShortId = u64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Id {
pub shortroomid: ShortRoomId,
pub shorteventid: Count,
}
impl From<RawId> for Id {
#[inline]
fn from(raw: RawId) -> Self {
Self {
shortroomid: u64_from_u8x8(raw.shortroomid()),
shorteventid: Count::from_unsigned(u64_from_u8x8(raw.shorteventid())),
}
}
}

View file

@ -0,0 +1,113 @@
use arrayvec::ArrayVec;
use super::{Count, Id, ShortEventId, ShortId, ShortRoomId};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawId {
Normal(RawIdNormal),
Backfilled(RawIdBackfilled),
}
type RawIdNormal = [u8; RawId::NORMAL_LEN];
type RawIdBackfilled = [u8; RawId::BACKFILLED_LEN];
const INT_LEN: usize = size_of::<ShortId>();
impl RawId {
const BACKFILLED_LEN: usize = size_of::<ShortRoomId>() + INT_LEN + size_of::<ShortEventId>();
const MAX_LEN: usize = Self::BACKFILLED_LEN;
const NORMAL_LEN: usize = size_of::<ShortRoomId>() + size_of::<ShortEventId>();
#[inline]
#[must_use]
pub fn pdu_count(&self) -> Count {
let id: Id = (*self).into();
id.shorteventid
}
#[inline]
#[must_use]
pub fn shortroomid(self) -> [u8; INT_LEN] {
match self {
| Self::Normal(raw) => raw[0..INT_LEN]
.try_into()
.expect("normal raw shortroomid array from slice"),
| Self::Backfilled(raw) => raw[0..INT_LEN]
.try_into()
.expect("backfilled raw shortroomid array from slice"),
}
}
#[inline]
#[must_use]
pub fn shorteventid(self) -> [u8; INT_LEN] {
match self {
| Self::Normal(raw) => raw[INT_LEN..INT_LEN * 2]
.try_into()
.expect("normal raw shorteventid array from slice"),
| Self::Backfilled(raw) => raw[INT_LEN * 2..INT_LEN * 3]
.try_into()
.expect("backfilled raw shorteventid array from slice"),
}
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
match self {
| Self::Normal(raw) => raw,
| Self::Backfilled(raw) => raw,
}
}
}
impl AsRef<[u8]> for RawId {
#[inline]
fn as_ref(&self) -> &[u8] { self.as_bytes() }
}
impl From<&[u8]> for RawId {
#[inline]
fn from(id: &[u8]) -> Self {
match id.len() {
| Self::NORMAL_LEN => Self::Normal(
id[0..Self::NORMAL_LEN]
.try_into()
.expect("normal RawId from [u8]"),
),
| Self::BACKFILLED_LEN => Self::Backfilled(
id[0..Self::BACKFILLED_LEN]
.try_into()
.expect("backfilled RawId from [u8]"),
),
| _ => unimplemented!("unrecognized RawId length"),
}
}
}
impl From<Id> for RawId {
#[inline]
fn from(id: Id) -> Self {
const MAX_LEN: usize = RawId::MAX_LEN;
type RawVec = ArrayVec<u8, MAX_LEN>;
let mut vec = RawVec::new();
vec.extend(id.shortroomid.to_be_bytes());
id.shorteventid.debug_assert_valid();
match id.shorteventid {
| Count::Normal(shorteventid) => {
vec.extend(shorteventid.to_be_bytes());
Self::Normal(vec.as_ref().try_into().expect("RawVec into RawId::Normal"))
},
| Count::Backfilled(shorteventid) => {
vec.extend(0_u64.to_be_bytes());
vec.extend(shorteventid.to_be_bytes());
Self::Backfilled(
vec.as_ref()
.try_into()
.expect("RawVec into RawId::Backfilled"),
)
},
}
}
}

View file

@ -0,0 +1,117 @@
use ruma::{
OwnedEventId, RoomVersionId,
canonical_json::redact_content_in_place,
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
};
use serde::Deserialize;
use serde_json::{
json,
value::{RawValue as RawJsonValue, to_raw_value},
};
use crate::{Error, Result, implement};
#[derive(Deserialize)]
struct ExtractRedactedBecause {
redacted_because: Option<serde::de::IgnoredAny>,
}
#[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: &Self) -> Result {
self.unsigned = None;
let mut content = serde_json::from_str(self.content.get())
.map_err(|_| Error::bad_database("PDU in db has invalid content."))?;
redact_content_in_place(&mut content, room_version_id, self.kind.to_string())
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
self.unsigned = Some(
to_raw_value(&json!({
"redacted_because": serde_json::to_value(reason).expect("to_value(Pdu) always works")
}))
.expect("to string always works"),
);
self.content = to_raw_value(&content).expect("to string always works");
Ok(())
}
#[implement(super::Pdu)]
#[must_use]
pub fn is_redacted(&self) -> bool {
let Some(unsigned) = &self.unsigned else {
return false;
};
let Ok(unsigned) = ExtractRedactedBecause::deserialize(&**unsigned) else {
return false;
};
unsigned.redacted_because.is_some()
}
/// 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.
#[implement(super::Pdu)]
#[must_use]
pub fn copy_redacts(&self) -> (Option<OwnedEventId>, Box<RawJsonValue>) {
if self.kind == TimelineEventType::RoomRedaction {
if let Ok(mut content) =
serde_json::from_str::<RoomRedactionEventContent>(self.content.get())
{
match content.redacts {
| Some(redacts) => {
return (Some(redacts), self.content.clone());
},
| _ => match self.redacts.clone() {
| Some(redacts) => {
content.redacts = Some(redacts);
return (
self.redacts.clone(),
to_raw_value(&content)
.expect("Must be valid, we only added redacts field"),
);
},
| _ => {},
},
}
}
}
(self.redacts.clone(), self.content.clone())
}
#[implement(super::Pdu)]
#[must_use]
pub fn redacts_id(&self, room_version: &RoomVersionId) -> Option<OwnedEventId> {
use RoomVersionId::*;
if self.kind != TimelineEventType::RoomRedaction {
return None;
}
match *room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => self.redacts.clone(),
| _ =>
self.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts,
}
}

View file

@ -0,0 +1,22 @@
use ruma::events::relation::RelationType;
use serde::Deserialize;
use crate::implement;
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelType {
rel_type: RelationType,
}
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelatesToEventId {
#[serde(rename = "m.relates_to")]
relates_to: ExtractRelType,
}
#[implement(super::Pdu)]
#[must_use]
pub fn relation_type_equal(&self, rel_type: &RelationType) -> bool {
self.get_content()
.map(|c: ExtractRelatesToEventId| c.relates_to.rel_type)
.is_ok_and(|r| r == *rel_type)
}

View file

@ -0,0 +1,8 @@
use smallstr::SmallString;
use super::ShortId;
pub type StateKey = SmallString<[u8; INLINE_SIZE]>;
pub type ShortStateKey = ShortId;
const INLINE_SIZE: usize = 48;

View file

@ -0,0 +1,288 @@
use ruma::{
events::{
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent,
room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent,
},
serde::Raw,
};
use serde_json::{json, value::Value as JsonValue};
use crate::implement;
/// This only works for events that are also AnyRoomEvents.
#[must_use]
#[implement(super::Pdu)]
pub fn into_any_event(self) -> Raw<AnyEphemeralRoomEvent> {
serde_json::from_value(self.into_any_event_value()).expect("Raw::from_value always works")
}
/// This only works for events that are also AnyRoomEvents.
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_any_event_value(self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_room_event(self) -> Raw<AnyTimelineEvent> { self.to_room_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_room_event(&self) -> Raw<AnyTimelineEvent> {
serde_json::from_value(self.to_room_event_value()).expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_room_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_message_like_event(self) -> Raw<AnyMessageLikeEvent> { self.to_message_like_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
serde_json::from_value(self.to_message_like_event_value())
.expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_message_like_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_sync_room_event(self) -> Raw<AnySyncTimelineEvent> { self.to_sync_room_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
serde_json::from_value(self.to_sync_room_event_value()).expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_sync_room_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_state_event(self) -> Raw<AnyStateEvent> {
serde_json::from_value(self.into_state_event_value()).expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_state_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
"state_key": self.state_key,
});
if let Some(unsigned) = self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_sync_state_event(self) -> Raw<AnySyncStateEvent> {
serde_json::from_value(self.into_sync_state_event_value())
.expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_sync_state_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"state_key": self.state_key,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_stripped_state_event(self) -> Raw<AnyStrippedStateEvent> {
self.to_stripped_state_event()
}
#[implement(super::Pdu)]
#[must_use]
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
serde_json::from_value(self.to_stripped_state_event_value())
.expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_stripped_state_event_value(&self) -> JsonValue {
json!({
"content": self.content,
"type": self.kind,
"sender": self.sender,
"state_key": self.state_key,
})
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_stripped_spacechild_state_event(self) -> Raw<HierarchySpaceChildEvent> {
serde_json::from_value(self.into_stripped_spacechild_state_event_value())
.expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_stripped_spacechild_state_event_value(self) -> JsonValue {
json!({
"content": self.content,
"type": self.kind,
"sender": self.sender,
"state_key": self.state_key,
"origin_server_ts": self.origin_server_ts,
})
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_member_event(self) -> Raw<StateEvent<RoomMemberEventContent>> {
serde_json::from_value(self.into_member_event_value()).expect("Raw::from_value always works")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_member_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"redacts": self.redacts,
"room_id": self.room_id,
"state_key": self.state_key,
});
if let Some(unsigned) = self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}

View file

@ -0,0 +1,17 @@
use super::Count;
#[test]
fn backfilled_parse() {
let count: Count = "-987654".parse().expect("parse() failed");
let backfilled = matches!(count, Count::Backfilled(_));
assert!(backfilled, "not backfilled variant");
}
#[test]
fn normal_parse() {
let count: Count = "987654".parse().expect("parse() failed");
let backfilled = matches!(count, Count::Backfilled(_));
assert!(!backfilled, "backfilled variant");
}

View file

@ -0,0 +1,116 @@
use std::collections::BTreeMap;
use ruma::MilliSecondsSinceUnixEpoch;
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
use super::Pdu;
use crate::{Result, err, implement, is_true};
#[implement(Pdu)]
pub fn remove_transaction_id(&mut self) -> Result {
use BTreeMap as Map;
let Some(unsigned) = &self.unsigned else {
return Ok(());
};
let mut unsigned: Map<&str, Box<RawJsonValue>> = serde_json::from_str(unsigned.get())
.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
unsigned.remove("transaction_id");
self.unsigned = to_raw_value(&unsigned)
.map(Some)
.expect("unsigned is valid");
Ok(())
}
#[implement(Pdu)]
pub fn add_age(&mut self) -> Result {
use BTreeMap as Map;
let mut unsigned: Map<&str, Box<RawJsonValue>> = self
.unsigned
.as_deref()
.map(RawJsonValue::get)
.map_or_else(|| Ok(Map::new()), serde_json::from_str)
.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
// deliberately allowing for the possibility of negative age
let now: i128 = MilliSecondsSinceUnixEpoch::now().get().into();
let then: i128 = self.origin_server_ts.into();
let this_age = now.saturating_sub(then);
unsigned.insert("age", to_raw_value(&this_age)?);
self.unsigned = Some(to_raw_value(&unsigned)?);
Ok(())
}
#[implement(Pdu)]
pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
use serde_json::Map;
let mut unsigned: Map<String, JsonValue> = self
.unsigned
.as_deref()
.map(RawJsonValue::get)
.map_or_else(|| Ok(Map::new()), serde_json::from_str)
.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
let pdu = pdu
.map(serde_json::to_value)
.transpose()?
.unwrap_or_else(|| JsonValue::Object(Map::new()));
unsigned
.entry("m.relations")
.or_insert(JsonValue::Object(Map::new()))
.as_object_mut()
.map(|object| object.insert(name.to_owned(), pdu));
self.unsigned = Some(to_raw_value(&unsigned)?);
Ok(())
}
#[implement(Pdu)]
pub fn contains_unsigned_property<F>(&self, property: &str, is_type: F) -> bool
where
F: FnOnce(&JsonValue) -> bool,
{
self.get_unsigned_as_value()
.get(property)
.map(is_type)
.is_some_and(is_true!())
}
#[implement(Pdu)]
pub fn get_unsigned_property<T>(&self, property: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
self.get_unsigned_as_value()
.get_mut(property)
.map(JsonValue::take)
.map(serde_json::from_value)
.ok_or(err!(Request(NotFound("property not found in unsigned object"))))?
.map_err(|e| err!(Database("Failed to deserialize unsigned.{property} into type: {e}")))
}
#[implement(Pdu)]
#[must_use]
pub fn get_unsigned_as_value(&self) -> JsonValue {
self.get_unsigned::<JsonValue>().unwrap_or_default()
}
#[implement(Pdu)]
pub fn get_unsigned<T>(&self) -> Result<JsonValue> {
self.unsigned
.as_ref()
.map(|raw| raw.get())
.map(serde_json::from_str)
.ok_or(err!(Request(NotFound("\"unsigned\" property not found in pdu"))))?
.map_err(|e| err!(Database("Failed to deserialize \"unsigned\" into value: {e}")))
}