use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::webhook::events::{StripeEvent, WebhookEvent};
use crate::Error;
type BoxedHandler = Box<
dyn Fn(WebhookEvent) -> Pin<Box<dyn Future<Output = (bool, Result<(), Error>)> + Send>>
+ Send
+ Sync,
>;
pub struct SyncDispatcher {
handlers: Vec<BoxedHandler>,
}
impl SyncDispatcher {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
pub fn on<E, H, Fut>(mut self, handler: H) -> Self
where
E: StripeEvent,
H: Fn(E) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), Error>> + Send + 'static,
{
let handler = Arc::new(handler);
self.handlers.push(Box::new(move |event: WebhookEvent| {
let handler = Arc::clone(&handler);
Box::pin(async move {
match E::from_raw(&event) {
Some(typed) => {
let result = handler(typed).await;
(true, result)
}
None => (false, Ok(())),
}
})
}));
self
}
pub async fn dispatch(&self, event: WebhookEvent) -> Result<(), Error> {
let mut any_matched = false;
for handler in &self.handlers {
let (matched, result) = handler(event.clone()).await;
if matched {
any_matched = true;
result?;
}
}
if !any_matched {
tracing::debug!(
event_type = ?event.type_,
event_id = %event.id,
"unregistered stripe event type — skipping"
);
}
Ok(())
}
}
impl Default for SyncDispatcher {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for SyncDispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyncDispatcher")
.field("handlers_count", &self.handlers.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_send_sync<T: Send + Sync>() {}
#[test]
fn sync_dispatcher_is_send_and_sync() {
_assert_send_sync::<SyncDispatcher>();
_assert_send_sync::<Arc<SyncDispatcher>>();
}
#[test]
fn new_dispatcher_has_no_handlers() {
let d = SyncDispatcher::new();
assert!(d.handlers.is_empty());
}
#[test]
fn default_dispatcher_has_no_handlers() {
let d = SyncDispatcher::default();
assert!(d.handlers.is_empty());
}
}