use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, UserId,
events::{
AnyPossiblyRedactedStateEventContent, AnyStrippedStateEvent, AnySyncStateEvent,
AnySyncTimelineEvent, PossiblyRedactedStateEventContent, RedactContent,
RedactedStateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
StrippedStateEvent, SyncStateEvent,
room::{
create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
member::PossiblyRedactedRoomMemberEventContent,
},
},
room_version_rules::RedactionRules,
serde::Raw,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use tracing::{error, warn};
use crate::room::RoomCreateWithCreatorEventContent;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(
bound(serialize = "C: Serialize + Clone"),
from = "MinimalStateEventSerdeHelper<C>",
into = "MinimalStateEventSerdeHelper<C>"
)]
pub struct MinimalStateEvent<C: PossiblyRedactedStateEventContent + RedactContent> {
pub content: C,
pub event_id: Option<OwnedEventId>,
}
impl<C> MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
C::Redacted: Into<C>,
{
pub fn redact(&mut self, rules: &RedactionRules)
where
C: Clone,
{
self.content = self.content.clone().redact(rules).into()
}
}
#[derive(Serialize, Deserialize)]
enum MinimalStateEventSerdeHelper<C> {
Original(MinimalStateEventSerdeHelperInner<C>),
Redacted(MinimalStateEventSerdeHelperInner<C>),
PossiblyRedacted(MinimalStateEventSerdeHelperInner<C>),
}
impl<C> From<MinimalStateEventSerdeHelper<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEventSerdeHelper<C>) -> Self {
match value {
MinimalStateEventSerdeHelper::Original(event) => event,
MinimalStateEventSerdeHelper::Redacted(event) => event,
MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event,
}
.into()
}
}
impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelper<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEvent<C>) -> Self {
Self::PossiblyRedacted(value.into())
}
}
#[derive(Serialize, Deserialize)]
struct MinimalStateEventSerdeHelperInner<C> {
content: C,
event_id: Option<OwnedEventId>,
}
impl<C> From<MinimalStateEventSerdeHelperInner<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEventSerdeHelperInner<C>) -> Self {
let MinimalStateEventSerdeHelperInner { content, event_id } = value;
Self { content, event_id }
}
}
impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelperInner<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEvent<C>) -> Self {
let MinimalStateEvent { content, event_id } = value;
Self { content, event_id }
}
}
pub type MinimalRoomMemberEvent = MinimalStateEvent<PossiblyRedactedRoomMemberEventContent>;
impl<C1, C2> From<SyncStateEvent<C1>> for MinimalStateEvent<C2>
where
C1: StaticStateEventContent + RedactContent + Into<C2>,
C1::Redacted: RedactedStateEventContent + Into<C2>,
C2: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(ev: SyncStateEvent<C1>) -> Self {
match ev {
SyncStateEvent::Original(ev) => {
Self { content: ev.content.into(), event_id: Some(ev.event_id) }
}
SyncStateEvent::Redacted(ev) => {
Self { content: ev.content.into(), event_id: Some(ev.event_id) }
}
}
}
}
impl<C1, C2> From<&SyncStateEvent<C1>> for MinimalStateEvent<C2>
where
C1: Clone + StaticStateEventContent + RedactContent + Into<C2>,
C1::Redacted: Clone + RedactedStateEventContent + Into<C2>,
C2: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(ev: &SyncStateEvent<C1>) -> Self {
match ev {
SyncStateEvent::Original(ev) => {
Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
}
SyncStateEvent::Redacted(ev) => {
Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
}
}
}
}
impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
fn from(ev: &SyncRoomCreateEvent) -> Self {
match ev {
SyncStateEvent::Original(ev) => Self {
content: RoomCreateWithCreatorEventContent::from_event_content(
ev.content.clone(),
ev.sender.clone(),
),
event_id: Some(ev.event_id.clone()),
},
SyncStateEvent::Redacted(ev) => Self {
content: RoomCreateWithCreatorEventContent::from_event_content(
ev.content.clone(),
ev.sender.clone(),
),
event_id: Some(ev.event_id.clone()),
},
}
}
}
impl<C> From<StrippedStateEvent<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(event: StrippedStateEvent<C>) -> Self {
Self { content: event.content, event_id: None }
}
}
impl<C> From<&StrippedStateEvent<C>> for MinimalStateEvent<C>
where
C: Clone + PossiblyRedactedStateEventContent + RedactContent,
{
fn from(event: &StrippedStateEvent<C>) -> Self {
Self { content: event.content.clone(), event_id: None }
}
}
impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
fn from(event: &StrippedRoomCreateEvent) -> Self {
let content = RoomCreateWithCreatorEventContent::from_event_content(
event.content.clone(),
event.sender.clone(),
);
Self { content, event_id: None }
}
}
#[derive(Debug, Clone)]
pub struct RawStateEventWithKeys<T: AnyStateEventEnum> {
pub raw: Raw<T>,
pub event_type: StateEventType,
pub state_key: String,
cached_event: Option<Result<T, ()>>,
}
impl<T: AnyStateEventEnum> RawStateEventWithKeys<T> {
pub fn try_from_raw_state_event(raw: Raw<T>) -> Option<Self> {
let StateEventWithKeysDeHelper { event_type, state_key } =
match raw.deserialize_as_unchecked() {
Ok(fields) => fields,
Err(error) => {
warn!(?error, "Couldn't deserialize type and state key of state event");
return None;
}
};
let Some(state_key) = state_key else {
warn!(
?event_type,
"Couldn't deserialize type and state key of state event: missing state key"
);
return None;
};
Some(Self { raw, event_type, state_key, cached_event: None })
}
pub fn deserialize(&mut self) -> Option<&T> {
self.cached_event
.get_or_insert_with(|| {
self.raw.deserialize().map_err(|error| {
warn!(?error, "Couldn't deserialize state event");
})
})
.as_ref()
.ok()
}
pub fn deserialize_as_minimal_event<F, C>(
&mut self,
as_variant_fn: F,
) -> Option<MinimalStateEvent<C>>
where
F: FnOnce(AnyPossiblyRedactedStateEventContent) -> Option<C>,
C: StaticEventContent + PossiblyRedactedStateEventContent + RedactContent,
{
let any_event = self.deserialize()?;
let any_content = any_event.get_content();
let Some(content) = as_variant_fn(any_content) else {
error!(
expected_event_type = ?C::TYPE,
actual_event_type = ?any_event.get_event_type().to_string(),
"Couldn't deserialize state event content: unexpected type",
);
return None;
};
Some(MinimalStateEvent {
content,
event_id: any_event.get_event_id().map(ToOwned::to_owned),
})
}
pub(crate) fn set_cached_event(&mut self, event: T) {
self.cached_event = Some(Ok(event));
}
}
impl RawStateEventWithKeys<AnySyncStateEvent> {
pub fn try_from_raw_timeline_event(raw: &Raw<AnySyncTimelineEvent>) -> Option<Self> {
let StateEventWithKeysDeHelper { event_type, state_key } = match raw
.deserialize_as_unchecked()
{
Ok(fields) => fields,
Err(error) => {
warn!(?error, "Couldn't deserialize type and optional state key of timeline event");
return None;
}
};
Some(Self {
event_type,
state_key: state_key?,
raw: raw.clone().cast_unchecked(),
cached_event: None,
})
}
pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
where
F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
C: StaticEventContent + StaticStateEventContent + RedactContent,
C::Redacted: RedactedStateEventContent,
{
let any_event = self.deserialize()?;
let event = as_variant_fn(any_event);
if event.is_none() {
error!(
expected_event_type = ?C::TYPE,
actual_event_type = ?any_event.event_type().to_string(),
"Couldn't deserialize state event: unexpected type",
);
}
event
}
}
impl RawStateEventWithKeys<AnyStrippedStateEvent> {
pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&StrippedStateEvent<C>>
where
F: FnOnce(&AnyStrippedStateEvent) -> Option<&StrippedStateEvent<C>>,
C: StaticEventContent + PossiblyRedactedStateEventContent,
{
let any_event = self.deserialize()?;
let event = as_variant_fn(any_event);
if event.is_none() {
error!(
expected_event_type = ?C::TYPE,
actual_event_type = ?any_event.event_type().to_string(),
"Couldn't deserialize stripped state event: unexpected type",
);
}
event
}
}
#[derive(Deserialize)]
struct StateEventWithKeysDeHelper {
#[serde(rename = "type")]
event_type: StateEventType,
state_key: Option<String>,
}
pub trait AnyStateEventEnum: DeserializeOwned {
fn get_event_type(&self) -> StateEventType;
fn get_content(&self) -> AnyPossiblyRedactedStateEventContent;
fn get_event_id(&self) -> Option<&EventId>;
fn get_sender(&self) -> &UserId;
fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
}
impl AnyStateEventEnum for AnySyncStateEvent {
fn get_event_type(&self) -> StateEventType {
self.event_type()
}
fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
self.content()
}
fn get_event_id(&self) -> Option<&EventId> {
Some(self.event_id())
}
fn get_sender(&self) -> &UserId {
self.sender()
}
fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
Some(self.origin_server_ts())
}
}
impl AnyStateEventEnum for AnyStrippedStateEvent {
fn get_event_type(&self) -> StateEventType {
self.event_type()
}
fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
self.content()
}
fn get_event_id(&self) -> Option<&EventId> {
None
}
fn get_sender(&self) -> &UserId {
self.sender()
}
fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
None
}
}
#[cfg(test)]
mod tests {
use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
use super::MinimalStateEvent;
#[test]
fn test_backward_compatible_deserialize_minimal_state_event() {
let event_id = event_id!("$event");
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name.as_deref(), Some("My Room"));
assert_eq!(event.event_id.as_deref(), Some(event_id));
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name, None);
assert_eq!(event.event_id.as_deref(), Some(event_id));
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name.as_deref(), Some("My Room"));
assert_eq!(event.event_id.as_deref(), Some(event_id));
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name, None);
assert_eq!(event.event_id.as_deref(), Some(event_id));
}
}