Skip to main content

fakecloud_stepfunctions/
state.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use chrono::{DateTime, Utc};
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub type SharedStepFunctionsState =
10    Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<StepFunctionsState>>>;
11
12impl fakecloud_core::multi_account::AccountState for StepFunctionsState {
13    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
14        Self::new(account_id, region)
15    }
16}
17
18pub const STEPFUNCTIONS_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct StepFunctionsSnapshot {
22    pub schema_version: u32,
23    #[serde(default)]
24    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<StepFunctionsState>>,
25    #[serde(default)]
26    pub state: Option<StepFunctionsState>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct StepFunctionsState {
31    pub account_id: String,
32    pub region: String,
33    /// State machines keyed by ARN.
34    #[serde(default)]
35    pub state_machines: HashMap<String, StateMachine>,
36    /// Executions keyed by execution ARN.
37    #[serde(default)]
38    pub executions: HashMap<String, Execution>,
39    #[serde(default)]
40    pub activities: HashMap<String, Activity>,
41    #[serde(default)]
42    pub state_machine_versions: HashMap<String, StateMachineVersion>,
43    #[serde(default)]
44    pub state_machine_aliases: HashMap<String, StateMachineAlias>,
45    #[serde(default)]
46    pub map_runs: HashMap<String, MapRun>,
47    /// Pending task tokens issued for sync activities + their outcome.
48    #[serde(default)]
49    pub task_tokens: HashMap<String, TaskTokenState>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Activity {
54    pub name: String,
55    pub arn: String,
56    pub creation_date: DateTime<Utc>,
57    pub tags: HashMap<String, String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct StateMachineVersion {
62    pub state_machine_arn: String,
63    pub version: i64,
64    pub revision_id: String,
65    pub description: String,
66    pub creation_date: DateTime<Utc>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct StateMachineAlias {
71    pub name: String,
72    pub arn: String,
73    pub description: String,
74    pub routing_configuration: Vec<AliasRoute>,
75    pub creation_date: DateTime<Utc>,
76    pub update_date: DateTime<Utc>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct AliasRoute {
81    pub state_machine_version_arn: String,
82    pub weight: i32,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct MapRun {
87    pub map_run_arn: String,
88    pub execution_arn: String,
89    pub max_concurrency: i32,
90    pub tolerated_failure_percentage: f64,
91    pub tolerated_failure_count: i64,
92    pub status: String,
93    pub start_date: DateTime<Utc>,
94    pub stop_date: Option<DateTime<Utc>>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct TaskTokenState {
99    pub activity_arn: String,
100    pub status: String, // PENDING / SUCCEEDED / FAILED / HEARTBEAT
101    pub output: Option<String>,
102    pub error: Option<String>,
103    pub cause: Option<String>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct StateMachine {
108    pub name: String,
109    pub arn: String,
110    pub definition: String,
111    pub role_arn: String,
112    pub machine_type: StateMachineType,
113    pub status: StateMachineStatus,
114    pub creation_date: DateTime<Utc>,
115    pub update_date: DateTime<Utc>,
116    pub tags: HashMap<String, String>,
117    pub revision_id: String,
118    pub logging_configuration: Option<Value>,
119    pub tracing_configuration: Option<Value>,
120    pub description: String,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124pub enum StateMachineType {
125    Standard,
126    Express,
127}
128
129impl StateMachineType {
130    pub fn as_str(&self) -> &'static str {
131        match self {
132            Self::Standard => "STANDARD",
133            Self::Express => "EXPRESS",
134        }
135    }
136
137    pub fn parse(s: &str) -> Option<Self> {
138        match s {
139            "STANDARD" => Some(Self::Standard),
140            "EXPRESS" => Some(Self::Express),
141            _ => None,
142        }
143    }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147pub enum StateMachineStatus {
148    Active,
149    Deleting,
150}
151
152impl StateMachineStatus {
153    pub fn as_str(&self) -> &'static str {
154        match self {
155            Self::Active => "ACTIVE",
156            Self::Deleting => "DELETING",
157        }
158    }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Execution {
163    pub execution_arn: String,
164    pub state_machine_arn: String,
165    pub state_machine_name: String,
166    pub name: String,
167    pub status: ExecutionStatus,
168    pub input: Option<String>,
169    pub output: Option<String>,
170    pub start_date: DateTime<Utc>,
171    pub stop_date: Option<DateTime<Utc>>,
172    pub error: Option<String>,
173    pub cause: Option<String>,
174    pub history_events: Vec<HistoryEvent>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
178pub enum ExecutionStatus {
179    Running,
180    Succeeded,
181    Failed,
182    TimedOut,
183    Aborted,
184    PendingRedrive,
185}
186
187impl ExecutionStatus {
188    pub fn as_str(&self) -> &'static str {
189        match self {
190            Self::Running => "RUNNING",
191            Self::Succeeded => "SUCCEEDED",
192            Self::Failed => "FAILED",
193            Self::TimedOut => "TIMED_OUT",
194            Self::Aborted => "ABORTED",
195            Self::PendingRedrive => "PENDING_REDRIVE",
196        }
197    }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct HistoryEvent {
202    pub id: i64,
203    pub event_type: String,
204    pub timestamp: DateTime<Utc>,
205    pub previous_event_id: i64,
206    pub details: Value,
207}
208
209impl StepFunctionsState {
210    pub fn new(account_id: &str, region: &str) -> Self {
211        Self {
212            account_id: account_id.to_string(),
213            region: region.to_string(),
214            state_machines: HashMap::new(),
215            executions: HashMap::new(),
216            activities: HashMap::new(),
217            state_machine_versions: HashMap::new(),
218            state_machine_aliases: HashMap::new(),
219            map_runs: HashMap::new(),
220            task_tokens: HashMap::new(),
221        }
222    }
223
224    pub fn reset(&mut self) {
225        self.state_machines.clear();
226        self.executions.clear();
227        self.activities.clear();
228        self.state_machine_versions.clear();
229        self.state_machine_aliases.clear();
230        self.map_runs.clear();
231        self.task_tokens.clear();
232    }
233
234    pub fn state_machine_arn(&self, name: &str) -> String {
235        format!(
236            "arn:aws:states:{}:{}:stateMachine:{}",
237            self.region, self.account_id, name
238        )
239    }
240
241    pub fn execution_arn(&self, state_machine_name: &str, execution_name: &str) -> String {
242        format!(
243            "arn:aws:states:{}:{}:execution:{}:{}",
244            self.region, self.account_id, state_machine_name, execution_name
245        )
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn state_machine_type_as_str() {
255        assert_eq!(StateMachineType::Standard.as_str(), "STANDARD");
256        assert_eq!(StateMachineType::Express.as_str(), "EXPRESS");
257    }
258
259    #[test]
260    fn state_machine_type_parse() {
261        assert_eq!(
262            StateMachineType::parse("STANDARD"),
263            Some(StateMachineType::Standard)
264        );
265        assert_eq!(
266            StateMachineType::parse("EXPRESS"),
267            Some(StateMachineType::Express)
268        );
269        assert_eq!(StateMachineType::parse("bogus"), None);
270    }
271
272    #[test]
273    fn state_machine_status_as_str() {
274        assert_eq!(StateMachineStatus::Active.as_str(), "ACTIVE");
275        assert_eq!(StateMachineStatus::Deleting.as_str(), "DELETING");
276    }
277
278    #[test]
279    fn execution_status_as_str() {
280        assert_eq!(ExecutionStatus::Running.as_str(), "RUNNING");
281        assert_eq!(ExecutionStatus::Succeeded.as_str(), "SUCCEEDED");
282        assert_eq!(ExecutionStatus::Failed.as_str(), "FAILED");
283        assert_eq!(ExecutionStatus::TimedOut.as_str(), "TIMED_OUT");
284        assert_eq!(ExecutionStatus::Aborted.as_str(), "ABORTED");
285        assert_eq!(ExecutionStatus::PendingRedrive.as_str(), "PENDING_REDRIVE");
286    }
287
288    #[test]
289    fn state_machine_arn_format() {
290        let state = StepFunctionsState::new("123456789012", "us-east-1");
291        assert_eq!(
292            state.state_machine_arn("my-sm"),
293            "arn:aws:states:us-east-1:123456789012:stateMachine:my-sm"
294        );
295    }
296
297    #[test]
298    fn execution_arn_format() {
299        let state = StepFunctionsState::new("123456789012", "us-east-1");
300        assert_eq!(
301            state.execution_arn("sm", "exec-1"),
302            "arn:aws:states:us-east-1:123456789012:execution:sm:exec-1"
303        );
304    }
305
306    #[test]
307    fn state_reset_clears_all() {
308        let mut state = StepFunctionsState::new("123456789012", "us-east-1");
309        state.state_machines.insert(
310            "x".to_string(),
311            StateMachine {
312                name: "sm".to_string(),
313                arn: "arn:aws:states:us-east-1:123:stateMachine:sm".to_string(),
314                definition: "{}".to_string(),
315                role_arn: "r".to_string(),
316                machine_type: StateMachineType::Standard,
317                status: StateMachineStatus::Active,
318                creation_date: Utc::now(),
319                update_date: Utc::now(),
320                tags: HashMap::new(),
321                revision_id: "v1".to_string(),
322                logging_configuration: None,
323                tracing_configuration: None,
324                description: String::new(),
325            },
326        );
327        state.reset();
328        assert!(state.state_machines.is_empty());
329        assert!(state.executions.is_empty());
330    }
331}