1use crate::event_sourcing::*;
2use crate::foundation::*;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Timed<T> {
7 pub ret: T,
8 pub time: Nat,
9}
10
11impl<T> Timed<T> {
12 pub const fn new(ret: T, time: Nat) -> Self {
13 Self { ret, time }
14 }
15}
16
17pub fn webhook_replay_in_steps(
18 state: WebhookOrderingState,
19 events: &[EventEnvelope],
20) -> DomainResult<Timed<WebhookOrderingState>> {
21 let next = replay_webhook_stream(state, events)?;
22 Ok(Timed::new(next, events.len() as Nat))
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub enum WebhookOrderingStep {
28 Accept {
29 before: WebhookOrderingState,
30 sequence: Nat,
31 after: WebhookOrderingState,
32 },
33}
34
35impl WebhookOrderingStep {
36 pub fn accept(before: WebhookOrderingState, sequence: Nat) -> DomainResult<Self> {
37 let after = apply_webhook(&before, sequence)?;
38 Ok(Self::Accept {
39 before,
40 sequence,
41 after,
42 })
43 }
44
45 #[must_use]
46 pub const fn before(&self) -> &WebhookOrderingState {
47 match self {
48 Self::Accept { before, .. } => before,
49 }
50 }
51
52 #[must_use]
53 pub const fn after(&self) -> &WebhookOrderingState {
54 match self {
55 Self::Accept { after, .. } => after,
56 }
57 }
58}
59
60pub fn webhook_replay_within_steps(
61 state: WebhookOrderingState,
62 events: &[EventEnvelope],
63 bound: Nat,
64) -> DomainResult<Option<Timed<WebhookOrderingState>>> {
65 let replay = webhook_replay_in_steps(state, events)?;
66 Ok((replay.time <= bound).then_some(replay))
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub enum ValidSystemEvent {
72 StockReserved(Sku, Quantity),
73 RefundIssued(Money),
74 ReservationReleased(Sku, Quantity),
75 ReservedShipmentConfirmed(Sku, Quantity),
76 TaxLiabilityRecorded(Money),
77 CrmProjected,
78 LogisticsProjected,
79}
80
81pub fn valid_system_replay_in_steps(
82 mut state: ValidSystemState,
83 events: &[ValidSystemEvent],
84) -> DomainResult<Timed<ValidSystemState>> {
85 for event in events {
86 state = match *event {
87 ValidSystemEvent::StockReserved(sku, quantity) => {
88 apply_stock_reserved_event(&state, sku, quantity)?
89 }
90 ValidSystemEvent::RefundIssued(amount) => apply_refund_issued_event(&state, amount)?,
91 ValidSystemEvent::ReservationReleased(sku, quantity) => {
92 apply_reservation_released_event(&state, sku, quantity)?
93 }
94 ValidSystemEvent::ReservedShipmentConfirmed(sku, quantity) => {
95 apply_reserved_shipment_confirmed_event(&state, sku, quantity)?
96 }
97 ValidSystemEvent::TaxLiabilityRecorded(amount) => {
98 apply_tax_liability_recorded_event(&state, amount)?
99 }
100 ValidSystemEvent::CrmProjected => apply_crm_projected_event(&state)?,
101 ValidSystemEvent::LogisticsProjected => apply_logistics_projected_event(&state)?,
102 };
103 }
104 Ok(Timed::new(state, events.len() as Nat))
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109pub enum ValidSystemEventStep {
110 StockReserved {
111 before: ValidSystemState,
112 sku: Sku,
113 quantity: Quantity,
114 after: ValidSystemState,
115 },
116 RefundIssued {
117 before: ValidSystemState,
118 amount: Money,
119 after: ValidSystemState,
120 },
121 ReservationReleased {
122 before: ValidSystemState,
123 sku: Sku,
124 quantity: Quantity,
125 after: ValidSystemState,
126 },
127 ReservedShipmentConfirmed {
128 before: ValidSystemState,
129 sku: Sku,
130 quantity: Quantity,
131 after: ValidSystemState,
132 },
133 TaxLiabilityRecorded {
134 before: ValidSystemState,
135 amount: Money,
136 after: ValidSystemState,
137 },
138 CrmProjected {
139 before: ValidSystemState,
140 after: ValidSystemState,
141 },
142 LogisticsProjected {
143 before: ValidSystemState,
144 after: ValidSystemState,
145 },
146}
147
148impl ValidSystemEventStep {
149 pub fn stock_reserved(
150 before: ValidSystemState,
151 sku: Sku,
152 quantity: Quantity,
153 ) -> DomainResult<Self> {
154 let after = apply_stock_reserved_event(&before, sku, quantity)?;
155 Ok(Self::StockReserved {
156 before,
157 sku,
158 quantity,
159 after,
160 })
161 }
162
163 pub fn refund_issued(before: ValidSystemState, amount: Money) -> DomainResult<Self> {
164 let after = apply_refund_issued_event(&before, amount)?;
165 Ok(Self::RefundIssued {
166 before,
167 amount,
168 after,
169 })
170 }
171
172 pub fn reservation_released(
173 before: ValidSystemState,
174 sku: Sku,
175 quantity: Quantity,
176 ) -> DomainResult<Self> {
177 let after = apply_reservation_released_event(&before, sku, quantity)?;
178 Ok(Self::ReservationReleased {
179 before,
180 sku,
181 quantity,
182 after,
183 })
184 }
185
186 pub fn reserved_shipment_confirmed(
187 before: ValidSystemState,
188 sku: Sku,
189 quantity: Quantity,
190 ) -> DomainResult<Self> {
191 let after = apply_reserved_shipment_confirmed_event(&before, sku, quantity)?;
192 Ok(Self::ReservedShipmentConfirmed {
193 before,
194 sku,
195 quantity,
196 after,
197 })
198 }
199
200 pub fn tax_liability_recorded(before: ValidSystemState, amount: Money) -> DomainResult<Self> {
201 let after = apply_tax_liability_recorded_event(&before, amount)?;
202 Ok(Self::TaxLiabilityRecorded {
203 before,
204 amount,
205 after,
206 })
207 }
208
209 pub fn crm_projected(before: ValidSystemState) -> DomainResult<Self> {
210 let after = apply_crm_projected_event(&before)?;
211 Ok(Self::CrmProjected { before, after })
212 }
213
214 pub fn logistics_projected(before: ValidSystemState) -> DomainResult<Self> {
215 let after = apply_logistics_projected_event(&before)?;
216 Ok(Self::LogisticsProjected { before, after })
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
222pub enum ValidDomainEventStep {
223 StockReserved {
224 event: DomainEvent,
225 before: ValidSystemState,
226 after: ValidSystemState,
227 },
228 RefundIssued {
229 event: DomainEvent,
230 before: ValidSystemState,
231 after: ValidSystemState,
232 },
233 ReservationReleased {
234 event: DomainEvent,
235 before: ValidSystemState,
236 after: ValidSystemState,
237 },
238 ReservedShipmentConfirmed {
239 event: DomainEvent,
240 before: ValidSystemState,
241 after: ValidSystemState,
242 },
243 TaxLiabilityRecorded {
244 event: DomainEvent,
245 before: ValidSystemState,
246 after: ValidSystemState,
247 },
248 CrmProjected {
249 event: DomainEvent,
250 before: ValidSystemState,
251 after: ValidSystemState,
252 },
253 LogisticsProjected {
254 event: DomainEvent,
255 before: ValidSystemState,
256 after: ValidSystemState,
257 },
258}
259
260impl ValidDomainEventStep {
261 pub fn stock_reserved(
262 before: ValidSystemState,
263 sku: Sku,
264 quantity: Quantity,
265 ) -> DomainResult<Self> {
266 let after = apply_stock_reserved_event(&before, sku, quantity)?;
267 Ok(Self::StockReserved {
268 event: DomainEvent::StockReserved(sku, quantity),
269 before,
270 after,
271 })
272 }
273
274 pub fn refund_issued(
275 before: ValidSystemState,
276 order_id: OrderId,
277 amount: Money,
278 ) -> DomainResult<Self> {
279 let after = apply_refund_issued_event(&before, amount)?;
280 Ok(Self::RefundIssued {
281 event: DomainEvent::RefundIssued(order_id, amount),
282 before,
283 after,
284 })
285 }
286
287 pub fn reservation_released(
288 before: ValidSystemState,
289 sku: Sku,
290 quantity: Quantity,
291 ) -> DomainResult<Self> {
292 let after = apply_reservation_released_event(&before, sku, quantity)?;
293 Ok(Self::ReservationReleased {
294 event: DomainEvent::ReservationReleased(sku, quantity),
295 before,
296 after,
297 })
298 }
299
300 pub fn reserved_shipment_confirmed(
301 before: ValidSystemState,
302 sku: Sku,
303 quantity: Quantity,
304 ) -> DomainResult<Self> {
305 let after = apply_reserved_shipment_confirmed_event(&before, sku, quantity)?;
306 Ok(Self::ReservedShipmentConfirmed {
307 event: DomainEvent::ReservedShipmentConfirmed(sku, quantity),
308 before,
309 after,
310 })
311 }
312
313 pub fn tax_liability_recorded(
314 before: ValidSystemState,
315 id: Id,
316 amount: Money,
317 ) -> DomainResult<Self> {
318 let after = apply_tax_liability_recorded_event(&before, amount)?;
319 Ok(Self::TaxLiabilityRecorded {
320 event: DomainEvent::TaxLiabilityRecorded(id, amount),
321 before,
322 after,
323 })
324 }
325
326 pub fn crm_projected(before: ValidSystemState, event: DomainEvent) -> DomainResult<Self> {
327 if !domain_event_is_crm(&event) {
328 return Err(ValidationError::EventStreamInvalid);
329 }
330 let after = apply_crm_projected_event(&before)?;
331 Ok(Self::CrmProjected {
332 event,
333 before,
334 after,
335 })
336 }
337
338 pub fn logistics_projected(before: ValidSystemState, event: DomainEvent) -> DomainResult<Self> {
339 if !domain_event_is_logistics(&event) {
340 return Err(ValidationError::EventStreamInvalid);
341 }
342 let after = apply_logistics_projected_event(&before)?;
343 Ok(Self::LogisticsProjected {
344 event,
345 before,
346 after,
347 })
348 }
349
350 pub fn from_event(before: ValidSystemState, event: DomainEvent) -> DomainResult<Self> {
351 match event {
352 DomainEvent::StockReserved(sku, quantity) => {
353 Self::stock_reserved(before, sku, quantity)
354 }
355 DomainEvent::RefundIssued(order_id, amount) => {
356 Self::refund_issued(before, order_id, amount)
357 }
358 DomainEvent::ReservationReleased(sku, quantity) => {
359 Self::reservation_released(before, sku, quantity)
360 }
361 DomainEvent::ReservedShipmentConfirmed(sku, quantity) => {
362 Self::reserved_shipment_confirmed(before, sku, quantity)
363 }
364 DomainEvent::TaxLiabilityRecorded(id, amount) => {
365 Self::tax_liability_recorded(before, id, amount)
366 }
367 event if domain_event_is_crm(&event) => Self::crm_projected(before, event),
368 event if domain_event_is_logistics(&event) => Self::logistics_projected(before, event),
369 _ => Err(ValidationError::EventStreamInvalid),
370 }
371 }
372
373 #[must_use]
374 pub const fn event(&self) -> &DomainEvent {
375 match self {
376 Self::StockReserved { event, .. }
377 | Self::RefundIssued { event, .. }
378 | Self::ReservationReleased { event, .. }
379 | Self::ReservedShipmentConfirmed { event, .. }
380 | Self::TaxLiabilityRecorded { event, .. }
381 | Self::CrmProjected { event, .. }
382 | Self::LogisticsProjected { event, .. } => event,
383 }
384 }
385
386 #[must_use]
387 pub const fn before(&self) -> &ValidSystemState {
388 match self {
389 Self::StockReserved { before, .. }
390 | Self::RefundIssued { before, .. }
391 | Self::ReservationReleased { before, .. }
392 | Self::ReservedShipmentConfirmed { before, .. }
393 | Self::TaxLiabilityRecorded { before, .. }
394 | Self::CrmProjected { before, .. }
395 | Self::LogisticsProjected { before, .. } => before,
396 }
397 }
398
399 #[must_use]
400 pub const fn after(&self) -> &ValidSystemState {
401 match self {
402 Self::StockReserved { after, .. }
403 | Self::RefundIssued { after, .. }
404 | Self::ReservationReleased { after, .. }
405 | Self::ReservedShipmentConfirmed { after, .. }
406 | Self::TaxLiabilityRecorded { after, .. }
407 | Self::CrmProjected { after, .. }
408 | Self::LogisticsProjected { after, .. } => after,
409 }
410 }
411}
412
413pub fn valid_system_replay_within_steps(
414 state: ValidSystemState,
415 events: &[ValidSystemEvent],
416 bound: Nat,
417) -> DomainResult<Option<Timed<ValidSystemState>>> {
418 let replay = valid_system_replay_in_steps(state, events)?;
419 Ok((replay.time <= bound).then_some(replay))
420}