1use std::collections::BTreeMap;
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 #[serde(default)]
35 pub state_machines: BTreeMap<String, StateMachine>,
36 #[serde(default)]
38 pub executions: BTreeMap<String, Execution>,
39 #[serde(default)]
40 pub activities: BTreeMap<String, Activity>,
41 #[serde(default)]
42 pub state_machine_versions: BTreeMap<String, StateMachineVersion>,
43 #[serde(default)]
44 pub state_machine_aliases: BTreeMap<String, StateMachineAlias>,
45 #[serde(default)]
46 pub map_runs: BTreeMap<String, MapRun>,
47 #[serde(default)]
49 pub task_tokens: BTreeMap<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: BTreeMap<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 #[serde(default)]
97 pub total_count: i64,
98 #[serde(default)]
100 pub succeeded_count: i64,
101 #[serde(default)]
103 pub failed_count: i64,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct TaskTokenState {
108 pub activity_arn: String,
109 pub status: String,
113 pub output: Option<String>,
114 pub error: Option<String>,
115 pub cause: Option<String>,
116 #[serde(default)]
120 pub input: Option<String>,
121 #[serde(default = "default_now")]
122 pub created_at: DateTime<Utc>,
123 #[serde(default)]
124 pub last_heartbeat_at: Option<DateTime<Utc>>,
125 #[serde(default)]
128 pub heartbeat_seconds: Option<i64>,
129 #[serde(default)]
131 pub timeout_seconds: Option<i64>,
132}
133
134fn default_now() -> DateTime<Utc> {
135 Utc::now()
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct StateMachine {
140 pub name: String,
141 pub arn: String,
142 pub definition: String,
143 pub role_arn: String,
144 pub machine_type: StateMachineType,
145 pub status: StateMachineStatus,
146 pub creation_date: DateTime<Utc>,
147 pub update_date: DateTime<Utc>,
148 pub tags: BTreeMap<String, String>,
149 pub revision_id: String,
150 pub logging_configuration: Option<Value>,
151 pub tracing_configuration: Option<Value>,
152 pub description: String,
153 #[serde(default)]
154 pub encryption_configuration: Option<Value>,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
158pub enum StateMachineType {
159 Standard,
160 Express,
161}
162
163impl StateMachineType {
164 pub fn as_str(&self) -> &'static str {
165 match self {
166 Self::Standard => "STANDARD",
167 Self::Express => "EXPRESS",
168 }
169 }
170
171 pub fn parse(s: &str) -> Option<Self> {
172 match s {
173 "STANDARD" => Some(Self::Standard),
174 "EXPRESS" => Some(Self::Express),
175 _ => None,
176 }
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum StateMachineStatus {
182 Active,
183 Deleting,
184}
185
186impl StateMachineStatus {
187 pub fn as_str(&self) -> &'static str {
188 match self {
189 Self::Active => "ACTIVE",
190 Self::Deleting => "DELETING",
191 }
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct Execution {
197 pub execution_arn: String,
198 pub state_machine_arn: String,
199 pub state_machine_name: String,
200 pub name: String,
201 pub status: ExecutionStatus,
202 pub input: Option<String>,
203 pub output: Option<String>,
204 pub start_date: DateTime<Utc>,
205 pub stop_date: Option<DateTime<Utc>>,
206 pub error: Option<String>,
207 pub cause: Option<String>,
208 pub history_events: Vec<HistoryEvent>,
209 #[serde(default)]
213 pub parent_execution_arn: Option<String>,
214 #[serde(default)]
218 pub is_sync: bool,
219 #[serde(default)]
223 pub billed_duration_ms: Option<i64>,
224 #[serde(default)]
227 pub billed_memory_mb: Option<i64>,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
231pub enum ExecutionStatus {
232 Running,
233 Succeeded,
234 Failed,
235 TimedOut,
236 Aborted,
237 PendingRedrive,
238}
239
240impl ExecutionStatus {
241 pub fn as_str(&self) -> &'static str {
242 match self {
243 Self::Running => "RUNNING",
244 Self::Succeeded => "SUCCEEDED",
245 Self::Failed => "FAILED",
246 Self::TimedOut => "TIMED_OUT",
247 Self::Aborted => "ABORTED",
248 Self::PendingRedrive => "PENDING_REDRIVE",
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct HistoryEvent {
255 pub id: i64,
256 pub event_type: String,
257 pub timestamp: DateTime<Utc>,
258 pub previous_event_id: i64,
259 pub details: Value,
260}
261
262impl StepFunctionsState {
263 pub fn new(account_id: &str, region: &str) -> Self {
264 Self {
265 account_id: account_id.to_string(),
266 region: region.to_string(),
267 state_machines: BTreeMap::new(),
268 executions: BTreeMap::new(),
269 activities: BTreeMap::new(),
270 state_machine_versions: BTreeMap::new(),
271 state_machine_aliases: BTreeMap::new(),
272 map_runs: BTreeMap::new(),
273 task_tokens: BTreeMap::new(),
274 }
275 }
276
277 pub fn reset(&mut self) {
278 self.state_machines.clear();
279 self.executions.clear();
280 self.activities.clear();
281 self.state_machine_versions.clear();
282 self.state_machine_aliases.clear();
283 self.map_runs.clear();
284 self.task_tokens.clear();
285 }
286
287 pub fn state_machine_arn(&self, region: &str, name: &str) -> String {
288 format!(
289 "arn:aws:states:{}:{}:stateMachine:{}",
290 region, self.account_id, name
291 )
292 }
293
294 pub fn execution_arn(
295 &self,
296 region: &str,
297 state_machine_name: &str,
298 execution_name: &str,
299 ) -> String {
300 format!(
301 "arn:aws:states:{}:{}:execution:{}:{}",
302 region, self.account_id, state_machine_name, execution_name
303 )
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[test]
312 fn state_machine_type_as_str() {
313 assert_eq!(StateMachineType::Standard.as_str(), "STANDARD");
314 assert_eq!(StateMachineType::Express.as_str(), "EXPRESS");
315 }
316
317 #[test]
318 fn state_machine_type_parse() {
319 assert_eq!(
320 StateMachineType::parse("STANDARD"),
321 Some(StateMachineType::Standard)
322 );
323 assert_eq!(
324 StateMachineType::parse("EXPRESS"),
325 Some(StateMachineType::Express)
326 );
327 assert_eq!(StateMachineType::parse("bogus"), None);
328 }
329
330 #[test]
331 fn state_machine_status_as_str() {
332 assert_eq!(StateMachineStatus::Active.as_str(), "ACTIVE");
333 assert_eq!(StateMachineStatus::Deleting.as_str(), "DELETING");
334 }
335
336 #[test]
337 fn execution_status_as_str() {
338 assert_eq!(ExecutionStatus::Running.as_str(), "RUNNING");
339 assert_eq!(ExecutionStatus::Succeeded.as_str(), "SUCCEEDED");
340 assert_eq!(ExecutionStatus::Failed.as_str(), "FAILED");
341 assert_eq!(ExecutionStatus::TimedOut.as_str(), "TIMED_OUT");
342 assert_eq!(ExecutionStatus::Aborted.as_str(), "ABORTED");
343 assert_eq!(ExecutionStatus::PendingRedrive.as_str(), "PENDING_REDRIVE");
344 }
345
346 #[test]
347 fn state_machine_arn_format() {
348 let state = StepFunctionsState::new("123456789012", "us-east-1");
349 assert_eq!(
350 state.state_machine_arn("us-east-1", "my-sm"),
351 "arn:aws:states:us-east-1:123456789012:stateMachine:my-sm"
352 );
353 }
354
355 #[test]
356 fn execution_arn_format() {
357 let state = StepFunctionsState::new("123456789012", "us-east-1");
358 assert_eq!(
359 state.execution_arn("us-east-1", "sm", "exec-1"),
360 "arn:aws:states:us-east-1:123456789012:execution:sm:exec-1"
361 );
362 }
363
364 #[test]
365 fn arns_use_request_region_not_server_default() {
366 let state = StepFunctionsState::new("123456789012", "us-east-1");
369 assert_eq!(
370 state.state_machine_arn("eu-central-1", "my-sm"),
371 "arn:aws:states:eu-central-1:123456789012:stateMachine:my-sm"
372 );
373 assert_eq!(
374 state.execution_arn("eu-central-1", "sm", "exec-1"),
375 "arn:aws:states:eu-central-1:123456789012:execution:sm:exec-1"
376 );
377 }
378
379 #[test]
380 fn state_reset_clears_all() {
381 let mut state = StepFunctionsState::new("123456789012", "us-east-1");
382 state.state_machines.insert(
383 "x".to_string(),
384 StateMachine {
385 name: "sm".to_string(),
386 arn: "arn:aws:states:us-east-1:123:stateMachine:sm".to_string(),
387 definition: "{}".to_string(),
388 role_arn: "r".to_string(),
389 machine_type: StateMachineType::Standard,
390 status: StateMachineStatus::Active,
391 creation_date: Utc::now(),
392 update_date: Utc::now(),
393 tags: BTreeMap::new(),
394 revision_id: "v1".to_string(),
395 logging_configuration: None,
396 tracing_configuration: None,
397 description: String::new(),
398 encryption_configuration: None,
399 },
400 );
401 state.reset();
402 assert!(state.state_machines.is_empty());
403 assert!(state.executions.is_empty());
404 }
405}