Skip to main content

commerce_theory/
event_replay.rs

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}