wfe_core/models/
workflow_instance.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use super::execution_pointer::ExecutionPointer;
5use super::status::{PointerStatus, WorkflowStatus};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct WorkflowInstance {
10 pub id: String,
12 pub name: String,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub root_workflow_id: Option<String>,
30 pub workflow_definition_id: String,
32 pub version: u32,
34 pub description: Option<String>,
36 pub reference: Option<String>,
38 pub execution_pointers: Vec<ExecutionPointer>,
40 pub next_execution: Option<i64>,
42 pub status: WorkflowStatus,
44 pub data: serde_json::Value,
46 pub create_time: DateTime<Utc>,
48 pub complete_time: Option<DateTime<Utc>>,
50}
51
52impl WorkflowInstance {
53 pub fn new(
54 workflow_definition_id: impl Into<String>,
55 version: u32,
56 data: serde_json::Value,
57 ) -> Self {
58 Self {
59 id: uuid::Uuid::new_v4().to_string(),
60 name: String::new(),
62 root_workflow_id: None,
66 workflow_definition_id: workflow_definition_id.into(),
67 version,
68 description: None,
69 reference: None,
70 execution_pointers: Vec::new(),
71 next_execution: Some(0),
72 status: WorkflowStatus::Runnable,
73 data,
74 create_time: Utc::now(),
75 complete_time: None,
76 }
77 }
78
79 pub fn is_branch_complete(&self, scope: &[String]) -> bool {
81 self.execution_pointers
82 .iter()
83 .filter(|p| p.scope == scope)
84 .all(|p| {
85 matches!(
86 p.status,
87 PointerStatus::Complete
88 | PointerStatus::Skipped
89 | PointerStatus::Compensated
90 | PointerStatus::Cancelled
91 | PointerStatus::Failed
92 )
93 })
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use pretty_assertions::assert_eq;
101
102 #[test]
103 fn new_instance_defaults() {
104 let instance = WorkflowInstance::new("test-workflow", 1, serde_json::json!({}));
105 assert_eq!(instance.workflow_definition_id, "test-workflow");
106 assert_eq!(instance.version, 1);
107 assert_eq!(instance.status, WorkflowStatus::Runnable);
108 assert_eq!(instance.next_execution, Some(0));
109 assert!(instance.execution_pointers.is_empty());
110 assert!(instance.complete_time.is_none());
111 }
112
113 #[test]
114 fn is_branch_complete_empty_scope_returns_true() {
115 let instance = WorkflowInstance::new("test", 1, serde_json::json!({}));
116 assert!(instance.is_branch_complete(&[]));
117 }
118
119 #[test]
120 fn is_branch_complete_all_complete() {
121 let scope = vec!["parent-1".to_string()];
122 let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({}));
123
124 let mut p1 = ExecutionPointer::new(0);
125 p1.scope = scope.clone();
126 p1.status = PointerStatus::Complete;
127
128 let mut p2 = ExecutionPointer::new(1);
129 p2.scope = scope.clone();
130 p2.status = PointerStatus::Compensated;
131
132 instance.execution_pointers = vec![p1, p2];
133 assert!(instance.is_branch_complete(&scope));
134 }
135
136 #[test]
137 fn is_branch_complete_with_active_pointer() {
138 let scope = vec!["parent-1".to_string()];
139 let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({}));
140
141 let mut p1 = ExecutionPointer::new(0);
142 p1.scope = scope.clone();
143 p1.status = PointerStatus::Complete;
144
145 let mut p2 = ExecutionPointer::new(1);
146 p2.scope = scope.clone();
147 p2.status = PointerStatus::Running;
148
149 instance.execution_pointers = vec![p1, p2];
150 assert!(!instance.is_branch_complete(&scope));
151 }
152
153 #[test]
154 fn is_branch_complete_ignores_different_scope() {
155 let scope_a = vec!["parent-a".to_string()];
156 let scope_b = vec!["parent-b".to_string()];
157 let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({}));
158
159 let mut p1 = ExecutionPointer::new(0);
160 p1.scope = scope_a.clone();
161 p1.status = PointerStatus::Complete;
162
163 let mut p2 = ExecutionPointer::new(1);
164 p2.scope = scope_b.clone();
165 p2.status = PointerStatus::Running;
166
167 instance.execution_pointers = vec![p1, p2];
168 assert!(instance.is_branch_complete(&scope_a));
169 }
170
171 #[test]
172 fn serde_round_trip() {
173 let instance = WorkflowInstance::new("my-workflow", 2, serde_json::json!({"key": "value"}));
174 let json = serde_json::to_string(&instance).unwrap();
175 let deserialized: WorkflowInstance = serde_json::from_str(&json).unwrap();
176 assert_eq!(instance.id, deserialized.id);
177 assert_eq!(
178 instance.workflow_definition_id,
179 deserialized.workflow_definition_id
180 );
181 assert_eq!(instance.version, deserialized.version);
182 assert_eq!(instance.status, deserialized.status);
183 assert_eq!(instance.data, deserialized.data);
184 }
185}