1use patterns_macros::because;
2
3pub trait Context {
4 type State;
5 type Command;
6 type Event;
7 type Fault;
8
9 fn initial() -> Self::State;
10 fn decide(state: &Self::State, command: &Self::Command) -> Result<Self::Event, Self::Fault>;
11 fn apply(state: Self::State, event: &Self::Event) -> Self::State;
12}
13
14pub fn replay_from<C: Context>(state: C::State, events: &[C::Event]) -> C::State {
15 let mut next = state;
16 for event in events {
17 next = C::apply(next, event);
18 }
19 next
20}
21
22pub fn replay<C: Context>(events: &[C::Event]) -> C::State {
23 replay_from::<C>(C::initial(), events)
24}
25
26pub fn handle<C: Context>(state: C::State, command: &C::Command) -> Result<C::State, C::Fault> {
27 match C::decide(&state, command) {
28 Ok(event) => Ok(C::apply(state, &event)),
29 Err(fault) => Err(fault),
30 }
31}
32
33pub fn handle_all<C: Context>(
34 state: C::State,
35 commands: &[C::Command],
36) -> Result<C::State, (usize, C::Fault)> {
37 let mut next = state;
38 for (at, command) in commands.iter().enumerate() {
39 match handle::<C>(next, command) {
40 Ok(moved) => next = moved,
41 Err(fault) => return Err((at, fault)),
42 }
43 }
44 Ok(next)
45}
46because!(replay_from, "applying a run of events to a state that already exists, which is how replay works forward from initial and how a caller holding a snapshot catches up without folding the whole log again");
47because!(replay, "folding a whole event log into the current state, so state is always derived and never stored");
48because!(handle, "deciding on a command and applying the resulting event, so a caller cannot apply an event the rules did not produce");
49because!(handle_all, "handle over a sequence, stopping at the first fault and naming the position of the command that raised it, because a partially applied batch is a state no replay can reach and a caller can only report or retry a batch when it knows where it broke");