Skip to main content

ddd_toolkit_core/port/
event.rs

1use std::fmt;
2
3use crate::{domain::DomainEvent, port::PortError};
4
5/// A `dispatch` failure that reports which events were not delivered, so a
6/// caller can retry or persist just the remainder instead of losing track
7/// of the whole batch.
8///
9/// Implementors that dispatch events one at a time should populate
10/// `undelivered` with every event from the input that was not confirmed
11/// delivered (typically the failing event and everything after it).
12/// Implementors that dispatch atomically (all-or-nothing) should populate
13/// it with the entire input batch on failure.
14#[derive(Debug)]
15pub struct DispatchError<E> {
16    /// Events from the input batch that were not confirmed delivered.
17    pub undelivered: Vec<E>,
18    /// The underlying failure.
19    pub source: PortError,
20}
21
22impl<E> DispatchError<E> {
23    /// Creates a new `DispatchError`.
24    pub fn new(undelivered: Vec<E>, source: PortError) -> Self {
25        Self {
26            undelivered,
27            source,
28        }
29    }
30}
31
32impl<E: fmt::Debug> fmt::Display for DispatchError<E> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(
35            f,
36            "dispatch failed with {} undelivered event(s): {}",
37            self.undelivered.len(),
38            self.source
39        )
40    }
41}
42
43impl<E: fmt::Debug> std::error::Error for DispatchError<E> {
44    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
45        Some(&self.source)
46    }
47}
48
49/// Publishes recorded [`DomainEvent`]s, e.g. to a message broker.
50#[trait_variant::make(Send)]
51pub trait EventDispatcher<E: DomainEvent> {
52    /// Dispatches `events`. See [`DispatchError`] for how partial failure
53    /// is reported.
54    async fn dispatch(&self, events: Vec<E>) -> Result<(), DispatchError<E>>;
55}
56
57#[cfg(test)]
58mod test {
59    use std::sync::Mutex;
60
61    use crate::{port::PortErrorKind, testing::block_on};
62
63    use super::*;
64
65    #[derive(Debug, Clone, PartialEq)]
66    struct FooEvent {
67        name: String,
68    }
69
70    impl FooEvent {
71        fn new(name: &str) -> Self {
72            Self {
73                name: name.to_string(),
74            }
75        }
76    }
77
78    impl DomainEvent for FooEvent {}
79
80    struct RecordingDispatcher {
81        dispatched: Mutex<Vec<FooEvent>>,
82    }
83
84    impl RecordingDispatcher {
85        fn new() -> Self {
86            Self {
87                dispatched: Mutex::new(Vec::new()),
88            }
89        }
90    }
91
92    impl EventDispatcher<FooEvent> for RecordingDispatcher {
93        async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
94            self.dispatched.lock().unwrap().extend(events);
95            Ok(())
96        }
97    }
98
99    struct FailingDispatcher;
100
101    impl EventDispatcher<FooEvent> for FailingDispatcher {
102        async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
103            Err(DispatchError::new(
104                events,
105                PortError::unavailable("dispatch failed"),
106            ))
107        }
108    }
109
110    // Dispatches events one at a time, failing on (and after) the event
111    // whose name is "boom" - a stand-in for a partial-failure dispatcher.
112    struct PartiallyFailingDispatcher;
113
114    impl EventDispatcher<FooEvent> for PartiallyFailingDispatcher {
115        async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
116            let boom_at = events.iter().position(|e| e.name == "boom");
117            match boom_at {
118                Some(index) => Err(DispatchError::new(
119                    events[index..].to_vec(),
120                    PortError::unavailable("dispatch failed"),
121                )),
122                None => Ok(()),
123            }
124        }
125    }
126
127    #[test]
128    fn dispatch_records_all_events_in_order() {
129        let dispatcher = RecordingDispatcher::new();
130        let events = vec![FooEvent::new("created"), FooEvent::new("renamed")];
131
132        block_on(dispatcher.dispatch(events)).expect("dispatch should succeed");
133
134        assert_eq!(
135            *dispatcher.dispatched.lock().unwrap(),
136            vec![FooEvent::new("created"), FooEvent::new("renamed")]
137        );
138    }
139
140    #[test]
141    fn dispatch_called_twice_delivers_both_batches() {
142        let dispatcher = RecordingDispatcher::new();
143
144        block_on(dispatcher.dispatch(vec![FooEvent::new("created")]))
145            .expect("first dispatch should succeed");
146        block_on(dispatcher.dispatch(vec![FooEvent::new("renamed")]))
147            .expect("second dispatch should succeed");
148
149        assert_eq!(
150            *dispatcher.dispatched.lock().unwrap(),
151            vec![FooEvent::new("created"), FooEvent::new("renamed")]
152        );
153    }
154
155    #[test]
156    fn generic_fn_accepts_event_dispatcher_bound() {
157        fn publish<D: EventDispatcher<FooEvent>>(
158            dispatcher: &D,
159            events: Vec<FooEvent>,
160        ) -> Result<(), DispatchError<FooEvent>> {
161            block_on(dispatcher.dispatch(events))
162        }
163
164        let dispatcher = RecordingDispatcher::new();
165
166        publish(&dispatcher, vec![FooEvent::new("created")]).expect("publish should succeed");
167
168        assert_eq!(
169            *dispatcher.dispatched.lock().unwrap(),
170            vec![FooEvent::new("created")]
171        );
172    }
173
174    #[test]
175    fn dispatch_propagates_port_error() {
176        let dispatcher = FailingDispatcher;
177
178        let error = block_on(dispatcher.dispatch(vec![FooEvent::new("created")]))
179            .expect_err("dispatch should fail");
180
181        assert_eq!(error.source.kind(), PortErrorKind::Unavailable);
182    }
183
184    #[test]
185    fn dispatch_error_reports_the_undelivered_events() {
186        let dispatcher = FailingDispatcher;
187        let events = vec![FooEvent::new("created"), FooEvent::new("renamed")];
188
189        let error = block_on(dispatcher.dispatch(events)).expect_err("dispatch should fail");
190
191        assert_eq!(
192            error.undelivered,
193            vec![FooEvent::new("created"), FooEvent::new("renamed")]
194        );
195    }
196
197    #[test]
198    fn partial_failure_reports_only_the_events_after_the_failure_point() {
199        let dispatcher = PartiallyFailingDispatcher;
200        let events = vec![
201            FooEvent::new("created"),
202            FooEvent::new("boom"),
203            FooEvent::new("renamed"),
204        ];
205
206        let error = block_on(dispatcher.dispatch(events)).expect_err("dispatch should fail");
207
208        assert_eq!(
209            error.undelivered,
210            vec![FooEvent::new("boom"), FooEvent::new("renamed")]
211        );
212    }
213
214    #[test]
215    fn dispatch_error_display_includes_undelivered_count_and_source() {
216        let error = DispatchError::new(
217            vec![FooEvent::new("created")],
218            PortError::unavailable("boom"),
219        );
220
221        assert_eq!(
222            error.to_string(),
223            "dispatch failed with 1 undelivered event(s): Unavailable: boom"
224        );
225    }
226
227    #[test]
228    fn dispatch_empty_vec_is_ok() {
229        let dispatcher = RecordingDispatcher::new();
230
231        block_on(dispatcher.dispatch(Vec::new())).expect("dispatch should succeed");
232
233        assert!(dispatcher.dispatched.lock().unwrap().is_empty());
234    }
235
236    // Compile-time only (no runtime assertion): the generic bound accepts the
237    // future as `Send` purely because `#[trait_variant::make(Send)]` promises
238    // it at the trait level, not because of the concrete impl.
239    #[test]
240    fn dispatcher_future_is_send() {
241        fn assert_send(_: impl Send) {}
242
243        fn check<D: EventDispatcher<FooEvent>>(dispatcher: &D, events: Vec<FooEvent>) {
244            assert_send(dispatcher.dispatch(events));
245        }
246
247        let dispatcher = RecordingDispatcher::new();
248
249        check(&dispatcher, vec![FooEvent::new("created")]);
250    }
251}