forge_core/workflow/
traits.rs1use 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
11pub trait ForgeWorkflow: Send + Sync + 'static {
13 type Input: DeserializeOwned + Serialize + Send + Sync;
15 type Output: Serialize + Send;
17
18 fn info() -> WorkflowInfo;
20
21 fn execute(
23 ctx: &WorkflowContext,
24 input: Self::Input,
25 ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
26}
27
28#[derive(Debug, Clone)]
30pub struct WorkflowInfo {
31 pub name: &'static str,
33 pub version: u32,
35 pub timeout: Duration,
37 pub http_timeout: Option<Duration>,
39 pub deprecated: bool,
41 pub is_public: bool,
43 pub required_role: Option<&'static str>,
45}
46
47impl Default for WorkflowInfo {
48 fn default() -> Self {
49 Self {
50 name: "",
51 version: 1,
52 timeout: Duration::from_secs(86400), http_timeout: None,
54 deprecated: false,
55 is_public: false,
56 required_role: None,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum WorkflowStatus {
64 Created,
66 Running,
68 Waiting,
70 Completed,
72 Compensating,
74 Compensated,
76 Failed,
78}
79
80impl WorkflowStatus {
81 pub fn as_str(&self) -> &'static str {
83 match self {
84 Self::Created => "created",
85 Self::Running => "running",
86 Self::Waiting => "waiting",
87 Self::Completed => "completed",
88 Self::Compensating => "compensating",
89 Self::Compensated => "compensated",
90 Self::Failed => "failed",
91 }
92 }
93
94 pub fn is_terminal(&self) -> bool {
96 matches!(self, Self::Completed | Self::Compensated | Self::Failed)
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ParseWorkflowStatusError(pub String);
102
103impl std::fmt::Display for ParseWorkflowStatusError {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(f, "invalid workflow status: '{}'", self.0)
106 }
107}
108
109impl std::error::Error for ParseWorkflowStatusError {}
110
111impl FromStr for WorkflowStatus {
112 type Err = ParseWorkflowStatusError;
113
114 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
115 match s {
116 "created" => Ok(Self::Created),
117 "running" => Ok(Self::Running),
118 "waiting" => Ok(Self::Waiting),
119 "completed" => Ok(Self::Completed),
120 "compensating" => Ok(Self::Compensating),
121 "compensated" => Ok(Self::Compensated),
122 "failed" => Ok(Self::Failed),
123 _ => Err(ParseWorkflowStatusError(s.to_string())),
124 }
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_workflow_info_default() {
135 let info = WorkflowInfo::default();
136 assert_eq!(info.name, "");
137 assert_eq!(info.version, 1);
138 assert!(!info.deprecated);
139 }
140
141 #[test]
142 fn test_workflow_status_conversion() {
143 assert_eq!(WorkflowStatus::Running.as_str(), "running");
144 assert_eq!(WorkflowStatus::Completed.as_str(), "completed");
145 assert_eq!(WorkflowStatus::Compensating.as_str(), "compensating");
146
147 assert_eq!(
148 "running".parse::<WorkflowStatus>(),
149 Ok(WorkflowStatus::Running)
150 );
151 assert_eq!(
152 "completed".parse::<WorkflowStatus>(),
153 Ok(WorkflowStatus::Completed)
154 );
155 }
156
157 #[test]
158 fn test_workflow_status_is_terminal() {
159 assert!(!WorkflowStatus::Running.is_terminal());
160 assert!(!WorkflowStatus::Waiting.is_terminal());
161 assert!(WorkflowStatus::Completed.is_terminal());
162 assert!(WorkflowStatus::Failed.is_terminal());
163 assert!(WorkflowStatus::Compensated.is_terminal());
164 }
165}