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 deprecated: bool,
39 pub is_public: bool,
41 pub required_role: Option<&'static str>,
43}
44
45impl Default for WorkflowInfo {
46 fn default() -> Self {
47 Self {
48 name: "",
49 version: 1,
50 timeout: Duration::from_secs(86400), deprecated: false,
52 is_public: false,
53 required_role: None,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum WorkflowStatus {
61 Created,
63 Running,
65 Waiting,
67 Completed,
69 Compensating,
71 Compensated,
73 Failed,
75}
76
77impl WorkflowStatus {
78 pub fn as_str(&self) -> &'static str {
80 match self {
81 Self::Created => "created",
82 Self::Running => "running",
83 Self::Waiting => "waiting",
84 Self::Completed => "completed",
85 Self::Compensating => "compensating",
86 Self::Compensated => "compensated",
87 Self::Failed => "failed",
88 }
89 }
90
91 pub fn is_terminal(&self) -> bool {
93 matches!(self, Self::Completed | Self::Compensated | Self::Failed)
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ParseWorkflowStatusError(pub String);
99
100impl std::fmt::Display for ParseWorkflowStatusError {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 write!(f, "invalid workflow status: '{}'", self.0)
103 }
104}
105
106impl std::error::Error for ParseWorkflowStatusError {}
107
108impl FromStr for WorkflowStatus {
109 type Err = ParseWorkflowStatusError;
110
111 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
112 match s {
113 "created" => Ok(Self::Created),
114 "running" => Ok(Self::Running),
115 "waiting" => Ok(Self::Waiting),
116 "completed" => Ok(Self::Completed),
117 "compensating" => Ok(Self::Compensating),
118 "compensated" => Ok(Self::Compensated),
119 "failed" => Ok(Self::Failed),
120 _ => Err(ParseWorkflowStatusError(s.to_string())),
121 }
122 }
123}
124
125#[cfg(test)]
126#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_workflow_info_default() {
132 let info = WorkflowInfo::default();
133 assert_eq!(info.name, "");
134 assert_eq!(info.version, 1);
135 assert!(!info.deprecated);
136 }
137
138 #[test]
139 fn test_workflow_status_conversion() {
140 assert_eq!(WorkflowStatus::Running.as_str(), "running");
141 assert_eq!(WorkflowStatus::Completed.as_str(), "completed");
142 assert_eq!(WorkflowStatus::Compensating.as_str(), "compensating");
143
144 assert_eq!(
145 "running".parse::<WorkflowStatus>(),
146 Ok(WorkflowStatus::Running)
147 );
148 assert_eq!(
149 "completed".parse::<WorkflowStatus>(),
150 Ok(WorkflowStatus::Completed)
151 );
152 }
153
154 #[test]
155 fn test_workflow_status_is_terminal() {
156 assert!(!WorkflowStatus::Running.is_terminal());
157 assert!(!WorkflowStatus::Waiting.is_terminal());
158 assert!(WorkflowStatus::Completed.is_terminal());
159 assert!(WorkflowStatus::Failed.is_terminal());
160 assert!(WorkflowStatus::Compensated.is_terminal());
161 }
162}