use std::collections::BTreeMap;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use fakecloud_core::multi_account::{AccountState, MultiAccountState};
pub const SWF_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Domain {
pub name: String,
pub status: String,
#[serde(default)]
pub description: Option<String>,
pub retention_days: String,
pub arn: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeRecord {
pub name: String,
pub version: String,
pub status: String,
#[serde(default)]
pub description: Option<String>,
pub creation_date: f64,
#[serde(default)]
pub deprecation_date: Option<f64>,
#[serde(default)]
pub configuration: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ActivityState {
Scheduled,
Started,
Closed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityRecord {
pub activity_id: String,
pub activity_type_name: String,
pub activity_type_version: String,
#[serde(default)]
pub input: Option<String>,
pub task_list: String,
pub scheduled_event_id: i64,
#[serde(default)]
pub started_event_id: Option<i64>,
pub state: ActivityState,
pub task_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Execution {
pub domain: String,
pub workflow_id: String,
pub run_id: String,
pub workflow_type_name: String,
pub workflow_type_version: String,
pub task_list: String,
#[serde(default)]
pub task_priority: Option<String>,
pub status: String,
#[serde(default)]
pub close_status: Option<String>,
pub start_timestamp: f64,
#[serde(default)]
pub close_timestamp: Option<f64>,
#[serde(default)]
pub tag_list: Vec<String>,
#[serde(default)]
pub input: Option<String>,
pub child_policy: String,
pub task_start_to_close_timeout: String,
pub execution_start_to_close_timeout: String,
#[serde(default)]
pub lambda_role: Option<String>,
#[serde(default)]
pub cancel_requested: bool,
#[serde(default)]
pub latest_execution_context: Option<String>,
#[serde(default)]
pub events: Vec<Value>,
pub next_event_id: i64,
#[serde(default)]
pub decision_scheduled: bool,
#[serde(default)]
pub decision_scheduled_event_id: Option<i64>,
#[serde(default)]
pub decision_started_event_id: Option<i64>,
#[serde(default)]
pub previous_started_event_id: i64,
#[serde(default)]
pub decision_task_token: Option<String>,
#[serde(default)]
pub activities: Vec<ActivityRecord>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SwfData {
#[serde(default)]
pub account_id: String,
#[serde(default)]
pub region: String,
#[serde(default)]
pub domains: BTreeMap<String, Domain>,
#[serde(default)]
pub activity_types: BTreeMap<String, TypeRecord>,
#[serde(default)]
pub workflow_types: BTreeMap<String, TypeRecord>,
#[serde(default)]
pub executions: BTreeMap<String, Execution>,
#[serde(default)]
pub decision_tokens: BTreeMap<String, String>,
#[serde(default)]
pub activity_tokens: BTreeMap<String, ActivityRef>,
#[serde(default)]
pub tags: BTreeMap<String, BTreeMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityRef {
pub run_id: String,
pub activity_id: String,
}
impl AccountState for SwfData {
fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
Self {
account_id: account_id.to_string(),
region: region.to_string(),
..Default::default()
}
}
}
pub type SharedSwfState = Arc<RwLock<MultiAccountState<SwfData>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct SwfSnapshot {
pub schema_version: u32,
pub accounts: MultiAccountState<SwfData>,
}
impl SwfData {
pub fn type_map_key(domain: &str, name: &str, version: &str) -> String {
format!("{domain}\u{1}{name}\u{1}{version}")
}
pub fn find_execution<'a>(
&'a self,
workflow_id: &str,
run_id: Option<&str>,
) -> Option<&'a Execution> {
if let Some(rid) = run_id.filter(|r| !r.is_empty()) {
return self
.executions
.get(rid)
.filter(|e| e.workflow_id == workflow_id);
}
self.executions
.values()
.filter(|e| e.workflow_id == workflow_id)
.max_by(|a, b| {
(a.status == "OPEN", a.start_timestamp)
.partial_cmp(&(b.status == "OPEN", b.start_timestamp))
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn find_execution_mut<'a>(
&'a mut self,
workflow_id: &str,
run_id: Option<&str>,
) -> Option<&'a mut Execution> {
let rid = match run_id.filter(|r| !r.is_empty()) {
Some(rid) => Some(rid.to_string()),
None => self
.executions
.values()
.filter(|e| e.workflow_id == workflow_id)
.max_by(|a, b| {
(a.status == "OPEN", a.start_timestamp)
.partial_cmp(&(b.status == "OPEN", b.start_timestamp))
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|e| e.run_id.clone()),
}?;
self.executions
.get_mut(&rid)
.filter(|e| e.workflow_id == workflow_id)
}
}
impl Execution {
pub fn alloc_event_id(&mut self) -> i64 {
let id = self.next_event_id;
self.next_event_id += 1;
id
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_execution_prefers_open() {
let mut d = SwfData::new_for_account("000000000000", "us-east-1", "");
d.executions
.insert("run-old".into(), exec("wf-1", "run-old", "CLOSED", 1.0));
d.executions
.insert("run-new".into(), exec("wf-1", "run-new", "OPEN", 2.0));
assert_eq!(d.find_execution("wf-1", None).unwrap().run_id, "run-new");
assert_eq!(
d.find_execution("wf-1", Some("run-old")).unwrap().run_id,
"run-old"
);
}
fn exec(wf: &str, run: &str, status: &str, ts: f64) -> Execution {
Execution {
domain: "d".into(),
workflow_id: wf.into(),
run_id: run.into(),
workflow_type_name: "t".into(),
workflow_type_version: "1".into(),
task_list: "tl".into(),
task_priority: None,
status: status.into(),
close_status: None,
start_timestamp: ts,
close_timestamp: None,
tag_list: vec![],
input: None,
child_policy: "TERMINATE".into(),
task_start_to_close_timeout: "10".into(),
execution_start_to_close_timeout: "3600".into(),
lambda_role: None,
cancel_requested: false,
latest_execution_context: None,
events: vec![],
next_event_id: 1,
decision_scheduled: false,
decision_scheduled_event_id: None,
decision_started_event_id: None,
previous_started_event_id: 0,
decision_task_token: None,
activities: vec![],
}
}
}