forge_core/workflow/
traits.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::str::FromStr;
4use std::time::Duration;
5
6use serde::{Serialize, de::DeserializeOwned};
7
8use super::context::WorkflowContext;
9use crate::Result;
10
11/// Trait for workflow handlers.
12pub trait ForgeWorkflow: Send + Sync + 'static {
13    /// Input type for the workflow.
14    type Input: DeserializeOwned + Serialize + Send + Sync;
15    /// Output type for the workflow.
16    type Output: Serialize + Send;
17
18    /// Get workflow metadata.
19    fn info() -> WorkflowInfo;
20
21    /// Execute the workflow.
22    fn execute(
23        ctx: &WorkflowContext,
24        input: Self::Input,
25    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
26}
27
28/// Workflow metadata.
29#[derive(Debug, Clone)]
30pub struct WorkflowInfo {
31    /// Workflow name.
32    pub name: &'static str,
33    /// Workflow version.
34    pub version: u32,
35    /// Default timeout for the entire workflow.
36    pub timeout: Duration,
37    /// Whether the workflow is deprecated.
38    pub deprecated: bool,
39}
40
41impl Default for WorkflowInfo {
42    fn default() -> Self {
43        Self {
44            name: "",
45            version: 1,
46            timeout: Duration::from_secs(86400), // 24 hours
47            deprecated: false,
48        }
49    }
50}
51
52/// Workflow execution status.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum WorkflowStatus {
55    /// Workflow is created but not started.
56    Created,
57    /// Workflow is actively running.
58    Running,
59    /// Workflow is waiting for an external event.
60    Waiting,
61    /// Workflow completed successfully.
62    Completed,
63    /// Workflow failed and is running compensation.
64    Compensating,
65    /// Workflow compensation completed.
66    Compensated,
67    /// Workflow failed (compensation also failed or not available).
68    Failed,
69}
70
71impl WorkflowStatus {
72    /// Convert to string for database storage.
73    pub fn as_str(&self) -> &'static str {
74        match self {
75            Self::Created => "created",
76            Self::Running => "running",
77            Self::Waiting => "waiting",
78            Self::Completed => "completed",
79            Self::Compensating => "compensating",
80            Self::Compensated => "compensated",
81            Self::Failed => "failed",
82        }
83    }
84
85    /// Check if the workflow is terminal (no longer running).
86    pub fn is_terminal(&self) -> bool {
87        matches!(self, Self::Completed | Self::Compensated | Self::Failed)
88    }
89}
90
91impl FromStr for WorkflowStatus {
92    type Err = std::convert::Infallible;
93
94    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
95        Ok(match s {
96            "created" => Self::Created,
97            "running" => Self::Running,
98            "waiting" => Self::Waiting,
99            "completed" => Self::Completed,
100            "compensating" => Self::Compensating,
101            "compensated" => Self::Compensated,
102            "failed" => Self::Failed,
103            _ => Self::Created,
104        })
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_workflow_info_default() {
114        let info = WorkflowInfo::default();
115        assert_eq!(info.name, "");
116        assert_eq!(info.version, 1);
117        assert!(!info.deprecated);
118    }
119
120    #[test]
121    fn test_workflow_status_conversion() {
122        assert_eq!(WorkflowStatus::Running.as_str(), "running");
123        assert_eq!(WorkflowStatus::Completed.as_str(), "completed");
124        assert_eq!(WorkflowStatus::Compensating.as_str(), "compensating");
125
126        assert_eq!(
127            "running".parse::<WorkflowStatus>(),
128            Ok(WorkflowStatus::Running)
129        );
130        assert_eq!(
131            "completed".parse::<WorkflowStatus>(),
132            Ok(WorkflowStatus::Completed)
133        );
134    }
135
136    #[test]
137    fn test_workflow_status_is_terminal() {
138        assert!(!WorkflowStatus::Running.is_terminal());
139        assert!(!WorkflowStatus::Waiting.is_terminal());
140        assert!(WorkflowStatus::Completed.is_terminal());
141        assert!(WorkflowStatus::Failed.is_terminal());
142        assert!(WorkflowStatus::Compensated.is_terminal());
143    }
144}