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
9use fakecloud_aws::arn::Arn;
10
11pub type SharedStepFunctionsState =
12 Arc<RwLock<fakecloud_core::multi_account::MultiRegionState<StepFunctionsState>>>;
13
14impl fakecloud_core::multi_account::AccountState for StepFunctionsState {
15 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
16 Self::new(account_id, region)
17 }
18}
19
20pub const STEPFUNCTIONS_SNAPSHOT_SCHEMA_VERSION: u32 = 3;
23
24pub type StepFunctionsSnapshot =
25 fakecloud_core::multi_account::RegionalSnapshot<StepFunctionsState>;
26
27pub fn parse_stepfunctions_snapshot(
30 bytes: &[u8],
31) -> Result<StepFunctionsSnapshot, serde_json::Error> {
32 fakecloud_core::multi_account::parse_regional_snapshot(
33 bytes,
34 STEPFUNCTIONS_SNAPSHOT_SCHEMA_VERSION,
35 |s: StepFunctionsState| {
36 let (account, region) = (s.account_id.clone(), s.region.clone());
37 fakecloud_core::multi_account::RegionalState::from_legacy(&account, ®ion, "", s)
38 },
39 )
40}
41
42impl fakecloud_core::multi_account::SplitByRegion for StepFunctionsState {
43 fn split_by_region(self, into: &mut fakecloud_core::multi_account::RegionalState<Self>) {
48 use fakecloud_aws::arn::region_of;
49 for (k, v) in self.state_machines {
50 into.region_or_default_mut(region_of(&k))
51 .state_machines
52 .insert(k, v);
53 }
54 for (k, v) in self.executions {
55 into.region_or_default_mut(region_of(&k))
56 .executions
57 .insert(k, v);
58 }
59 for (k, v) in self.activities {
60 into.region_or_default_mut(region_of(&k))
61 .activities
62 .insert(k, v);
63 }
64 for (k, v) in self.state_machine_versions {
65 into.region_or_default_mut(region_of(&k))
66 .state_machine_versions
67 .insert(k, v);
68 }
69 for (k, v) in self.state_machine_aliases {
70 into.region_or_default_mut(region_of(&k))
71 .state_machine_aliases
72 .insert(k, v);
73 }
74 for (k, v) in self.map_runs {
75 into.region_or_default_mut(region_of(&k))
76 .map_runs
77 .insert(k, v);
78 }
79 for (k, v) in self.task_tokens {
80 let region = region_of(&v.activity_arn).map(str::to_string);
81 into.region_or_default_mut(region.as_deref())
82 .task_tokens
83 .insert(k, v);
84 }
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct StepFunctionsState {
90 pub account_id: String,
91 pub region: String,
92 #[serde(default)]
94 pub state_machines: BTreeMap<String, StateMachine>,
95 #[serde(default)]
97 pub executions: BTreeMap<String, Execution>,
98 #[serde(default)]
99 pub activities: BTreeMap<String, Activity>,
100 #[serde(default)]
101 pub state_machine_versions: BTreeMap<String, StateMachineVersion>,
102 #[serde(default)]
103 pub state_machine_aliases: BTreeMap<String, StateMachineAlias>,
104 #[serde(default)]
105 pub map_runs: BTreeMap<String, MapRun>,
106 #[serde(default)]
108 pub task_tokens: BTreeMap<String, TaskTokenState>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct Activity {
113 pub name: String,
114 pub arn: String,
115 pub creation_date: DateTime<Utc>,
116 pub tags: BTreeMap<String, String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct StateMachineVersion {
121 pub state_machine_arn: String,
122 pub version: i64,
123 pub revision_id: String,
124 pub description: String,
125 pub creation_date: DateTime<Utc>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct StateMachineAlias {
130 pub name: String,
131 pub arn: String,
132 pub description: String,
133 pub routing_configuration: Vec<AliasRoute>,
134 pub creation_date: DateTime<Utc>,
135 pub update_date: DateTime<Utc>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct AliasRoute {
140 pub state_machine_version_arn: String,
141 pub weight: i32,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct MapRun {
146 pub map_run_arn: String,
147 pub execution_arn: String,
148 pub max_concurrency: i32,
149 pub tolerated_failure_percentage: f64,
150 pub tolerated_failure_count: i64,
151 pub status: String,
152 pub start_date: DateTime<Utc>,
153 pub stop_date: Option<DateTime<Utc>>,
154 #[serde(default)]
156 pub total_count: i64,
157 #[serde(default)]
159 pub succeeded_count: i64,
160 #[serde(default)]
162 pub failed_count: i64,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct TaskTokenState {
167 pub activity_arn: String,
168 pub status: String,
172 pub output: Option<String>,
173 pub error: Option<String>,
174 pub cause: Option<String>,
175 #[serde(default)]
179 pub input: Option<String>,
180 #[serde(default = "default_now")]
181 pub created_at: DateTime<Utc>,
182 #[serde(default)]
183 pub last_heartbeat_at: Option<DateTime<Utc>>,
184 #[serde(default)]
187 pub heartbeat_seconds: Option<i64>,
188 #[serde(default)]
190 pub timeout_seconds: Option<i64>,
191}
192
193fn default_now() -> DateTime<Utc> {
194 Utc::now()
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct StateMachine {
199 pub name: String,
200 pub arn: String,
201 pub definition: String,
202 pub role_arn: String,
203 pub machine_type: StateMachineType,
204 pub status: StateMachineStatus,
205 pub creation_date: DateTime<Utc>,
206 pub update_date: DateTime<Utc>,
207 pub tags: BTreeMap<String, String>,
208 pub revision_id: String,
209 pub logging_configuration: Option<Value>,
210 pub tracing_configuration: Option<Value>,
211 pub description: String,
212 #[serde(default)]
213 pub encryption_configuration: Option<Value>,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
217pub enum StateMachineType {
218 Standard,
219 Express,
220}
221
222impl StateMachineType {
223 pub fn as_str(&self) -> &'static str {
224 match self {
225 Self::Standard => "STANDARD",
226 Self::Express => "EXPRESS",
227 }
228 }
229
230 pub fn parse(s: &str) -> Option<Self> {
231 match s {
232 "STANDARD" => Some(Self::Standard),
233 "EXPRESS" => Some(Self::Express),
234 _ => None,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
240pub enum StateMachineStatus {
241 Active,
242 Deleting,
243}
244
245impl StateMachineStatus {
246 pub fn as_str(&self) -> &'static str {
247 match self {
248 Self::Active => "ACTIVE",
249 Self::Deleting => "DELETING",
250 }
251 }
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct Execution {
256 pub execution_arn: String,
257 pub state_machine_arn: String,
258 pub state_machine_name: String,
259 pub name: String,
260 pub status: ExecutionStatus,
261 pub input: Option<String>,
262 pub output: Option<String>,
263 pub start_date: DateTime<Utc>,
264 pub stop_date: Option<DateTime<Utc>>,
265 pub error: Option<String>,
266 pub cause: Option<String>,
267 pub history_events: Vec<HistoryEvent>,
268 #[serde(default)]
272 pub parent_execution_arn: Option<String>,
273 #[serde(default)]
277 pub is_sync: bool,
278 #[serde(default)]
282 pub billed_duration_ms: Option<i64>,
283 #[serde(default)]
286 pub billed_memory_mb: Option<i64>,
287 #[serde(default)]
290 pub role_arn: String,
291 #[serde(default)]
295 pub redrive_count: u32,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299pub enum ExecutionStatus {
300 Running,
301 Succeeded,
302 Failed,
303 TimedOut,
304 Aborted,
305 PendingRedrive,
306}
307
308impl ExecutionStatus {
309 pub fn as_str(&self) -> &'static str {
310 match self {
311 Self::Running => "RUNNING",
312 Self::Succeeded => "SUCCEEDED",
313 Self::Failed => "FAILED",
314 Self::TimedOut => "TIMED_OUT",
315 Self::Aborted => "ABORTED",
316 Self::PendingRedrive => "PENDING_REDRIVE",
317 }
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct HistoryEvent {
323 pub id: i64,
324 pub event_type: String,
325 pub timestamp: DateTime<Utc>,
326 pub previous_event_id: i64,
327 pub details: Value,
328}
329
330impl StepFunctionsState {
331 pub fn new(account_id: &str, region: &str) -> Self {
332 Self {
333 account_id: account_id.to_string(),
334 region: region.to_string(),
335 state_machines: BTreeMap::new(),
336 executions: BTreeMap::new(),
337 activities: BTreeMap::new(),
338 state_machine_versions: BTreeMap::new(),
339 state_machine_aliases: BTreeMap::new(),
340 map_runs: BTreeMap::new(),
341 task_tokens: BTreeMap::new(),
342 }
343 }
344
345 pub fn reset(&mut self) {
346 self.state_machines.clear();
347 self.executions.clear();
348 self.activities.clear();
349 self.state_machine_versions.clear();
350 self.state_machine_aliases.clear();
351 self.map_runs.clear();
352 self.task_tokens.clear();
353 }
354
355 pub fn state_machine_arn(&self, region: &str, name: &str) -> String {
356 state_machine_arn(region, &self.account_id, name)
357 }
358
359 pub fn execution_arn(
360 &self,
361 region: &str,
362 state_machine_name: &str,
363 execution_name: &str,
364 ) -> String {
365 execution_arn(region, &self.account_id, state_machine_name, execution_name)
366 }
367}
368
369pub fn states_arn(region: &str, account_id: &str, resource: &str) -> String {
372 Arn::regional("states", region, account_id, resource).to_string()
373}
374
375pub fn state_machine_arn(region: &str, account_id: &str, name: &str) -> String {
377 states_arn(region, account_id, &format!("stateMachine:{name}"))
378}
379
380pub fn execution_arn(
382 region: &str,
383 account_id: &str,
384 state_machine_name: &str,
385 execution_name: &str,
386) -> String {
387 states_arn(
388 region,
389 account_id,
390 &format!("execution:{state_machine_name}:{execution_name}"),
391 )
392}
393
394pub fn activity_arn(region: &str, account_id: &str, name: &str) -> String {
396 states_arn(region, account_id, &format!("activity:{name}"))
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn state_machine_type_as_str() {
405 assert_eq!(StateMachineType::Standard.as_str(), "STANDARD");
406 assert_eq!(StateMachineType::Express.as_str(), "EXPRESS");
407 }
408
409 #[test]
410 fn state_machine_type_parse() {
411 assert_eq!(
412 StateMachineType::parse("STANDARD"),
413 Some(StateMachineType::Standard)
414 );
415 assert_eq!(
416 StateMachineType::parse("EXPRESS"),
417 Some(StateMachineType::Express)
418 );
419 assert_eq!(StateMachineType::parse("bogus"), None);
420 }
421
422 #[test]
423 fn state_machine_status_as_str() {
424 assert_eq!(StateMachineStatus::Active.as_str(), "ACTIVE");
425 assert_eq!(StateMachineStatus::Deleting.as_str(), "DELETING");
426 }
427
428 #[test]
429 fn execution_status_as_str() {
430 assert_eq!(ExecutionStatus::Running.as_str(), "RUNNING");
431 assert_eq!(ExecutionStatus::Succeeded.as_str(), "SUCCEEDED");
432 assert_eq!(ExecutionStatus::Failed.as_str(), "FAILED");
433 assert_eq!(ExecutionStatus::TimedOut.as_str(), "TIMED_OUT");
434 assert_eq!(ExecutionStatus::Aborted.as_str(), "ABORTED");
435 assert_eq!(ExecutionStatus::PendingRedrive.as_str(), "PENDING_REDRIVE");
436 }
437
438 #[test]
439 fn state_machine_arn_format() {
440 let state = StepFunctionsState::new("123456789012", "us-east-1");
441 assert_eq!(
442 state.state_machine_arn("us-east-1", "my-sm"),
443 "arn:aws:states:us-east-1:123456789012:stateMachine:my-sm"
444 );
445 }
446
447 #[test]
448 fn execution_arn_format() {
449 let state = StepFunctionsState::new("123456789012", "us-east-1");
450 assert_eq!(
451 state.execution_arn("us-east-1", "sm", "exec-1"),
452 "arn:aws:states:us-east-1:123456789012:execution:sm:exec-1"
453 );
454 }
455
456 #[test]
457 fn arns_use_request_region_not_server_default() {
458 let state = StepFunctionsState::new("123456789012", "us-east-1");
461 assert_eq!(
462 state.state_machine_arn("eu-central-1", "my-sm"),
463 "arn:aws:states:eu-central-1:123456789012:stateMachine:my-sm"
464 );
465 assert_eq!(
466 state.execution_arn("eu-central-1", "sm", "exec-1"),
467 "arn:aws:states:eu-central-1:123456789012:execution:sm:exec-1"
468 );
469 }
470
471 #[test]
472 fn state_reset_clears_all() {
473 let mut state = StepFunctionsState::new("123456789012", "us-east-1");
474 state.state_machines.insert(
475 "x".to_string(),
476 StateMachine {
477 name: "sm".to_string(),
478 arn: "arn:aws:states:us-east-1:123:stateMachine:sm".to_string(),
479 definition: "{}".to_string(),
480 role_arn: "r".to_string(),
481 machine_type: StateMachineType::Standard,
482 status: StateMachineStatus::Active,
483 creation_date: Utc::now(),
484 update_date: Utc::now(),
485 tags: BTreeMap::new(),
486 revision_id: "v1".to_string(),
487 logging_configuration: None,
488 tracing_configuration: None,
489 description: String::new(),
490 encryption_configuration: None,
491 },
492 );
493 state.reset();
494 assert!(state.state_machines.is_empty());
495 assert!(state.executions.is_empty());
496 }
497}