sfn_machine/
lib.rs

1/*!
2This crate provides a simple idiomatic implementation of a state machine in rust.
3The state machine offered by this crate is in close mimic of the AWS state-machine definition
4style.
5The crate is still in development and more features will be published to it as they
6become available.
7
8A state machine is comprised of steps which can be one of the following states
9```text
10pub enum State {
11    Task,
12    Choice(fn() -> bool),
13    Sleep(u64),
14    Pass,
15    Parallel,
16    Succeed,
17    Fail,
18    Map,
19    CustomState,
20}
21```
22
23A simple example of the usage is given below:
24
25```text
26use std::{error::Error, fmt::Debug};
27use serde::{Deserialize, Serialize};
28use sfn_machine::machine::
29    {state::{StateMachine, State}, data::DeserializeStateData};
30
31// Define the struct representing the shared data
32#[derive(Debug, Serialize, Deserialize)]
33struct SharedData {
34  counter: i16,
35  id: String,
36}
37
38// Implement the deserialization trait for the SharedData struct
39impl DeserializeStateData for SharedData {
40  fn from_json(json: &str) -> Result<Self, Box<dyn Error>> {
41    let data: Self = serde_json::from_str(json)?;
42    Ok(data)
43  }
44}
45
46fn match_vecs<T: PartialEq + std::fmt::Debug>(a: &Vec<T>, b: &Vec<T>) -> bool {
47    let mut matching = true;
48    for index in 0..a.len() {
49        if !b.contains(&a[index]) {
50            matching = false;
51            break
52        }
53    };
54
55    matching
56}
57
58pub fn main() {
59    // JSON representation of the shared data
60    let json_data = r#"{"counter": 5, "id": "come-id"}"#;
61    // Deserialize the shared data
62    let shared_data: SharedData = SharedData::from_json(json_data).expect("Failed to deserialize data");
63  
64    // Define state functions
65    fn state_function_a(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
66      data.counter += 1;
67      Ok(())
68    }
69  
70    fn state_function_b(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
71      data.counter += 100;
72      Ok(())
73    }
74  
75    fn state_function_c(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
76      data.counter *= 1;
77      Ok(())
78    }
79
80    fn state_function_d(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
81        data.counter *= 5;
82        Ok(())
83    }
84  
85    // Create a state machine
86    let mut shared_data = SharedData { counter: shared_data.counter, id: shared_data.id };
87    let mut sfn_machine = StateMachine::new("MachineA011".to_string(), &mut shared_data, 3);
88
89    sfn_machine.step("NodeA", State::Task, state_function_a, None, None, None, None);
90    sfn_machine.step("NodeB", State::Task, state_function_b, None, None, None, None);
91    sfn_machine.step("NodeC", State::Task, state_function_c, None, None, None, None);
92    // The end attribute can be set optionally. When set, the node becomes the last step in the state machine
93    sfn_machine.step("NodeD", State::Task, state_function_d, None, None, None, Some(true));
94
95    // Validate node IDs
96    sfn_machine.validate_node_ids();
97
98    // execute state machine
99    if let Err(err) = sfn_machine.execute() {
100      println!("State machine execution failed: {}", err);
101    }
102  }
103```
104
105# Overview
106The implementation is implemented as a linked-list, meaning the executions will follow
107their order of definition, requiring no additional work to execute in a given order.
108
109There is also the option to define the order of execution using the `next` attribute of the step function.
110
111```text
112fn state_function_a(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
113  data.counter += 1;
114  Ok(())
115}
116  
117fn state_function_b(data: &mut SharedData) -> Result<(), Box<dyn Error>> {
118  data.counter += 100;
119  Ok(())
120}
121
122let mut shared_data = SharedData { counter: shared_data.counter, id: shared_data.id };
123let mut sfn_machine = StateMachine::new("MachineA011".to_string(), &mut shared_data, 3);
124
125sfn_machine.step("NodeA", State::Task, state_function_a, state_function_b, None, None, None);
126sfn_machine.step("NodeB", State::Task, state_function_b, None, None, None, None);
127```
128
129Same is also true for defining the last step in the state machine.
130
131One can also define a set of errors to catch or retry, with corresponding actions to be taken when they are matched
132Example
133```text
134sfn_machine.step("Node0", State::Task, StateMachine::error, None, None, Some(vec!["STATE.FAILED"]), Some(false));
135```
136*/
137
138#![deny(missing_docs)]
139#![warn(missing_debug_implementations)]
140
141
142/// The state machine module defines a process for procedurally orchestrating a set of tasks
143/// 
144/// It is a minimalistic implementation that utilizes a linked-list such that the tasks already
145/// execute is a given fashion with little work needed to defined the steps
146pub mod machine;