1use crate::WorkflowError;
4use crate::context::WorkflowContext;
5
6#[async_trait::async_trait]
8pub trait Step: Send + Sync {
9 fn id(&self) -> &str;
11
12 async fn execute(&self, ctx: &mut WorkflowContext) -> Result<StepOutput, WorkflowError>;
14}
15
16#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
18pub struct StepOutput {
19 value: String,
20 metadata: Option<serde_json::Value>,
21 skipped: bool,
22}
23
24impl StepOutput {
25 pub fn new(value: &str) -> Self {
27 Self {
28 value: value.into(),
29 metadata: None,
30 skipped: false,
31 }
32 }
33
34 pub fn skipped() -> Self {
36 Self {
37 value: String::new(),
38 metadata: None,
39 skipped: true,
40 }
41 }
42
43 pub fn is_skipped(&self) -> bool {
45 self.skipped
46 }
47
48 pub fn value(&self) -> &str {
50 &self.value
51 }
52
53 pub fn metadata(&self) -> Option<&serde_json::Value> {
55 self.metadata.as_ref()
56 }
57
58 #[must_use]
60 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
61 self.metadata = Some(metadata);
62 self
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 struct AddStep {
73 id: String,
74 value: String,
75 }
76
77 impl AddStep {
78 fn new(id: &str, value: &str) -> Self {
79 Self {
80 id: id.into(),
81 value: value.into(),
82 }
83 }
84 }
85
86 #[async_trait::async_trait]
87 impl Step for AddStep {
88 fn id(&self) -> &str {
89 &self.id
90 }
91
92 async fn execute(&self, _ctx: &mut WorkflowContext) -> Result<StepOutput, WorkflowError> {
93 Ok(StepOutput::new(&self.value))
94 }
95 }
96
97 #[test]
100 fn step_returns_id() {
101 let step = AddStep::new("build", "result");
102 assert_eq!(step.id(), "build");
103 }
104
105 #[tokio::test]
106 async fn step_executes_and_returns_output() {
107 let step = AddStep::new("build", "compiled");
108 let mut ctx = WorkflowContext::new();
109 let output = step.execute(&mut ctx).await.unwrap();
110 assert_eq!(output.value(), "compiled");
111 }
112
113 #[test]
116 fn step_output_stores_value() {
117 let output = StepOutput::new("hello");
118 assert_eq!(output.value(), "hello");
119 }
120
121 #[test]
122 fn step_output_with_metadata() {
123 let output = StepOutput::new("result").with_metadata(serde_json::json!({"exit_code": 0}));
124 assert_eq!(output.value(), "result");
125 assert!(output.metadata().is_some());
126 }
127}