ddd_toolkit_core/port/
event.rs1use std::fmt;
2
3use crate::{domain::DomainEvent, port::PortError};
4
5#[derive(Debug)]
15pub struct DispatchError<E> {
16 pub undelivered: Vec<E>,
18 pub source: PortError,
20}
21
22impl<E> DispatchError<E> {
23 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#[trait_variant::make(Send)]
51pub trait EventDispatcher<E: DomainEvent> {
52 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 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 #[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}