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
254tokio::task_local! {
255 pub(crate) static STARTED_BY_EXECUTION: String;
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Execution {
262 pub execution_arn: String,
263 pub state_machine_arn: String,
264 pub state_machine_name: String,
265 pub name: String,
266 pub status: ExecutionStatus,
267 pub input: Option<String>,
268 pub output: Option<String>,
269 pub start_date: DateTime<Utc>,
270 pub stop_date: Option<DateTime<Utc>>,
271 pub error: Option<String>,
272 pub cause: Option<String>,
273 pub history_events: Vec<HistoryEvent>,
274 #[serde(default)]
278 pub parent_execution_arn: Option<String>,
279 #[serde(default)]
283 pub is_sync: bool,
284 #[serde(default)]
288 pub billed_duration_ms: Option<i64>,
289 #[serde(default)]
292 pub billed_memory_mb: Option<i64>,
293 #[serde(default)]
296 pub role_arn: String,
297 #[serde(default)]
301 pub redrive_count: u32,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305pub enum ExecutionStatus {
306 Running,
307 Succeeded,
308 Failed,
309 TimedOut,
310 Aborted,
311 PendingRedrive,
312}
313
314impl ExecutionStatus {
315 pub fn as_str(&self) -> &'static str {
316 match self {
317 Self::Running => "RUNNING",
318 Self::Succeeded => "SUCCEEDED",
319 Self::Failed => "FAILED",
320 Self::TimedOut => "TIMED_OUT",
321 Self::Aborted => "ABORTED",
322 Self::PendingRedrive => "PENDING_REDRIVE",
323 }
324 }
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct HistoryEvent {
329 pub id: i64,
330 pub event_type: String,
331 pub timestamp: DateTime<Utc>,
332 pub previous_event_id: i64,
333 pub details: Value,
334}
335
336impl StepFunctionsState {
337 pub fn new(account_id: &str, region: &str) -> Self {
338 Self {
339 account_id: account_id.to_string(),
340 region: region.to_string(),
341 state_machines: BTreeMap::new(),
342 executions: BTreeMap::new(),
343 activities: BTreeMap::new(),
344 state_machine_versions: BTreeMap::new(),
345 state_machine_aliases: BTreeMap::new(),
346 map_runs: BTreeMap::new(),
347 task_tokens: BTreeMap::new(),
348 }
349 }
350
351 pub fn reset(&mut self) {
352 self.state_machines.clear();
353 self.executions.clear();
354 self.activities.clear();
355 self.state_machine_versions.clear();
356 self.state_machine_aliases.clear();
357 self.map_runs.clear();
358 self.task_tokens.clear();
359 }
360
361 pub fn state_machine_arn(&self, region: &str, name: &str) -> String {
362 state_machine_arn(region, &self.account_id, name)
363 }
364
365 pub fn execution_arn(
366 &self,
367 region: &str,
368 state_machine_name: &str,
369 execution_name: &str,
370 ) -> String {
371 execution_arn(region, &self.account_id, state_machine_name, execution_name)
372 }
373
374 pub fn new_execution_arn(
378 &self,
379 region: &str,
380 machine_type: StateMachineType,
381 state_machine_name: &str,
382 execution_name: &str,
383 ) -> String {
384 match machine_type {
385 StateMachineType::Standard => {
386 self.execution_arn(region, state_machine_name, execution_name)
387 }
388 StateMachineType::Express => {
389 express_execution_arn(region, &self.account_id, state_machine_name, execution_name)
390 }
391 }
392 }
393}
394
395pub fn states_arn(region: &str, account_id: &str, resource: &str) -> String {
398 Arn::regional("states", region, account_id, resource).to_string()
399}
400
401pub fn state_machine_arn(region: &str, account_id: &str, name: &str) -> String {
403 states_arn(region, account_id, &format!("stateMachine:{name}"))
404}
405
406pub fn execution_arn(
408 region: &str,
409 account_id: &str,
410 state_machine_name: &str,
411 execution_name: &str,
412) -> String {
413 states_arn(
414 region,
415 account_id,
416 &format!("execution:{state_machine_name}:{execution_name}"),
417 )
418}
419
420pub fn express_execution_arn(
426 region: &str,
427 account_id: &str,
428 state_machine_name: &str,
429 execution_name: &str,
430) -> String {
431 states_arn(
432 region,
433 account_id,
434 &format!(
435 "express:{state_machine_name}:{execution_name}:{}",
436 uuid::Uuid::new_v4()
437 ),
438 )
439}
440
441pub fn activity_arn(region: &str, account_id: &str, name: &str) -> String {
443 states_arn(region, account_id, &format!("activity:{name}"))
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn state_machine_type_as_str() {
452 assert_eq!(StateMachineType::Standard.as_str(), "STANDARD");
453 assert_eq!(StateMachineType::Express.as_str(), "EXPRESS");
454 }
455
456 #[test]
457 fn state_machine_type_parse() {
458 assert_eq!(
459 StateMachineType::parse("STANDARD"),
460 Some(StateMachineType::Standard)
461 );
462 assert_eq!(
463 StateMachineType::parse("EXPRESS"),
464 Some(StateMachineType::Express)
465 );
466 assert_eq!(StateMachineType::parse("bogus"), None);
467 }
468
469 #[test]
470 fn state_machine_status_as_str() {
471 assert_eq!(StateMachineStatus::Active.as_str(), "ACTIVE");
472 assert_eq!(StateMachineStatus::Deleting.as_str(), "DELETING");
473 }
474
475 #[test]
476 fn execution_status_as_str() {
477 assert_eq!(ExecutionStatus::Running.as_str(), "RUNNING");
478 assert_eq!(ExecutionStatus::Succeeded.as_str(), "SUCCEEDED");
479 assert_eq!(ExecutionStatus::Failed.as_str(), "FAILED");
480 assert_eq!(ExecutionStatus::TimedOut.as_str(), "TIMED_OUT");
481 assert_eq!(ExecutionStatus::Aborted.as_str(), "ABORTED");
482 assert_eq!(ExecutionStatus::PendingRedrive.as_str(), "PENDING_REDRIVE");
483 }
484
485 #[test]
486 fn state_machine_arn_format() {
487 let state = StepFunctionsState::new("123456789012", "us-east-1");
488 assert_eq!(
489 state.state_machine_arn("us-east-1", "my-sm"),
490 "arn:aws:states:us-east-1:123456789012:stateMachine:my-sm"
491 );
492 }
493
494 #[test]
495 fn execution_arn_format() {
496 let state = StepFunctionsState::new("123456789012", "us-east-1");
497 assert_eq!(
498 state.execution_arn("us-east-1", "sm", "exec-1"),
499 "arn:aws:states:us-east-1:123456789012:execution:sm:exec-1"
500 );
501 }
502
503 #[test]
504 fn arns_use_request_region_not_server_default() {
505 let state = StepFunctionsState::new("123456789012", "us-east-1");
508 assert_eq!(
509 state.state_machine_arn("eu-central-1", "my-sm"),
510 "arn:aws:states:eu-central-1:123456789012:stateMachine:my-sm"
511 );
512 assert_eq!(
513 state.execution_arn("eu-central-1", "sm", "exec-1"),
514 "arn:aws:states:eu-central-1:123456789012:execution:sm:exec-1"
515 );
516 }
517
518 #[test]
519 fn state_reset_clears_all() {
520 let mut state = StepFunctionsState::new("123456789012", "us-east-1");
521 state.state_machines.insert(
522 "x".to_string(),
523 StateMachine {
524 name: "sm".to_string(),
525 arn: "arn:aws:states:us-east-1:123:stateMachine:sm".to_string(),
526 definition: "{}".to_string(),
527 role_arn: "r".to_string(),
528 machine_type: StateMachineType::Standard,
529 status: StateMachineStatus::Active,
530 creation_date: Utc::now(),
531 update_date: Utc::now(),
532 tags: BTreeMap::new(),
533 revision_id: "v1".to_string(),
534 logging_configuration: None,
535 tracing_configuration: None,
536 description: String::new(),
537 encryption_configuration: None,
538 },
539 );
540 state.reset();
541 assert!(state.state_machines.is_empty());
542 assert!(state.executions.is_empty());
543 }
544}