fmodel_rust/
saga.rs

1use crate::{ReactFunction, Saga3, Saga4, Saga5, Saga6, Sum, Sum3, Sum4, Sum5, Sum6};
2
3/// [Saga] is a datatype that represents the central point of control, deciding what to execute next (`A`), based on the action result (`AR`).
4/// It has two generic parameters `AR`/Action Result, `A`/Action , representing the type of the values that Saga may contain or use.
5/// `'a` is used as a lifetime parameter, indicating that all references contained within the struct (e.g., references within the function closures) must have a lifetime that is at least as long as 'a.
6///
7/// It is common to consider Event as Action Result, and Command as Action, but it is not mandatory.
8/// For example, Action Result can be a request response from a remote service.
9///
10/// ## Example
11///
12/// ```
13/// use fmodel_rust::saga::Saga;
14///
15/// fn saga<'a>() -> Saga<'a, OrderEvent, ShipmentCommand> {
16///     Saga {
17///         react: Box::new(|event| match event {
18///             OrderEvent::Created(created_event) => {
19///                 vec![ShipmentCommand::Create(CreateShipmentCommand {
20///                     shipment_id: created_event.order_id,
21///                     order_id: created_event.order_id,
22///                     customer_name: created_event.customer_name.to_owned(),
23///                     items: created_event.items.to_owned(),
24///                 })]
25///             }
26///             OrderEvent::Updated(_updated_event) => {
27///                 vec![]
28///             }
29///             OrderEvent::Cancelled(_cancelled_event) => {
30///                 vec![]
31///             }
32///         }),
33///     }
34/// }
35///
36/// #[derive(Debug, PartialEq)]
37/// #[allow(dead_code)]
38/// pub enum ShipmentCommand {
39///     Create(CreateShipmentCommand),
40/// }
41///
42/// #[derive(Debug, PartialEq)]
43/// pub struct CreateShipmentCommand {
44///     pub shipment_id: u32,
45///     pub order_id: u32,
46///     pub customer_name: String,
47///     pub items: Vec<String>,
48/// }
49///
50/// #[derive(Debug)]
51/// pub enum OrderEvent {
52///     Created(OrderCreatedEvent),
53///     Updated(OrderUpdatedEvent),
54///     Cancelled(OrderCancelledEvent),
55/// }
56///
57/// #[derive(Debug)]
58/// pub struct OrderCreatedEvent {
59///     pub order_id: u32,
60///     pub customer_name: String,
61///     pub items: Vec<String>,
62/// }
63///
64/// #[derive(Debug)]
65/// pub struct OrderUpdatedEvent {
66///     pub order_id: u32,
67///     pub updated_items: Vec<String>,
68/// }
69///
70/// #[derive(Debug)]
71/// pub struct OrderCancelledEvent {
72///     pub order_id: u32,
73/// }
74///
75/// let saga: Saga<OrderEvent, ShipmentCommand> = saga();
76/// let order_created_event = OrderEvent::Created(OrderCreatedEvent {
77///         order_id: 1,
78///         customer_name: "John Doe".to_string(),
79///         items: vec!["Item 1".to_string(), "Item 2".to_string()],
80///     });
81///
82/// let commands = (saga.react)(&order_created_event);
83/// ```
84pub struct Saga<'a, AR: 'a, A: 'a> {
85    /// The `react` function is driving the next action based on the action result.
86    pub react: ReactFunction<'a, AR, A>,
87}
88
89impl<'a, AR, A> Saga<'a, AR, A> {
90    /// Maps the Saga over the A/Action type parameter.
91    /// Creates a new instance of [Saga]`<AR, A2>`.
92    #[cfg(not(feature = "not-send-futures"))]
93    pub fn map_action<A2, F>(self, f: F) -> Saga<'a, AR, A2>
94    where
95        F: Fn(&A) -> A2 + Send + Sync + 'a,
96    {
97        let new_react = Box::new(move |ar: &AR| {
98            let a = (self.react)(ar);
99            a.into_iter().map(|a: A| f(&a)).collect()
100        });
101
102        Saga { react: new_react }
103    }
104
105    /// Maps the Saga over the A/Action type parameter.
106    /// Creates a new instance of [Saga]`<AR, A2>`.
107    #[cfg(feature = "not-send-futures")]
108    pub fn map_action<A2, F>(self, f: F) -> Saga<'a, AR, A2>
109    where
110        F: Fn(&A) -> A2 + 'a,
111    {
112        let new_react = Box::new(move |ar: &AR| {
113            let a = (self.react)(ar);
114            a.into_iter().map(|a: A| f(&a)).collect()
115        });
116
117        Saga { react: new_react }
118    }
119
120    /// Maps the Saga over the AR/ActionResult type parameter.
121    /// Creates a new instance of [Saga]`<AR2, A>`.
122    #[cfg(not(feature = "not-send-futures"))]
123    pub fn map_action_result<AR2, F>(self, f: F) -> Saga<'a, AR2, A>
124    where
125        F: Fn(&AR2) -> AR + Send + Sync + 'a,
126    {
127        let new_react = Box::new(move |ar2: &AR2| {
128            let ar = f(ar2);
129            (self.react)(&ar)
130        });
131
132        Saga { react: new_react }
133    }
134
135    /// Maps the Saga over the AR/ActionResult type parameter.
136    /// Creates a new instance of [Saga]`<AR2, A>`.
137    #[cfg(feature = "not-send-futures")]
138    pub fn map_action_result<AR2, F>(self, f: F) -> Saga<'a, AR2, A>
139    where
140        F: Fn(&AR2) -> AR + 'a,
141    {
142        let new_react = Box::new(move |ar2: &AR2| {
143            let ar = f(ar2);
144            (self.react)(&ar)
145        });
146
147        Saga { react: new_react }
148    }
149
150    /// Combines two sagas into one.
151    /// Creates a new instance of a Saga by combining two sagas of type `AR`, `A` and `AR2`, `A2` into a new saga of type `Sum<AR, AR2>`, `Sum<A2, A>`
152    #[deprecated(
153        since = "0.8.0",
154        note = "Use the `merge` function instead. This ensures all your sagas can subscribe to all `Event`/`E` in the system."
155    )]
156    pub fn combine<AR2, A2>(self, saga2: Saga<'a, AR2, A2>) -> Saga<'a, Sum<AR, AR2>, Sum<A2, A>> {
157        let new_react = Box::new(move |ar: &Sum<AR, AR2>| match ar {
158            Sum::First(ar) => {
159                let a = (self.react)(ar);
160                a.into_iter().map(|a: A| Sum::Second(a)).collect()
161            }
162            Sum::Second(ar2) => {
163                let a2 = (saga2.react)(ar2);
164                a2.into_iter().map(|a: A2| Sum::First(a)).collect()
165            }
166        });
167
168        Saga { react: new_react }
169    }
170
171    /// Merges two sagas into one.
172    /// Creates a new instance of a Saga by merging two sagas of type `AR`, `A` and `AR`, `A2` into a new saga of type `AR`, `Sum<A, A2>`
173    /// Similar to `combine`, but the event type is the same for both sagas.
174    /// This ensures all your sagas can subscribe to all `Event`/`E` in the system.
175    pub fn merge<A2>(self, saga2: Saga<'a, AR, A2>) -> Saga<'a, AR, Sum<A2, A>> {
176        let new_react = Box::new(move |ar: &AR| {
177            let a: Vec<Sum<A2, A>> = (self.react)(ar)
178                .into_iter()
179                .map(|a: A| Sum::Second(a))
180                .collect();
181            let a2: Vec<Sum<A2, A>> = (saga2.react)(ar)
182                .into_iter()
183                .map(|a2: A2| Sum::First(a2))
184                .collect();
185
186            a.into_iter().chain(a2).collect()
187        });
188
189        Saga { react: new_react }
190    }
191
192    /// Merges three sagas into one.
193    pub fn merge3<A2, A3>(
194        self,
195        saga2: Saga<'a, AR, A2>,
196        saga3: Saga<'a, AR, A3>,
197    ) -> Saga3<'a, AR, A, A2, A3>
198    where
199        A: Clone,
200        A2: Clone,
201        A3: Clone,
202    {
203        self.merge(saga2)
204            .merge(saga3)
205            .map_action(|a: &Sum<A3, Sum<A2, A>>| match a {
206                Sum::First(a) => Sum3::Third(a.clone()),
207                Sum::Second(Sum::First(a)) => Sum3::Second(a.clone()),
208                Sum::Second(Sum::Second(a)) => Sum3::First(a.clone()),
209            })
210    }
211
212    /// Merges four sagas into one.
213    pub fn merge4<A2, A3, A4>(
214        self,
215        saga2: Saga<'a, AR, A2>,
216        saga3: Saga<'a, AR, A3>,
217        saga4: Saga<'a, AR, A4>,
218    ) -> Saga4<'a, AR, A, A2, A3, A4>
219    where
220        A: Clone,
221        A2: Clone,
222        A3: Clone,
223        A4: Clone,
224    {
225        self.merge(saga2).merge(saga3).merge(saga4).map_action(
226            |a: &Sum<A4, Sum<A3, Sum<A2, A>>>| match a {
227                Sum::First(a) => Sum4::Fourth(a.clone()),
228                Sum::Second(Sum::First(a)) => Sum4::Third(a.clone()),
229                Sum::Second(Sum::Second(Sum::First(a))) => Sum4::Second(a.clone()),
230                Sum::Second(Sum::Second(Sum::Second(a))) => Sum4::First(a.clone()),
231            },
232        )
233    }
234
235    #[allow(clippy::type_complexity)]
236    /// Merges five sagas into one.
237    pub fn merge5<A2, A3, A4, A5>(
238        self,
239        saga2: Saga<'a, AR, A2>,
240        saga3: Saga<'a, AR, A3>,
241        saga4: Saga<'a, AR, A4>,
242        saga5: Saga<'a, AR, A5>,
243    ) -> Saga5<'a, AR, A, A2, A3, A4, A5>
244    where
245        A: Clone,
246        A2: Clone,
247        A3: Clone,
248        A4: Clone,
249        A5: Clone,
250    {
251        self.merge(saga2)
252            .merge(saga3)
253            .merge(saga4)
254            .merge(saga5)
255            .map_action(|a: &Sum<A5, Sum<A4, Sum<A3, Sum<A2, A>>>>| match a {
256                Sum::First(a) => Sum5::Fifth(a.clone()),
257                Sum::Second(Sum::First(a)) => Sum5::Fourth(a.clone()),
258                Sum::Second(Sum::Second(Sum::First(a))) => Sum5::Third(a.clone()),
259                Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum5::Second(a.clone()),
260                Sum::Second(Sum::Second(Sum::Second(Sum::Second(a)))) => Sum5::First(a.clone()),
261            })
262    }
263
264    #[allow(clippy::type_complexity)]
265    /// Merges six sagas into one.
266    pub fn merge6<A2, A3, A4, A5, A6>(
267        self,
268        saga2: Saga<'a, AR, A2>,
269        saga3: Saga<'a, AR, A3>,
270        saga4: Saga<'a, AR, A4>,
271        saga5: Saga<'a, AR, A5>,
272        saga6: Saga<'a, AR, A6>,
273    ) -> Saga6<'a, AR, A, A2, A3, A4, A5, A6>
274    where
275        A: Clone,
276        A2: Clone,
277        A3: Clone,
278        A4: Clone,
279        A5: Clone,
280        A6: Clone,
281    {
282        self.merge(saga2)
283            .merge(saga3)
284            .merge(saga4)
285            .merge(saga5)
286            .merge(saga6)
287            .map_action(
288                |a: &Sum<A6, Sum<A5, Sum<A4, Sum<A3, Sum<A2, A>>>>>| match a {
289                    Sum::First(a) => Sum6::Sixth(a.clone()),
290                    Sum::Second(Sum::First(a)) => Sum6::Fifth(a.clone()),
291                    Sum::Second(Sum::Second(Sum::First(a))) => Sum6::Fourth(a.clone()),
292                    Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum6::Third(a.clone()),
293                    Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::First(a))))) => {
294                        Sum6::Second(a.clone())
295                    }
296                    Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::Second(a))))) => {
297                        Sum6::First(a.clone())
298                    }
299                },
300            )
301    }
302}
303
304/// Formalizes the `Action Computation` algorithm for the `saga` to handle events/action_results, and produce new commands/actions.
305pub trait ActionComputation<AR, A> {
306    /// Computes new commands/actions based on the event/action_result.
307    fn compute_new_actions(&self, event: &AR) -> Vec<A>;
308}
309
310impl<AR, A> ActionComputation<AR, A> for Saga<'_, AR, A> {
311    /// Computes new commands/actions based on the event/action_result.
312    fn compute_new_actions(&self, event: &AR) -> Vec<A> {
313        (self.react)(event).into_iter().collect()
314    }
315}