Skip to main content

swf_core/models/task/
mod.rs

1pub mod constants;
2pub mod custom_task;
3pub mod do_task;
4pub mod emit_task;
5pub mod flow_directive;
6pub mod for_task;
7pub mod fork_task;
8pub mod listen_task;
9pub mod raise_task;
10pub mod run_task;
11pub mod set_task;
12pub mod switch_task;
13pub mod try_task;
14pub mod wait_task;
15
16#[cfg(test)]
17mod tests;
18
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21use std::collections::HashMap;
22
23use super::input::InputDataModelDefinition;
24use super::map::Map;
25use super::output::OutputDataModelDefinition;
26use super::timeout::OneOfTimeoutDefinitionOrReference;
27
28// Re-export CallTaskDefinition for convenience
29pub use crate::models::call::CallTaskDefinition;
30
31// Re-export all public types from sub-modules
32pub use constants::{
33    ContainerCleanupPolicy, EventReadMode, ExtensionTarget, FlowDirective, HttpMethod,
34    HttpOutputFormat, OAuth2GrantType, ProcessReturnType, ProcessType, PullPolicy, ScriptLanguage,
35    TaskType,
36};
37pub use custom_task::CustomTaskDefinition;
38pub use do_task::DoTaskDefinition;
39pub use emit_task::{EmitTaskDefinition, EventEmissionDefinition};
40pub use flow_directive::{FlowDirectiveType, FlowDirectiveValue};
41pub use for_task::{ForLoopDefinition, ForTaskDefinition};
42pub use fork_task::{BranchingDefinition, ForkTaskDefinition};
43pub use listen_task::{ListenTaskDefinition, ListenerDefinition, SubscriptionIteratorDefinition};
44pub use raise_task::{RaiseErrorDefinition, RaiseTaskDefinition};
45pub use run_task::{
46    ContainerLifetimeDefinition, ContainerProcessDefinition, OneOfRunArguments,
47    ProcessTypeDefinition, RunTaskDefinition, ScriptProcessDefinition, ShellProcessDefinition,
48    WorkflowProcessDefinition,
49};
50pub use set_task::{SetTaskDefinition, SetValue};
51pub use switch_task::{SwitchCaseDefinition, SwitchTaskDefinition};
52pub use try_task::{
53    ErrorCatcherDefinition, ErrorFilterDefinition, ErrorFilterProperties, TryTaskDefinition,
54};
55pub use wait_task::WaitTaskDefinition;
56
57/// Represents a value that can be any of the supported task definitions
58#[derive(Debug, Clone, PartialEq, Serialize)]
59#[serde(untagged)]
60pub enum TaskDefinition {
61    /// Variant holding the definition of a 'call' task
62    Call(Box<CallTaskDefinition>),
63    /// Variant holding the definition of a 'do' task
64    Do(DoTaskDefinition),
65    /// Variant holding the definition of an 'emit' task
66    Emit(EmitTaskDefinition),
67    /// Variant holding the definition of a 'for' task
68    For(ForTaskDefinition),
69    /// Variant holding the definition of a 'fork' task
70    Fork(ForkTaskDefinition),
71    /// Variant holding the definition of a 'listen' task
72    Listen(Box<ListenTaskDefinition>),
73    /// Variant holding the definition of a 'raise' task
74    Raise(RaiseTaskDefinition),
75    /// Variant holding the definition of a 'run' task
76    Run(Box<RunTaskDefinition>),
77    /// Variant holding the definition of a 'set' task
78    Set(SetTaskDefinition),
79    /// Variant holding the definition of a 'switch' task
80    Switch(SwitchTaskDefinition),
81    /// Variant holding the definition of a 'try' task
82    Try(TryTaskDefinition),
83    /// Variant holding the definition of a 'wait' task
84    Wait(WaitTaskDefinition),
85    /// Variant holding a custom/extension task definition (raw JSON value)
86    Custom(CustomTaskDefinition),
87}
88
89impl TaskDefinition {
90    /// Returns the common fields (if, input, output, export, timeout, then, metadata)
91    /// shared by all task definition variants.
92    pub fn common_fields(&self) -> &TaskDefinitionFields {
93        match self {
94            TaskDefinition::Do(t) => &t.common,
95            TaskDefinition::Set(t) => &t.common,
96            TaskDefinition::Wait(t) => &t.common,
97            TaskDefinition::Raise(t) => &t.common,
98            TaskDefinition::For(t) => &t.common,
99            TaskDefinition::Switch(t) => &t.common,
100            TaskDefinition::Fork(t) => &t.common,
101            TaskDefinition::Try(t) => &t.common,
102            TaskDefinition::Emit(t) => &t.common,
103            TaskDefinition::Listen(t) => &t.common,
104            TaskDefinition::Call(call_def) => call_def.common_fields(),
105            TaskDefinition::Run(t) => &t.common,
106            TaskDefinition::Custom(t) => &t.common,
107        }
108    }
109}
110
111// Custom deserializer that uses field-based detection to determine task type.
112//
113// Priority order matters because some task types share fields:
114// - `for` MUST be checked before `do`: For tasks contain a `do` sub-field,
115//   so a YAML with both `for` and `do` is a For task, not a Do task.
116// - All other task types have unique discriminant fields (`call`, `set`, etc.).
117// - `do` is checked last as a fallback for plain sequential task lists.
118impl<'de> serde::Deserialize<'de> for TaskDefinition {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: serde::Deserializer<'de>,
122    {
123        let value = Value::deserialize(deserializer)?;
124
125        macro_rules! try_deserialize {
126            ($key:expr, $variant:ident, $ty:ty) => {
127                if value.get($key).is_some() {
128                    return <$ty>::deserialize(value)
129                        .map(TaskDefinition::$variant)
130                        .map_err(serde::de::Error::custom);
131                }
132            };
133        }
134        macro_rules! try_deserialize_boxed {
135            ($key:expr, $variant:ident, $ty:ty) => {
136                if value.get($key).is_some() {
137                    return <$ty>::deserialize(value)
138                        .map(|v| TaskDefinition::$variant(Box::new(v)))
139                        .map_err(serde::de::Error::custom);
140                }
141            };
142        }
143
144        // Check for 'for' field first - if present, it's a For task
145        try_deserialize!("for", For, ForTaskDefinition);
146        // Try other variants in priority order
147        try_deserialize_boxed!("call", Call, CallTaskDefinition);
148        try_deserialize!("set", Set, SetTaskDefinition);
149        try_deserialize!("fork", Fork, ForkTaskDefinition);
150        try_deserialize_boxed!("run", Run, RunTaskDefinition);
151        try_deserialize!("switch", Switch, SwitchTaskDefinition);
152        try_deserialize!("try", Try, TryTaskDefinition);
153        try_deserialize!("emit", Emit, EmitTaskDefinition);
154        try_deserialize!("raise", Raise, RaiseTaskDefinition);
155        try_deserialize!("wait", Wait, WaitTaskDefinition);
156        try_deserialize_boxed!("listen", Listen, ListenTaskDefinition);
157        // If we get here and there's a 'do' field, it's a Do task (not a For task)
158        try_deserialize!("do", Do, DoTaskDefinition);
159
160        // Unrecognized task type: store as Custom for extensibility
161        // Matches Go SDK's TaskRegistry pattern — custom tasks are preserved
162        CustomTaskDefinition::deserialize(value)
163            .map(TaskDefinition::Custom)
164            .map_err(serde::de::Error::custom)
165    }
166}
167
168/// Holds the fields common to all tasks
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
170pub struct TaskDefinitionFields {
171    /// Gets/sets a runtime expression, if any, used to determine whether or not the execute the task in the current context
172    #[serde(rename = "if", skip_serializing_if = "Option::is_none")]
173    pub if_: Option<String>,
174
175    /// Gets/sets the definition, if any, of the task's input data
176    #[serde(rename = "input", skip_serializing_if = "Option::is_none")]
177    pub input: Option<InputDataModelDefinition>,
178
179    /// Gets/sets the definition, if any, of the task's output data
180    #[serde(rename = "output", skip_serializing_if = "Option::is_none")]
181    pub output: Option<OutputDataModelDefinition>,
182
183    /// Gets/sets the optional configuration for exporting data within the task's context
184    #[serde(rename = "export", skip_serializing_if = "Option::is_none")]
185    pub export: Option<OutputDataModelDefinition>,
186
187    /// Gets/sets the task's timeout, if any
188    #[serde(rename = "timeout", skip_serializing_if = "Option::is_none")]
189    pub timeout: Option<OneOfTimeoutDefinitionOrReference>,
190
191    /// Gets/sets the flow directive to be performed upon completion of the task
192    #[serde(rename = "then", skip_serializing_if = "Option::is_none")]
193    pub then: Option<String>,
194
195    /// Gets/sets a key/value mapping of additional information associated with the task
196    #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")]
197    pub metadata: Option<HashMap<String, Value>>,
198}
199impl Default for TaskDefinitionFields {
200    fn default() -> Self {
201        TaskDefinitionFields::new()
202    }
203}
204impl TaskDefinitionFields {
205    /// Initializes a new TaskDefinitionFields
206    pub fn new() -> Self {
207        Self {
208            if_: None,
209            input: None,
210            output: None,
211            export: None,
212            timeout: None,
213            then: None,
214            metadata: None,
215        }
216    }
217}