1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use crate::ReactFunction;

/// [Saga] is a datatype that represents the central point of control, deciding what to execute next (`A`), based on the action result (`AR`).
/// It has two generic parameters `AR`/Action Result, `A`/Action , representing the type of the values that Saga may contain or use.
/// `'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.
///
/// It is common to consider Event as Action Result, and Command as Action, but it is not mandatory.
/// For example, Action Result can be a request response from a remote service.
///
/// ## Example
///
/// ```
/// use fmodel_rust::saga::Saga;
///
/// fn saga<'a>() -> Saga<'a, OrderEvent, ShipmentCommand> {
///     Saga {
///         react: Box::new(|event| match event {
///             OrderEvent::Created(created_event) => {
///                 vec![ShipmentCommand::Create(CreateShipmentCommand {
///                     shipment_id: created_event.order_id,
///                     order_id: created_event.order_id,
///                     customer_name: created_event.customer_name.to_owned(),
///                     items: created_event.items.to_owned(),
///                 })]
///             }
///             OrderEvent::Updated(_updated_event) => {
///                 vec![]
///             }
///             OrderEvent::Cancelled(_cancelled_event) => {
///                 vec![]
///             }
///         }),
///     }
/// }
///
/// #[derive(Debug, PartialEq)]
/// #[allow(dead_code)]
/// pub enum ShipmentCommand {
///     Create(CreateShipmentCommand),
/// }
///
/// #[derive(Debug, PartialEq)]
/// pub struct CreateShipmentCommand {
///     pub shipment_id: u32,
///     pub order_id: u32,
///     pub customer_name: String,
///     pub items: Vec<String>,
/// }
///
/// #[derive(Debug)]
/// pub enum OrderEvent {
///     Created(OrderCreatedEvent),
///     Updated(OrderUpdatedEvent),
///     Cancelled(OrderCancelledEvent),
/// }
///
/// #[derive(Debug)]
/// pub struct OrderCreatedEvent {
///     pub order_id: u32,
///     pub customer_name: String,
///     pub items: Vec<String>,
/// }
///
/// #[derive(Debug)]
/// pub struct OrderUpdatedEvent {
///     pub order_id: u32,
///     pub updated_items: Vec<String>,
/// }
///
/// #[derive(Debug)]
/// pub struct OrderCancelledEvent {
///     pub order_id: u32,
/// }
///
/// let saga: Saga<OrderEvent, ShipmentCommand> = saga();
/// let order_created_event = OrderEvent::Created(OrderCreatedEvent {
///         order_id: 1,
///         customer_name: "John Doe".to_string(),
///         items: vec!["Item 1".to_string(), "Item 2".to_string()],
///     });
///
/// let commands = (saga.react)(&order_created_event);
/// ```
pub struct Saga<'a, AR: 'a, A: 'a> {
    /// The `react` function is driving the next action based on the action result.
    pub react: ReactFunction<'a, AR, A>,
}

impl<'a, AR, A> Saga<'a, AR, A> {
    /// Maps the Saga over the A/Action type parameter.
    /// Creates a new instance of [Saga]`<AR, A2>`.
    pub fn map_action<A2, F>(self, f: &'a F) -> Saga<'a, AR, A2>
    where
        F: Fn(&A) -> A2 + Send + Sync,
    {
        let new_react = Box::new(move |ar: &AR| {
            let a = (self.react)(ar);
            a.into_iter().map(|a: A| f(&a)).collect()
        });

        Saga { react: new_react }
    }

    /// Maps the Saga over the AR/ActionResult type parameter.
    /// Creates a new instance of [Saga]`<AR2, A>`.
    pub fn map_action_result<AR2, F>(self, f: &'a F) -> Saga<'a, AR2, A>
    where
        F: Fn(&AR2) -> AR + Send + Sync,
    {
        let new_react = Box::new(move |ar2: &AR2| {
            let ar = f(ar2);
            (self.react)(&ar)
        });

        Saga { react: new_react }
    }
}