Skip to main content

forge_runtime/workflow/
state.rs

1use chrono::{DateTime, Utc};
2use forge_core::workflow::{StepStatus, WorkflowStatus};
3use uuid::Uuid;
4
5/// A workflow run record in the database.
6#[derive(Debug, Clone)]
7pub struct WorkflowRecord {
8    /// Unique workflow run ID.
9    pub id: Uuid,
10    /// Workflow name.
11    pub workflow_name: String,
12    /// Workflow version.
13    pub version: u32,
14    /// Principal that started the workflow.
15    pub owner_subject: Option<String>,
16    /// Input data as JSON.
17    pub input: serde_json::Value,
18    /// Output data as JSON (if completed).
19    pub output: Option<serde_json::Value>,
20    /// Current status.
21    pub status: WorkflowStatus,
22    /// Current step name.
23    pub current_step: Option<String>,
24    /// Step results as JSON map.
25    pub step_results: serde_json::Value,
26    /// When the workflow started.
27    pub started_at: DateTime<Utc>,
28    /// When the workflow completed.
29    pub completed_at: Option<DateTime<Utc>>,
30    /// Error message if failed.
31    pub error: Option<String>,
32    /// Trace ID for distributed tracing.
33    pub trace_id: Option<String>,
34}
35
36impl WorkflowRecord {
37    /// Create a new workflow record.
38    pub fn new(
39        workflow_name: impl Into<String>,
40        version: u32,
41        input: serde_json::Value,
42        owner_subject: Option<String>,
43    ) -> Self {
44        Self {
45            id: Uuid::new_v4(),
46            workflow_name: workflow_name.into(),
47            version,
48            owner_subject,
49            input,
50            output: None,
51            status: WorkflowStatus::Created,
52            current_step: None,
53            step_results: serde_json::json!({}),
54            started_at: Utc::now(),
55            completed_at: None,
56            error: None,
57            trace_id: None,
58        }
59    }
60
61    /// Set trace ID.
62    pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
63        self.trace_id = Some(trace_id.into());
64        self
65    }
66
67    /// Mark as running.
68    pub fn start(&mut self) {
69        self.status = WorkflowStatus::Running;
70    }
71
72    /// Mark as completed.
73    pub fn complete(&mut self, output: serde_json::Value) {
74        self.status = WorkflowStatus::Completed;
75        self.output = Some(output);
76        self.completed_at = Some(Utc::now());
77    }
78
79    /// Mark as failed.
80    pub fn fail(&mut self, error: impl Into<String>) {
81        self.status = WorkflowStatus::Failed;
82        self.error = Some(error.into());
83        self.completed_at = Some(Utc::now());
84    }
85
86    /// Mark as compensating.
87    pub fn compensating(&mut self) {
88        self.status = WorkflowStatus::Compensating;
89    }
90
91    /// Mark as compensated.
92    pub fn compensated(&mut self) {
93        self.status = WorkflowStatus::Compensated;
94        self.completed_at = Some(Utc::now());
95    }
96
97    /// Update current step.
98    pub fn set_current_step(&mut self, step: impl Into<String>) {
99        self.current_step = Some(step.into());
100    }
101
102    /// Add step result.
103    pub fn add_step_result(&mut self, step_name: &str, result: serde_json::Value) {
104        if let Some(obj) = self.step_results.as_object_mut() {
105            obj.insert(step_name.to_string(), result);
106        }
107    }
108}
109
110/// A workflow step record in the database.
111#[derive(Debug, Clone)]
112pub struct WorkflowStepRecord {
113    /// Step record ID.
114    pub id: Uuid,
115    /// Parent workflow run ID.
116    pub workflow_run_id: Uuid,
117    /// Step name.
118    pub step_name: String,
119    /// Step status.
120    pub status: StepStatus,
121    /// Step result as JSON.
122    pub result: Option<serde_json::Value>,
123    /// Error message if failed.
124    pub error: Option<String>,
125    /// When the step started.
126    pub started_at: Option<DateTime<Utc>>,
127    /// When the step completed.
128    pub completed_at: Option<DateTime<Utc>>,
129}
130
131impl WorkflowStepRecord {
132    /// Create a new step record.
133    pub fn new(workflow_run_id: Uuid, step_name: impl Into<String>) -> Self {
134        Self {
135            id: Uuid::new_v4(),
136            workflow_run_id,
137            step_name: step_name.into(),
138            status: StepStatus::Pending,
139            result: None,
140            error: None,
141            started_at: None,
142            completed_at: None,
143        }
144    }
145
146    /// Mark as running.
147    pub fn start(&mut self) {
148        self.status = StepStatus::Running;
149        self.started_at = Some(Utc::now());
150    }
151
152    /// Mark as completed.
153    pub fn complete(&mut self, result: serde_json::Value) {
154        self.status = StepStatus::Completed;
155        self.result = Some(result);
156        self.completed_at = Some(Utc::now());
157    }
158
159    /// Mark as failed.
160    pub fn fail(&mut self, error: impl Into<String>) {
161        self.status = StepStatus::Failed;
162        self.error = Some(error.into());
163        self.completed_at = Some(Utc::now());
164    }
165
166    /// Mark as compensated.
167    pub fn compensate(&mut self) {
168        self.status = StepStatus::Compensated;
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_workflow_record_creation() {
178        let record = WorkflowRecord::new("test_workflow", 1, serde_json::json!({}), None);
179        assert_eq!(record.workflow_name, "test_workflow");
180        assert_eq!(record.version, 1);
181        assert_eq!(record.status, WorkflowStatus::Created);
182    }
183
184    #[test]
185    fn test_workflow_record_transitions() {
186        let mut record = WorkflowRecord::new("test", 1, serde_json::json!({}), None);
187
188        record.start();
189        assert_eq!(record.status, WorkflowStatus::Running);
190
191        record.complete(serde_json::json!({"result": "ok"}));
192        assert_eq!(record.status, WorkflowStatus::Completed);
193        assert!(record.completed_at.is_some());
194    }
195
196    #[test]
197    fn test_workflow_step_record() {
198        let workflow_id = Uuid::new_v4();
199        let mut step = WorkflowStepRecord::new(workflow_id, "step1");
200
201        assert_eq!(step.step_name, "step1");
202        assert_eq!(step.status, StepStatus::Pending);
203
204        step.start();
205        assert_eq!(step.status, StepStatus::Running);
206
207        step.complete(serde_json::json!({}));
208        assert_eq!(step.status, StepStatus::Completed);
209    }
210}