ddd_toolkit_core/port/
event.rs1use std::fmt;
2
3use crate::{domain::DomainEvent, port::PortError};
4
5#[derive(Debug)]
20pub struct DispatchError<E> {
21 pub undelivered: Vec<E>,
23 pub source: PortError,
25}
26
27impl<E> DispatchError<E> {
28 pub fn new(undelivered: Vec<E>, source: PortError) -> Self {
30 Self {
31 undelivered,
32 source,
33 }
34 }
35}
36
37impl<E: fmt::Debug> fmt::Display for DispatchError<E> {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(
40 f,
41 "dispatch failed with {} undelivered event(s): {}",
42 self.undelivered.len(),
43 self.source
44 )
45 }
46}
47
48impl<E: fmt::Debug> std::error::Error for DispatchError<E> {
49 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50 Some(&self.source)
51 }
52}
53
54#[trait_variant::make(Send)]
97pub trait EventDispatcher<E: DomainEvent> {
98 async fn dispatch(&self, events: Vec<E>) -> Result<(), DispatchError<E>>;
101}
102
103#[cfg(test)]
104mod test {
105 use std::sync::Mutex;
106
107 use crate::{port::PortErrorKind, testing::block_on};
108
109 use super::*;
110
111 #[derive(Debug, Clone, PartialEq)]
112 struct FooEvent {
113 name: String,
114 }
115
116 impl FooEvent {
117 fn new(name: &str) -> Self {
118 Self {
119 name: name.to_string(),
120 }
121 }
122 }
123
124 impl DomainEvent for FooEvent {}
125
126 struct RecordingDispatcher {
127 dispatched: Mutex<Vec<FooEvent>>,
128 }
129
130 impl RecordingDispatcher {
131 fn new() -> Self {
132 Self {
133 dispatched: Mutex::new(Vec::new()),
134 }
135 }
136 }
137
138 impl EventDispatcher<FooEvent> for RecordingDispatcher {
139 async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
140 self.dispatched.lock().unwrap().extend(events);
141 Ok(())
142 }
143 }
144
145 struct FailingDispatcher;
146
147 impl EventDispatcher<FooEvent> for FailingDispatcher {
148 async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
149 Err(DispatchError::new(
150 events,
151 PortError::unavailable("dispatch failed"),
152 ))
153 }
154 }
155
156 struct PartiallyFailingDispatcher;
159
160 impl EventDispatcher<FooEvent> for PartiallyFailingDispatcher {
161 async fn dispatch(&self, events: Vec<FooEvent>) -> Result<(), DispatchError<FooEvent>> {
162 let boom_at = events.iter().position(|e| e.name == "boom");
163 match boom_at {
164 Some(index) => Err(DispatchError::new(
165 events[index..].to_vec(),
166 PortError::unavailable("dispatch failed"),
167 )),
168 None => Ok(()),
169 }
170 }
171 }
172
173 #[test]
174 fn dispatch_records_all_events_in_order() {
175 let dispatcher = RecordingDispatcher::new();
176 let events = vec![FooEvent::new("created"), FooEvent::new("renamed")];
177
178 block_on(dispatcher.dispatch(events)).expect("dispatch should succeed");
179
180 assert_eq!(
181 *dispatcher.dispatched.lock().unwrap(),
182 vec![FooEvent::new("created"), FooEvent::new("renamed")]
183 );
184 }
185
186 #[test]
187 fn dispatch_called_twice_delivers_both_batches() {
188 let dispatcher = RecordingDispatcher::new();
189
190 block_on(dispatcher.dispatch(vec![FooEvent::new("created")]))
191 .expect("first dispatch should succeed");
192 block_on(dispatcher.dispatch(vec![FooEvent::new("renamed")]))
193 .expect("second dispatch should succeed");
194
195 assert_eq!(
196 *dispatcher.dispatched.lock().unwrap(),
197 vec![FooEvent::new("created"), FooEvent::new("renamed")]
198 );
199 }
200
201 #[test]
202 fn generic_fn_accepts_event_dispatcher_bound() {
203 fn publish<D: EventDispatcher<FooEvent>>(
204 dispatcher: &D,
205 events: Vec<FooEvent>,
206 ) -> Result<(), DispatchError<FooEvent>> {
207 block_on(dispatcher.dispatch(events))
208 }
209
210 let dispatcher = RecordingDispatcher::new();
211
212 publish(&dispatcher, vec![FooEvent::new("created")]).expect("publish should succeed");
213
214 assert_eq!(
215 *dispatcher.dispatched.lock().unwrap(),
216 vec![FooEvent::new("created")]
217 );
218 }
219
220 #[test]
221 fn dispatch_propagates_port_error() {
222 let dispatcher = FailingDispatcher;
223
224 let error = block_on(dispatcher.dispatch(vec![FooEvent::new("created")]))
225 .expect_err("dispatch should fail");
226
227 assert_eq!(error.source.kind(), PortErrorKind::Unavailable);
228 }
229
230 #[test]
231 fn dispatch_error_reports_the_undelivered_events() {
232 let dispatcher = FailingDispatcher;
233 let events = vec![FooEvent::new("created"), FooEvent::new("renamed")];
234
235 let error = block_on(dispatcher.dispatch(events)).expect_err("dispatch should fail");
236
237 assert_eq!(
238 error.undelivered,
239 vec![FooEvent::new("created"), FooEvent::new("renamed")]
240 );
241 }
242
243 #[test]
244 fn partial_failure_reports_only_the_events_after_the_failure_point() {
245 let dispatcher = PartiallyFailingDispatcher;
246 let events = vec![
247 FooEvent::new("created"),
248 FooEvent::new("boom"),
249 FooEvent::new("renamed"),
250 ];
251
252 let error = block_on(dispatcher.dispatch(events)).expect_err("dispatch should fail");
253
254 assert_eq!(
255 error.undelivered,
256 vec![FooEvent::new("boom"), FooEvent::new("renamed")]
257 );
258 }
259
260 #[test]
261 fn dispatch_error_display_includes_undelivered_count_and_source() {
262 let error = DispatchError::new(
263 vec![FooEvent::new("created")],
264 PortError::unavailable("boom"),
265 );
266
267 assert_eq!(
268 error.to_string(),
269 "dispatch failed with 1 undelivered event(s): Unavailable: boom"
270 );
271 }
272
273 #[test]
274 fn dispatch_empty_vec_is_ok() {
275 let dispatcher = RecordingDispatcher::new();
276
277 block_on(dispatcher.dispatch(Vec::new())).expect("dispatch should succeed");
278
279 assert!(dispatcher.dispatched.lock().unwrap().is_empty());
280 }
281
282 #[test]
286 fn dispatcher_future_is_send() {
287 fn assert_send(_: impl Send) {}
288
289 fn check<D: EventDispatcher<FooEvent>>(dispatcher: &D, events: Vec<FooEvent>) {
290 assert_send(dispatcher.dispatch(events));
291 }
292
293 let dispatcher = RecordingDispatcher::new();
294
295 check(&dispatcher, vec![FooEvent::new("created")]);
296 }
297}