Add some documentation about how the event handler abstraction works

This commit is contained in:
Jonas Platte 2021-09-08 20:29:09 +02:00
parent 3c38b26770
commit 606a57203e
No known key found for this signature in database
GPG key ID: CC154DE0E30B7C67

View file

@ -14,6 +14,21 @@
//! Types and traits related for event handlers. For usage, see //! Types and traits related for event handlers. For usage, see
//! [`Client::register_event_handler`]. //! [`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 std::{borrow::Cow, future::Future, ops::Deref};
@ -55,6 +70,27 @@ pub trait SyncEvent {
/// * Their return type has to be one of: `()`, `Result<(), impl /// * Their return type has to be one of: `()`, `Result<(), impl
/// std::error::Error>` or `anyhow::Result<()>` (requires the `anyhow` Cargo /// std::error::Error>` or `anyhow::Result<()>` (requires the `anyhow` Cargo
/// feature to be enabled) /// 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 { pub trait EventHandler<Ev, Ctx>: Clone + Send + Sync + 'static {
/// The future returned by `handle_event`. /// The future returned by `handle_event`.
#[doc(hidden)] #[doc(hidden)]
@ -161,6 +197,7 @@ struct UnsignedDetails {
redacted_because: Option<serde::de::IgnoredAny>, redacted_because: Option<serde::de::IgnoredAny>,
} }
/// Event handling internals.
impl Client { impl Client {
pub(crate) async fn handle_sync_events<T>( pub(crate) async fn handle_sync_events<T>(
&self, &self,