matrix-rust-sdk/crates/matrix-sdk/src/event_handler.rs

556 lines
18 KiB
Rust

// Copyright 2021 Jonas Platte
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types and traits related for event handlers. For usage, see
//! [`Client::register_event_handler`].
//!
//! ### How it works
//!
//! The `register_event_handler` method registers event handlers of different
//! signatures by actually storing boxed closures that all have the same
//! signature of `async (EventHandlerData) -> ()` where `EventHandlerData` is a
//! private type that contains all of the data an event handler *might* need.
//!
//! The stored closure takes care of deserializing the event which the
//! `EventHandlerData` contains as a (borrowed) [`serde_json::value::RawValue`],
//! extracing the context arguments from other fields of `EventHandlerData` and
//! calling / `.await`ing the event handler if the previous steps succeeded.
//! It also logs any errors from the above chain of function calls.
//!
//! For more details, see the [`EventHandler`] trait.
use std::{borrow::Cow, future::Future, ops::Deref};
use matrix_sdk_base::deserialized_responses::{EncryptionInfo, SyncRoomEvent};
use ruma::{events::AnySyncStateEvent, serde::Raw};
use serde::Deserialize;
use serde_json::value::RawValue as RawJsonValue;
use crate::{room, Client};
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum EventKind {
GlobalAccountData,
RoomAccountData,
EphemeralRoomData,
Message { redacted: bool },
State { redacted: bool },
StrippedState,
InitialState,
ToDevice,
Presence,
}
/// A statically-known event kind/type that can be retrieved from an event sync.
pub trait SyncEvent {
#[doc(hidden)]
const ID: (EventKind, &'static str);
}
/// Interface for event handlers.
///
/// This trait is an abstraction for a certain kind of functions / closures,
/// specifically:
///
/// * They must have at least one argument, which is the event itself, a type
/// that implements [`SyncEvent`]. Any additional arguments need to implement
/// the [`EventHandlerContext`] trait.
/// * Their return type has to be one of: `()`, `Result<(), impl
/// std::error::Error>` or `anyhow::Result<()>` (requires the `anyhow` Cargo
/// feature to be enabled)
///
/// ### How it works
///
/// This trait is basically a very constrained version of `Fn`: It requires at
/// least one argument, which is represented as its own generic parameter `Ev`
/// with the remaining parameter types being represented by the second generic
/// parameter `Ctx`; they have to be stuffed into one generic parameter as a
/// tuple because Rust doesn't have variadic generics.
///
/// `Ev` and `Ctx` are generic parameters rather than associated types because
/// the argument list is a generic parameter for the `Fn` traits too, so a
/// single type could implement `Fn` multiple times with different argument
/// lists¹. Luckily, when calling [`Client::register_event_handler`] with a
/// closure argument the trait solver takes into account that only a single one
/// of the implementations applies (even though this could theoretically change
/// through a dependency upgrade) and uses that rather than raising an ambiguity
/// error. This is the same trick used by web frameworks like actix-web and
/// axum.
///
/// ¹ the only thing stopping such types from existing in stable Rust is that
/// all manual implementations of the `Fn` traits require a Nightly feature
pub trait EventHandler<Ev, Ctx>: Clone + Send + Sync + 'static {
/// The future returned by `handle_event`.
#[doc(hidden)]
type Future: Future + Send + 'static;
/// The event type being handled, for example a message event of type
/// `m.room.message`.
#[doc(hidden)]
const ID: (EventKind, &'static str);
/// Create a future for handling the given event.
///
/// `data` provides additional data about the event, for example the room it
/// appeared in.
///
/// Returns `None` if one of the context extractors failed.
#[doc(hidden)]
fn handle_event(&self, ev: Ev, data: EventHandlerData<'_>) -> Option<Self::Future>;
}
#[doc(hidden)]
#[derive(Debug)]
pub struct EventHandlerData<'a> {
pub client: Client,
pub room: Option<room::Room>,
pub raw: &'a RawJsonValue,
pub encryption_info: Option<&'a EncryptionInfo>,
}
/// Context for an event handler.
///
/// This trait defines the set of types that may be used as additional arguments
/// in event handler functions after the event itself.
pub trait EventHandlerContext: Sized {
#[doc(hidden)]
fn from_data(_: &EventHandlerData<'_>) -> Option<Self>;
}
impl EventHandlerContext for Client {
fn from_data(data: &EventHandlerData<'_>) -> Option<Self> {
Some(data.client.clone())
}
}
/// This event handler context argument is only applicable to room-specific
/// events.
///
/// Trying to use it in the event handler for another event, for example a
/// global account data or presence event, will result in the event handler
/// being skipped and an error getting logged.
impl EventHandlerContext for room::Room {
fn from_data(data: &EventHandlerData<'_>) -> Option<Self> {
data.room.clone()
}
}
/// The raw JSON form of an event.
///
/// Used as a context argument for event handlers (see
/// [`Client::register_event_handler`]).
// FIXME: This could be made to not own the raw JSON value with some changes to
// the traits above, but only with GATs.
#[derive(Clone, Debug)]
pub struct RawEvent(pub Box<RawJsonValue>);
impl Deref for RawEvent {
type Target = RawJsonValue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EventHandlerContext for RawEvent {
fn from_data(data: &EventHandlerData<'_>) -> Option<Self> {
Some(Self(data.raw.to_owned()))
}
}
impl EventHandlerContext for Option<EncryptionInfo> {
fn from_data(data: &EventHandlerData<'_>) -> Option<Self> {
Some(data.encryption_info.cloned())
}
}
/// Return types supported for event handlers implement this trait.
///
/// It is not meant to be implemented outside of matrix-sdk.
pub trait EventHandlerResult: Sized {
#[doc(hidden)]
fn print_error(&self, event_type: &str);
}
impl EventHandlerResult for () {
fn print_error(&self, _event_type: &str) {}
}
impl<E: std::error::Error> EventHandlerResult for Result<(), E> {
fn print_error(&self, event_type: &str) {
if let Err(e) = self {
tracing::error!("Event handler for `{}` failed: {}", event_type, e);
}
}
}
#[cfg(feature = "anyhow")]
impl EventHandlerResult for anyhow::Result<()> {
fn print_error(&self, event_type: &str) {
if let Err(e) = self {
tracing::error!("Event handler for `{}` failed: {:?}", event_type, e);
}
}
}
#[derive(Deserialize)]
struct UnsignedDetails {
redacted_because: Option<serde::de::IgnoredAny>,
}
/// Event handling internals.
impl Client {
pub(crate) async fn handle_sync_events<T>(
&self,
kind: EventKind,
room: &Option<room::Room>,
events: &[Raw<T>],
) -> serde_json::Result<()> {
#[derive(Deserialize)]
struct ExtractType<'a> {
#[serde(borrow, rename = "type")]
event_type: Cow<'a, str>,
}
self.handle_sync_events_wrapped_with(
room,
events,
|ev| (ev, None),
|raw| Ok((kind, raw.deserialize_as::<ExtractType>()?.event_type)),
)
.await
}
pub(crate) async fn handle_sync_state_events(
&self,
room: &Option<room::Room>,
state_events: &[Raw<AnySyncStateEvent>],
) -> serde_json::Result<()> {
#[derive(Deserialize)]
struct StateEventDetails<'a> {
#[serde(borrow, rename = "type")]
event_type: Cow<'a, str>,
unsigned: Option<UnsignedDetails>,
}
self.handle_sync_events_wrapped_with(
room,
state_events,
|ev| (ev, None),
|raw| {
let StateEventDetails { event_type, unsigned } = raw.deserialize_as()?;
let redacted = unsigned.and_then(|u| u.redacted_because).is_some();
Ok((EventKind::State { redacted }, event_type))
},
)
.await
}
pub(crate) async fn handle_sync_timeline_events(
&self,
room: &Option<room::Room>,
timeline_events: &[SyncRoomEvent],
) -> serde_json::Result<()> {
#[derive(Deserialize)]
struct TimelineEventDetails<'a> {
#[serde(borrow, rename = "type")]
event_type: Cow<'a, str>,
state_key: Option<serde::de::IgnoredAny>,
unsigned: Option<UnsignedDetails>,
}
self.handle_sync_events_wrapped_with(
room,
timeline_events,
|e| (&e.event, e.encryption_info.as_ref()),
|raw| {
let TimelineEventDetails { event_type, state_key, unsigned } =
raw.deserialize_as()?;
let redacted = unsigned.and_then(|u| u.redacted_because).is_some();
let kind = match state_key {
Some(_) => EventKind::State { redacted },
None => EventKind::Message { redacted },
};
Ok((kind, event_type))
},
)
.await
}
async fn handle_sync_events_wrapped_with<'a, T: 'a, U: 'a>(
&self,
room: &Option<room::Room>,
list: &'a [U],
get_event_details: impl Fn(&'a U) -> (&'a Raw<T>, Option<&'a EncryptionInfo>),
get_id: impl Fn(&Raw<T>) -> serde_json::Result<(EventKind, Cow<'_, str>)>,
) -> serde_json::Result<()> {
for x in list {
let (raw_event, encryption_info) = get_event_details(x);
let (ev_kind, ev_type) = get_id(raw_event)?;
let event_handler_id = (ev_kind, &*ev_type);
if let Some(handlers) = self.event_handlers.read().await.get(&event_handler_id) {
for handler in &*handlers {
let data = EventHandlerData {
client: self.clone(),
room: room.clone(),
raw: raw_event.json(),
encryption_info,
};
matrix_sdk_common::executor::spawn((handler)(data));
}
}
}
Ok(())
}
}
macro_rules! impl_event_handler {
($($ty:ident),* $(,)?) => {
impl<Ev, Fun, Fut, $($ty),*> EventHandler<Ev, ($($ty,)*)> for Fun
where
Ev: SyncEvent,
Fun: Fn(Ev, $($ty),*) -> Fut + Clone + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: EventHandlerResult,
$($ty: EventHandlerContext),*
{
type Future = Fut;
const ID: (EventKind, &'static str) = Ev::ID;
fn handle_event(&self, ev: Ev, _d: EventHandlerData<'_>) -> Option<Self::Future> {
Some((self)(ev, $($ty::from_data(&_d)?),*))
}
}
};
}
impl_event_handler!();
impl_event_handler!(A);
impl_event_handler!(A, B);
impl_event_handler!(A, B, C);
impl_event_handler!(A, B, C, D);
impl_event_handler!(A, B, C, D, E);
impl_event_handler!(A, B, C, D, E, F);
impl_event_handler!(A, B, C, D, E, F, G);
impl_event_handler!(A, B, C, D, E, F, G, H);
mod static_events {
use ruma::events::{
self,
presence::{PresenceEvent, PresenceEventContent},
StaticEventContent,
};
use super::{EventKind, SyncEvent};
impl<C> SyncEvent for events::GlobalAccountDataEvent<C>
where
C: StaticEventContent + events::GlobalAccountDataEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::GlobalAccountData, C::TYPE);
}
impl<C> SyncEvent for events::RoomAccountDataEvent<C>
where
C: StaticEventContent + events::RoomAccountDataEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::RoomAccountData, C::TYPE);
}
impl<C> SyncEvent for events::SyncEphemeralRoomEvent<C>
where
C: StaticEventContent + events::EphemeralRoomEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::EphemeralRoomData, C::TYPE);
}
impl<C> SyncEvent for events::SyncMessageEvent<C>
where
C: StaticEventContent + events::MessageEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::Message { redacted: false }, C::TYPE);
}
impl<C> SyncEvent for events::SyncStateEvent<C>
where
C: StaticEventContent + events::StateEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::State { redacted: false }, C::TYPE);
}
impl<C> SyncEvent for events::StrippedStateEvent<C>
where
C: StaticEventContent + events::StateEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::StrippedState, C::TYPE);
}
impl<C> SyncEvent for events::InitialStateEvent<C>
where
C: StaticEventContent + events::StateEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::InitialState, C::TYPE);
}
impl<C> SyncEvent for events::ToDeviceEvent<C>
where
C: StaticEventContent + events::ToDeviceEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::ToDevice, C::TYPE);
}
impl SyncEvent for PresenceEvent {
const ID: (EventKind, &'static str) = (EventKind::Presence, PresenceEventContent::TYPE);
}
impl<C> SyncEvent for events::RedactedSyncMessageEvent<C>
where
C: StaticEventContent + events::RedactedMessageEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::Message { redacted: true }, C::TYPE);
}
impl<C> SyncEvent for events::RedactedSyncStateEvent<C>
where
C: StaticEventContent + events::RedactedStateEventContent,
{
const ID: (EventKind, &'static str) = (EventKind::State { redacted: true }, C::TYPE);
}
impl SyncEvent for events::room::redaction::SyncRedactionEvent {
const ID: (EventKind, &'static str) = (
EventKind::Message { redacted: false },
events::room::redaction::RedactionEventContent::TYPE,
);
}
}
#[cfg(test)]
mod test {
use std::{future, sync::Arc};
use matrix_sdk_test::{EventBuilder, EventsJson};
use ruma::{
events::{room::member::MemberEventContent, StrippedStateEvent, SyncStateEvent},
room_id,
};
use serde_json::json;
use crate::{room, Client};
#[tokio::test]
async fn event_handler() -> crate::Result<()> {
use std::sync::atomic::{AtomicU8, Ordering::SeqCst};
let client = crate::client::test::logged_in_client().await;
let member_count = Arc::new(AtomicU8::new(0));
let typing_count = Arc::new(AtomicU8::new(0));
let power_levels_count = Arc::new(AtomicU8::new(0));
let invited_member_count = Arc::new(AtomicU8::new(0));
client
.register_event_handler({
let member_count = member_count.clone();
move |_ev: SyncStateEvent<MemberEventContent>, _room: room::Room| {
member_count.fetch_add(1, SeqCst);
future::ready(())
}
})
.await
.register_event_handler({
let typing_count = typing_count.clone();
move |_ev: SyncStateEvent<MemberEventContent>| {
typing_count.fetch_add(1, SeqCst);
future::ready(())
}
})
.await
.register_event_handler({
let power_levels_count = power_levels_count.clone();
move |_ev: SyncStateEvent<MemberEventContent>,
_client: Client,
_room: room::Room| {
power_levels_count.fetch_add(1, SeqCst);
future::ready(())
}
})
.await
.register_event_handler({
let invited_member_count = invited_member_count.clone();
move |_ev: StrippedStateEvent<MemberEventContent>| {
invited_member_count.fetch_add(1, SeqCst);
future::ready(())
}
})
.await;
let response = EventBuilder::default()
.add_room_event(EventsJson::Member)
.add_ephemeral(EventsJson::Typing)
.add_state_event(EventsJson::PowerLevels)
.add_custom_invited_event(
&room_id!("!test_invited:example.org"),
json!({
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice",
"membership": "invite",
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653u64,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234,
"invite_room_state": [
{
"content": {
"name": "Example Room"
},
"sender": "@bob:example.org",
"state_key": "",
"type": "m.room.name"
},
{
"content": {
"join_rule": "invite"
},
"sender": "@bob:example.org",
"state_key": "",
"type": "m.room.join_rules"
}
]
}
}),
)
.build_sync_response();
client.process_sync(response).await?;
assert_eq!(member_count.load(SeqCst), 1);
assert_eq!(typing_count.load(SeqCst), 1);
assert_eq!(power_levels_count.load(SeqCst), 1);
assert_eq!(invited_member_count.load(SeqCst), 1);
Ok(())
}
}