Skip to main content

laterite_core/
events.rs

1//! Events: facts a module announces, and the listeners that act on them.
2//!
3//! An event is a serde struct with a dot-keyed name. Whoever owns the fact
4//! emits it once it is true; any module listens by registering a [`Listener`]
5//! from its [`register`](crate::module::Module::register). Listeners run in
6//! dependency order, one after another, inside the call that emitted.
7//!
8//! ```
9//! use laterite_core::events::{Event, EventCx, EventError, Listener};
10//! use serde::{Deserialize, Serialize};
11//!
12//! #[derive(Serialize, Deserialize)]
13//! struct Published {
14//!     article_id: i64,
15//! }
16//!
17//! impl Event for Published {
18//!     const NAME: &'static str = "acme.blog.published";
19//! }
20//!
21//! struct Announce;
22//!
23//! #[laterite_core::strata::async_trait]
24//! impl Listener<Published> for Announce {
25//!     async fn handle(&self, _cx: &EventCx<'_>, event: &Published) -> Result<(), EventError> {
26//!         println!("article {} is out", event.article_id);
27//!         Ok(())
28//!     }
29//! }
30//! ```
31
32use std::any::{Any, TypeId};
33use std::collections::HashMap;
34use std::marker::PhantomData;
35use std::sync::Arc;
36
37use async_trait::async_trait;
38use serde::{de::DeserializeOwned, Serialize};
39
40use crate::db::Db;
41use crate::error::{CoreError, CoreResult};
42use crate::migration::DbBackend;
43use crate::module::ModuleId;
44use crate::registry::Registry;
45
46/// What a listener returns when it could not do its work. Any error converts
47/// with `?`.
48pub type EventError = Box<dyn std::error::Error + Send + Sync + 'static>;
49
50/// A fact worth announcing: a serde struct carrying a dot-keyed name.
51///
52/// The name is past tense and starts with the owning module's id
53/// (`acme.blog.published`), so two modules cannot claim one name by accident.
54/// The payload is serde in both directions, so an event can leave the process.
55pub trait Event: Serialize + DeserializeOwned + Send + Sync + 'static {
56    /// The event's name, unique across the application.
57    const NAME: &'static str;
58}
59
60/// Acts on one kind of event.
61///
62/// A listener runs after the fact it hears about, so it cannot refuse it. An
63/// `Err` is logged with the event's name and the listening module, and the
64/// listeners after it still run.
65#[async_trait]
66pub trait Listener<E: Event>: Send + Sync + 'static {
67    async fn handle(&self, cx: &EventCx<'_>, event: &E) -> Result<(), EventError>;
68}
69
70/// What a listener works with besides the event.
71///
72/// Fields are private so more can be offered later without breaking a listener.
73pub struct EventCx<'a> {
74    db: &'a Db,
75    events: &'a Events,
76}
77
78impl EventCx<'_> {
79    /// The pool. `Db` is `Clone`, so a listener that spawns work clones it.
80    pub fn db(&self) -> &Db {
81        self.db
82    }
83
84    pub fn backend(&self) -> DbBackend {
85        self.db.backend
86    }
87
88    /// The bus, for a listener that announces a fact of its own.
89    pub fn events(&self) -> &Events {
90        self.events
91    }
92}
93
94/// A listener with its event type erased, so listeners for every event sit in
95/// one collection.
96#[async_trait]
97trait Erased: Send + Sync {
98    async fn handle(
99        &self,
100        cx: &EventCx<'_>,
101        event: &(dyn Any + Send + Sync),
102    ) -> Result<(), EventError>;
103}
104
105struct Typed<E, L> {
106    listener: L,
107    event: PhantomData<fn(E)>,
108}
109
110#[async_trait]
111impl<E: Event, L: Listener<E>> Erased for Typed<E, L> {
112    async fn handle(
113        &self,
114        cx: &EventCx<'_>,
115        event: &(dyn Any + Send + Sync),
116    ) -> Result<(), EventError> {
117        match event.downcast_ref::<E>() {
118            Some(event) => self.listener.handle(cx, event).await,
119            // Listeners are stored under their event's type, so nothing else
120            // is ever handed to one.
121            None => Ok(()),
122        }
123    }
124}
125
126/// A listener as a module contributes it. [`Registry::listen`] builds one.
127pub struct EventListenerReg {
128    event: TypeId,
129    name: &'static str,
130    type_name: &'static str,
131    listener: Arc<dyn Erased>,
132}
133
134impl EventListenerReg {
135    /// Registers `listener` for the event `E`.
136    pub fn on<E: Event>(listener: impl Listener<E>) -> Self {
137        Self {
138            event: TypeId::of::<E>(),
139            name: E::NAME,
140            type_name: std::any::type_name::<E>(),
141            listener: Arc::new(Typed {
142                listener,
143                event: PhantomData::<fn(E)>,
144            }),
145        }
146    }
147
148    /// The name of the event this listener hears.
149    pub fn event_name(&self) -> &'static str {
150        self.name
151    }
152}
153
154impl Registry {
155    /// Contributes a listener for the event `E`.
156    ///
157    /// ```ignore
158    /// fn register(&self, registry: &mut Registry) {
159    ///     registry.listen::<Published>(Announce);
160    /// }
161    /// ```
162    pub fn listen<E: Event>(&mut self, listener: impl Listener<E>) {
163        self.add(EventListenerReg::on::<E>(listener));
164    }
165}
166
167struct Bound {
168    owner: Option<ModuleId>,
169    listener: Arc<dyn Erased>,
170}
171
172struct Inner {
173    db: Db,
174    by_event: HashMap<TypeId, Vec<Bound>>,
175}
176
177/// The event bus. Cheap to clone: every clone is the same bus.
178#[derive(Clone)]
179pub struct Events {
180    inner: Arc<Inner>,
181}
182
183impl Events {
184    /// A bus nothing listens on. Emitting on it does nothing.
185    pub fn new(db: Db) -> Self {
186        Self {
187            inner: Arc::new(Inner {
188                db,
189                by_event: HashMap::new(),
190            }),
191        }
192    }
193
194    /// Starts a bus, for listeners added by hand: a test, or an application
195    /// assembled without the bootstrap.
196    pub fn builder(db: Db) -> EventsBuilder {
197        EventsBuilder {
198            db,
199            listeners: Vec::new(),
200        }
201    }
202
203    /// Announces `event` to its listeners, in the order they were registered,
204    /// and returns once the last has run.
205    ///
206    /// Emit once the fact is true: after the transaction that made it so has
207    /// committed. A listener that fails is logged and does not stop the others,
208    /// and nothing reaches the caller, since the fact stands either way.
209    pub async fn emit<E: Event>(&self, event: &E) {
210        let Some(listeners) = self.inner.by_event.get(&TypeId::of::<E>()) else {
211            tracing::debug!(event = E::NAME, listeners = 0, "event emitted");
212            return;
213        };
214        tracing::debug!(
215            event = E::NAME,
216            listeners = listeners.len(),
217            "event emitted"
218        );
219        let cx = EventCx {
220            db: &self.inner.db,
221            events: self,
222        };
223        for bound in listeners {
224            if let Err(error) = bound.listener.handle(&cx, event).await {
225                tracing::error!(
226                    event = E::NAME,
227                    module = bound.owner.map(|m| m.as_str()).unwrap_or("application"),
228                    error = %error,
229                    "an event listener failed"
230                );
231            }
232        }
233    }
234
235    /// How many listeners hear the event `E`.
236    pub fn listeners<E: Event>(&self) -> usize {
237        self.inner
238            .by_event
239            .get(&TypeId::of::<E>())
240            .map_or(0, Vec::len)
241    }
242}
243
244/// Collects listeners, then builds the [`Events`] bus.
245pub struct EventsBuilder {
246    db: Db,
247    listeners: Vec<(Option<ModuleId>, EventListenerReg)>,
248}
249
250impl EventsBuilder {
251    /// Adds a listener for the event `E`.
252    pub fn listen<E: Event>(mut self, listener: impl Listener<E>) -> Self {
253        self.listeners
254            .push((None, EventListenerReg::on::<E>(listener)));
255        self
256    }
257
258    /// Adds a listener a module contributed, keeping the module's name for the
259    /// log line a failure writes.
260    pub fn registered(mut self, owner: ModuleId, listener: EventListenerReg) -> Self {
261        self.listeners.push((Some(owner), listener));
262        self
263    }
264
265    /// Builds the bus. Two event types under one name is refused, naming both.
266    pub fn build(self) -> CoreResult<Events> {
267        let mut named: HashMap<&'static str, (TypeId, &'static str)> = HashMap::new();
268        let mut by_event: HashMap<TypeId, Vec<Bound>> = HashMap::new();
269        for (owner, reg) in self.listeners {
270            let (event, type_name) = *named.entry(reg.name).or_insert((reg.event, reg.type_name));
271            if event != reg.event {
272                return Err(CoreError::Config(format!(
273                    "event name '{}' is declared by both {} and {}",
274                    reg.name, type_name, reg.type_name
275                )));
276            }
277            by_event.entry(reg.event).or_default().push(Bound {
278                owner,
279                listener: reg.listener,
280            });
281        }
282        Ok(Events {
283            inner: Arc::new(Inner {
284                db: self.db,
285                by_event,
286            }),
287        })
288    }
289}
290
291#[cfg(all(test, feature = "testing"))]
292mod tests {
293    use super::*;
294    use crate::testing::connect_test;
295    use serde::Deserialize;
296    use std::sync::Mutex;
297
298    #[derive(Serialize, Deserialize)]
299    struct Published {
300        article_id: i64,
301    }
302
303    impl Event for Published {
304        const NAME: &'static str = "acme.blog.published";
305    }
306
307    #[derive(Serialize, Deserialize)]
308    struct Archived {
309        article_id: i64,
310    }
311
312    impl Event for Archived {
313        const NAME: &'static str = "acme.blog.archived";
314    }
315
316    type Heard = Arc<Mutex<Vec<String>>>;
317
318    /// Writes down what it heard, under its own name.
319    struct Note(&'static str, Heard);
320
321    #[async_trait]
322    impl Listener<Published> for Note {
323        async fn handle(&self, _cx: &EventCx<'_>, event: &Published) -> Result<(), EventError> {
324            self.1
325                .lock()
326                .unwrap()
327                .push(format!("{} published {}", self.0, event.article_id));
328            Ok(())
329        }
330    }
331
332    #[async_trait]
333    impl Listener<Archived> for Note {
334        async fn handle(&self, _cx: &EventCx<'_>, event: &Archived) -> Result<(), EventError> {
335            self.1
336                .lock()
337                .unwrap()
338                .push(format!("{} archived {}", self.0, event.article_id));
339            Ok(())
340        }
341    }
342
343    struct Fails;
344
345    #[async_trait]
346    impl Listener<Published> for Fails {
347        async fn handle(&self, _cx: &EventCx<'_>, _event: &Published) -> Result<(), EventError> {
348            Err("the mail server is away".into())
349        }
350    }
351
352    /// Announces an archive for whatever was published, through the bus it was
353    /// handed.
354    struct ArchiveAtOnce;
355
356    #[async_trait]
357    impl Listener<Published> for ArchiveAtOnce {
358        async fn handle(&self, cx: &EventCx<'_>, event: &Published) -> Result<(), EventError> {
359            cx.events()
360                .emit(&Archived {
361                    article_id: event.article_id,
362                })
363                .await;
364            Ok(())
365        }
366    }
367
368    fn heard() -> Heard {
369        Arc::new(Mutex::new(Vec::new()))
370    }
371
372    #[tokio::test]
373    async fn listeners_hear_their_own_event_in_the_order_registered() {
374        let (db, _guard) = connect_test(&[]).await;
375        let log = heard();
376        let events = Events::builder(db)
377            .listen::<Published>(Note("first", log.clone()))
378            .listen::<Archived>(Note("other", log.clone()))
379            .listen::<Published>(Note("second", log.clone()))
380            .build()
381            .unwrap();
382
383        events.emit(&Published { article_id: 7 }).await;
384
385        assert_eq!(
386            *log.lock().unwrap(),
387            ["first published 7", "second published 7"]
388        );
389        assert_eq!(events.listeners::<Published>(), 2);
390        assert_eq!(events.listeners::<Archived>(), 1);
391    }
392
393    #[tokio::test]
394    async fn a_failing_listener_does_not_stop_the_next() {
395        let (db, _guard) = connect_test(&[]).await;
396        let log = heard();
397        let events = Events::builder(db)
398            .listen::<Published>(Fails)
399            .listen::<Published>(Note("after", log.clone()))
400            .build()
401            .unwrap();
402
403        events.emit(&Published { article_id: 3 }).await;
404
405        assert_eq!(*log.lock().unwrap(), ["after published 3"]);
406    }
407
408    #[tokio::test]
409    async fn a_listener_announces_a_fact_of_its_own() {
410        let (db, _guard) = connect_test(&[]).await;
411        let log = heard();
412        let events = Events::builder(db)
413            .listen::<Published>(ArchiveAtOnce)
414            .listen::<Archived>(Note("chained", log.clone()))
415            .build()
416            .unwrap();
417
418        events.emit(&Published { article_id: 9 }).await;
419
420        assert_eq!(*log.lock().unwrap(), ["chained archived 9"]);
421    }
422
423    #[tokio::test]
424    async fn an_event_nobody_hears_is_emitted_quietly() {
425        let (db, _guard) = connect_test(&[]).await;
426        let events = Events::new(db);
427        events.emit(&Published { article_id: 1 }).await;
428        assert_eq!(events.listeners::<Published>(), 0);
429    }
430
431    #[tokio::test]
432    async fn one_name_for_two_events_is_refused() {
433        #[derive(Serialize, Deserialize)]
434        struct Imposter;
435        impl Event for Imposter {
436            const NAME: &'static str = "acme.blog.published";
437        }
438        struct Quiet;
439        #[async_trait]
440        impl Listener<Imposter> for Quiet {
441            async fn handle(&self, _cx: &EventCx<'_>, _event: &Imposter) -> Result<(), EventError> {
442                Ok(())
443            }
444        }
445
446        let (db, _guard) = connect_test(&[]).await;
447        let refused = Events::builder(db)
448            .listen::<Published>(Note("first", heard()))
449            .listen::<Imposter>(Quiet)
450            .build();
451
452        let Err(CoreError::Config(message)) = refused else {
453            panic!("two types under one name must be refused");
454        };
455        assert!(message.contains("acme.blog.published"), "{message}");
456        assert!(message.contains("Imposter"), "{message}");
457    }
458
459    #[tokio::test]
460    async fn a_module_contributes_a_listener_through_the_registry() {
461        let (db, _guard) = connect_test(&[]).await;
462        let log = heard();
463        let mut registry = Registry::new();
464        registry.set_owner(ModuleId::new("acme.blog"));
465        registry.listen::<Published>(Note("module", log.clone()));
466
467        let mut builder = Events::builder(db);
468        for (owner, reg) in registry.take_owned::<EventListenerReg>() {
469            assert_eq!(reg.event_name(), "acme.blog.published");
470            builder = builder.registered(owner, reg);
471        }
472        let events = builder.build().unwrap();
473        events.emit(&Published { article_id: 4 }).await;
474
475        assert_eq!(*log.lock().unwrap(), ["module published 4"]);
476    }
477}