Skip to main content

swf_core/models/
workflow.rs

1use crate::models::authentication::*;
2use crate::models::catalog::*;
3use crate::models::duration::*;
4use crate::models::error::*;
5use crate::models::event::*;
6use crate::models::extension::*;
7use crate::models::input::*;
8use crate::models::map::*;
9use crate::models::output::*;
10use crate::models::retry::*;
11use crate::models::schema::SchemaDefinition;
12use crate::models::task::*;
13use crate::models::timeout::*;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::collections::HashMap;
17
18/// Gets the namespace to use by default for workflow definitions
19pub const DEFAULT_NAMESPACE: &str = "default";
20// Provides the default namespace if not specified during deserialization
21fn default_namespace() -> String {
22    DEFAULT_NAMESPACE.to_string()
23}
24
25/// Gets the latest ServerlessWorkflow DSL version to use by default for workflow definitions
26pub const LATEST_DSL_VERSION: &str = "1.0.1";
27// Provides the latest ServerlessWorkflow DSL version
28fn default_dsl_version() -> String {
29    LATEST_DSL_VERSION.to_string()
30}
31
32// Provides the default runtime expression language
33fn default_runtime_expression_language() -> String {
34    RuntimeExpressionLanguage::JQ.to_string()
35}
36
37string_constants! {
38    /// Enumerates all supported runtime expression languages
39    RuntimeExpressionLanguage {
40        JQ => "jq",
41        JAVASCRIPT => "js",
42    }
43}
44
45/// Represents the definition of a workflow
46#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
47pub struct WorkflowDefinition {
48    /// Gets/sets an object used to document the defined workflow
49    pub document: WorkflowDefinitionMetadata,
50
51    /// Gets/sets the workflow's input definition, if any
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub input: Option<InputDataModelDefinition>,
54
55    /// Gets/sets a collection that contains reusable components for the workflow definition
56    #[serde(rename = "use", skip_serializing_if = "Option::is_none")]
57    pub use_: Option<ComponentDefinitionCollection>,
58
59    /// Gets/sets the workflow's timeout, if any
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub timeout: Option<OneOfTimeoutDefinitionOrReference>,
62
63    /// Gets/sets the workflow's output definition, if any
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub output: Option<OutputDataModelDefinition>,
66
67    /// Gets/sets the workflow's context data definition, if any
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub context: Option<ContextDataModelDefinition>,
70
71    /// Gets/sets the definition of the workflow's schedule, if any
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub schedule: Option<WorkflowScheduleDefinition>,
74
75    /// Gets/sets the configuration of how the runtime expressions
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub evaluate: Option<RuntimeExpressionEvaluationConfiguration>,
78
79    /// Gets/sets a name/value mapping of the tasks to perform
80    #[serde(rename = "do")]
81    pub do_: Map<String, TaskDefinition>,
82
83    /// Gets/sets a key/value mapping, if any, of additional information associated with the workflow
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub metadata: Option<HashMap<String, Value>>,
86}
87impl WorkflowDefinition {
88    /// Initializes a new workflow definition with the given document metadata
89    pub fn new(document: WorkflowDefinitionMetadata) -> Self {
90        Self {
91            document,
92            ..Default::default()
93        }
94    }
95}
96
97/// Represents the metadata of a workflow, including its name, version, and description.
98#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
99pub struct WorkflowDefinitionMetadata {
100    /// Gets/sets the version of the DSL used to define the workflow
101    pub dsl: String,
102
103    /// Gets/sets the workflow's namespace
104    ///
105    /// Defaults to [`DEFAULT_NAMESPACE`] if not specified.
106    #[serde(default = "default_namespace")]
107    pub namespace: String,
108
109    /// Gets/sets the workflow's name
110    pub name: String,
111
112    /// Gets/sets the workflow's semantic version
113    pub version: String,
114
115    /// Gets/sets the workflow's title, if any
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub title: Option<String>,
118
119    /// Gets/sets the workflow's Markdown summary, if any
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub summary: Option<String>,
122
123    /// Gets/sets a key/value mapping of the workflow's tags, if any
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub tags: Option<HashMap<String, String>>,
126
127    /// Gets/sets a key/value mapping, if any, of additional information associated with the workflow document
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub metadata: Option<HashMap<String, Value>>,
130}
131impl WorkflowDefinitionMetadata {
132    // Initializes a new workflow definition metadata
133    pub fn new(
134        namespace: &str,
135        name: &str,
136        version: &str,
137        title: Option<String>,
138        summary: Option<String>,
139        tags: Option<HashMap<String, String>>,
140    ) -> Self {
141        Self {
142            dsl: default_dsl_version(),
143            namespace: namespace.to_owned(),
144            name: name.to_owned(),
145            version: version.to_owned(),
146            title,
147            summary,
148            tags,
149            metadata: None,
150        }
151    }
152}
153
154/// Represents the definition of a workflow's context data
155#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
156pub struct ContextDataModelDefinition {
157    /// Gets/sets the schema, if any, that defines and describes the context data
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub schema: Option<SchemaDefinition>,
160
161    /// Gets/sets a runtime expression, if any, used to set the content of the workflow context
162    #[serde(rename = "as", skip_serializing_if = "Option::is_none")]
163    pub as_: Option<serde_json::Value>,
164}
165
166/// Represents the definition of a workflow's schedule
167#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
168pub struct WorkflowScheduleDefinition {
169    /// Gets/sets an object used to document the defined workflow
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub every: Option<Duration>,
172
173    /// Gets/sets the schedule using a CRON expression, e.g., '0 0 * * *' for daily at midnight.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub cron: Option<String>,
176
177    /// Gets/sets a delay duration, if any, that the workflow must wait before starting again after it completes. In other words, when this workflow completes, it should run again after the specified amount of time.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub after: Option<Duration>,
180
181    /// Gets/sets the events that trigger the workflow execution
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub on: Option<EventConsumptionStrategyDefinition>,
184}
185
186string_constants! {
187    /// Enumerates all supported runtime expression evaluation modes
188    RuntimeExpressionEvaluationMode {
189        STRICT => "strict",
190        LOOSE => "loose",
191    }
192}
193
194string_constants! {
195    /// Enumerates all supported runtime expression argument names
196    RuntimeExpressions {
197        RUNTIME => "runtime",
198        WORKFLOW => "workflow",
199        CONTEXT => "context",
200        ITEM => "item",
201        INDEX => "index",
202        OUTPUT => "output",
203        SECRET => "secret",
204        TASK => "task",
205        INPUT => "input",
206        ERROR => "error",
207        AUTHORIZATION => "authorization",
208    }
209}
210
211/// Represents an object used to configure the workflow's runtime expression evaluation
212#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct RuntimeExpressionEvaluationConfiguration {
214    /// Gets/sets the language used for writing runtime expressions
215    #[serde(default = "default_runtime_expression_language")]
216    pub language: String,
217
218    /// Gets/sets the evaluation mode used for runtime expressions. Defaults to 'loose'.
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub mode: Option<String>,
221}
222
223/// Represents a collection of workflow components
224#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
225pub struct ComponentDefinitionCollection {
226    /// Gets/sets a name/value mapping of the workflow's reusable authentication policies
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub authentications: Option<HashMap<String, ReferenceableAuthenticationPolicy>>,
229
230    /// Gets/sets a name/value mapping of the catalogs, if any, from which to import reusable components used within the workflow
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub catalogs: Option<HashMap<String, CatalogDefinition>>,
233
234    /// Gets/sets a name/value mapping of the workflow's errors, if any
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub errors: Option<HashMap<String, ErrorDefinition>>,
237
238    /// Gets/sets a list containing the workflow's extensions, if any
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub extensions: Option<Vec<HashMap<String, ExtensionDefinition>>>,
241
242    /// Gets/sets a name/value mapping of the workflow's reusable functions
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub functions: Option<HashMap<String, TaskDefinition>>,
245
246    /// Gets/sets a name/value mapping of the workflow's reusable retry policies
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub retries: Option<HashMap<String, RetryPolicyDefinition>>,
249
250    /// Gets/sets a list containing the workflow's secrets
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub secrets: Option<Vec<String>>,
253
254    /// Gets/sets a name/value mapping of the workflow's reusable timeouts
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub timeouts: Option<HashMap<String, TimeoutDefinition>>,
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_workflow_metadata_deserialize() {
265        let json = r#"{
266            "dsl": "1.0.0",
267            "namespace": "test-ns",
268            "name": "test-workflow",
269            "version": "1.0.0",
270            "title": "Test Workflow",
271            "summary": "A test workflow"
272        }"#;
273        let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
274        assert_eq!(meta.dsl, "1.0.0");
275        assert_eq!(meta.namespace, "test-ns");
276        assert_eq!(meta.name, "test-workflow");
277        assert_eq!(meta.version, "1.0.0");
278        assert_eq!(meta.title, Some("Test Workflow".to_string()));
279    }
280
281    #[test]
282    fn test_workflow_metadata_defaults() {
283        let json = r#"{
284            "dsl": "1.0.0",
285            "name": "minimal",
286            "version": "0.1.0"
287        }"#;
288        let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
289        assert_eq!(meta.dsl, "1.0.0");
290        assert_eq!(meta.namespace, "default");
291    }
292
293    #[test]
294    fn test_workflow_metadata_roundtrip() {
295        let json = r#"{
296            "dsl": "1.0.0",
297            "namespace": "test",
298            "name": "myflow",
299            "version": "1.0.0"
300        }"#;
301        let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
302        let serialized = serde_json::to_string(&meta).unwrap();
303        let deserialized: WorkflowDefinitionMetadata = serde_json::from_str(&serialized).unwrap();
304        assert_eq!(meta, deserialized);
305    }
306
307    #[test]
308    fn test_schedule_cron() {
309        let json = r#"{"cron": "0 0 * * *"}"#;
310        let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
311        assert_eq!(schedule.cron, Some("0 0 * * *".to_string()));
312        assert!(schedule.every.is_none());
313    }
314
315    #[test]
316    fn test_schedule_every() {
317        let json = r#"{"every": {"minutes": 30}}"#;
318        let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
319        assert!(schedule.every.is_some());
320        assert!(schedule.cron.is_none());
321    }
322
323    #[test]
324    fn test_schedule_roundtrip() {
325        let json = r#"{"cron": "0 0 * * *"}"#;
326        let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
327        let serialized = serde_json::to_string(&schedule).unwrap();
328        let deserialized: WorkflowScheduleDefinition = serde_json::from_str(&serialized).unwrap();
329        assert_eq!(schedule, deserialized);
330    }
331
332    #[test]
333    fn test_component_collection_deserialize() {
334        let json = r#"{
335            "secrets": ["dbPassword", "apiKey"],
336            "authentications": {
337                "basicAuth": {"basic": {"username": "admin", "password": "secret"}}
338            }
339        }"#;
340        let components: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
341        assert!(components.secrets.is_some());
342        assert!(components.authentications.is_some());
343    }
344
345    #[test]
346    fn test_component_collection_roundtrip() {
347        let json = r#"{
348            "secrets": ["dbPassword"]
349        }"#;
350        let components: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
351        let serialized = serde_json::to_string(&components).unwrap();
352        let deserialized: ComponentDefinitionCollection =
353            serde_json::from_str(&serialized).unwrap();
354        assert_eq!(components, deserialized);
355    }
356
357    #[test]
358    fn test_context_data_model_deserialize() {
359        let json = r#"{
360            "schema": {"format": "json", "document": {"type": "object"}},
361            "as": {"sessionId": "abc123"}
362        }"#;
363        let context: ContextDataModelDefinition = serde_json::from_str(json).unwrap();
364        assert!(context.schema.is_some());
365        assert!(context.as_.is_some());
366    }
367
368    #[test]
369    fn test_runtime_expression_config() {
370        let json = r#"{
371            "language": "jq",
372            "mode": "strict"
373        }"#;
374        let config: RuntimeExpressionEvaluationConfiguration = serde_json::from_str(json).unwrap();
375        assert_eq!(config.language, "jq");
376        assert_eq!(config.mode, Some("strict".to_string()));
377    }
378
379    #[test]
380    fn test_runtime_expression_config_defaults() {
381        let json = r#"{}"#;
382        let config: RuntimeExpressionEvaluationConfiguration = serde_json::from_str(json).unwrap();
383        assert_eq!(config.language, "jq");
384        assert!(config.mode.is_none());
385    }
386
387    // Additional tests matching Go SDK's workflow_test.go Use definition tests
388
389    #[test]
390    fn test_use_definition_comprehensive_deserialize() {
391        let json = r#"{
392            "secrets": ["secret1", "secret2"],
393            "timeouts": {"timeout1": {"after": "PT1M"}}
394        }"#;
395        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
396
397        // Secrets
398        assert!(use_.secrets.is_some());
399        let secrets = use_.secrets.as_ref().unwrap();
400        assert_eq!(secrets.len(), 2);
401        assert!(secrets.contains(&"secret1".to_string()));
402        assert!(secrets.contains(&"secret2".to_string()));
403
404        // Timeouts
405        assert!(use_.timeouts.is_some());
406        let timeouts = use_.timeouts.as_ref().unwrap();
407        assert!(timeouts.contains_key("timeout1"));
408    }
409
410    #[test]
411    fn test_use_definition_minimal() {
412        let json = r#"{
413            "secrets": ["mySecret"]
414        }"#;
415        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
416        assert!(use_.secrets.is_some());
417        assert!(use_.authentications.is_none());
418        assert!(use_.errors.is_none());
419        assert!(use_.extensions.is_none());
420        assert!(use_.retries.is_none());
421        assert!(use_.timeouts.is_none());
422        assert!(use_.catalogs.is_none());
423    }
424
425    #[test]
426    fn test_use_definition_empty() {
427        let json = r#"{}"#;
428        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
429        assert!(use_.authentications.is_none());
430        assert!(use_.secrets.is_none());
431    }
432
433    #[test]
434    fn test_use_definition_with_catalogs_roundtrip() {
435        let json = r#"{
436            "catalogs": {"default": {"endpoint": "http://example.com/catalog"}}
437        }"#;
438        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
439        let serialized = serde_json::to_string(&use_).unwrap();
440        let deserialized: ComponentDefinitionCollection =
441            serde_json::from_str(&serialized).unwrap();
442        assert_eq!(use_, deserialized);
443    }
444
445    #[test]
446    fn test_use_definition_with_timeouts_roundtrip() {
447        let json = r#"{
448            "timeouts": {"short": {"after": "PT10S"}, "long": {"after": "PT1H"}}
449        }"#;
450        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
451        let serialized = serde_json::to_string(&use_).unwrap();
452        let deserialized: ComponentDefinitionCollection =
453            serde_json::from_str(&serialized).unwrap();
454        assert_eq!(use_, deserialized);
455    }
456
457    #[test]
458    fn test_document_tags_and_metadata() {
459        // Matches Go SDK's Document_JSONMarshal/Unmarshal tests
460        let json = r#"{
461            "dsl": "1.0.0",
462            "namespace": "example-namespace",
463            "name": "example-name",
464            "version": "1.0.0",
465            "title": "Example Workflow",
466            "summary": "This is a sample workflow document.",
467            "tags": {"env": "prod", "team": "workflow"},
468            "metadata": {"author": "John Doe", "created": "2025-01-01"}
469        }"#;
470        let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
471        assert_eq!(meta.dsl, "1.0.0");
472        assert_eq!(meta.namespace, "example-namespace");
473        assert_eq!(meta.name, "example-name");
474        assert_eq!(meta.version, "1.0.0");
475        assert_eq!(meta.title, Some("Example Workflow".to_string()));
476        assert_eq!(
477            meta.summary,
478            Some("This is a sample workflow document.".to_string())
479        );
480        assert!(meta.tags.is_some());
481        let tags = meta.tags.as_ref().unwrap();
482        assert_eq!(tags.get("env").map(|s| s.as_str()), Some("prod"));
483        assert_eq!(tags.get("team").map(|s| s.as_str()), Some("workflow"));
484        assert!(meta.metadata.is_some());
485        let md = meta.metadata.as_ref().unwrap();
486        assert!(md.contains_key("author"));
487    }
488
489    // Additional tests matching Go SDK's TestUse_UnmarshalJSON - individual sub-types
490
491    #[test]
492    fn test_use_authentications_deserialize() {
493        let json = r#"{"authentications": {"auth1": {"basic": {"username": "alice", "password": "secret"}}}}"#;
494        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
495        assert!(use_.authentications.is_some());
496        let auths = use_.authentications.as_ref().unwrap();
497        assert!(auths.contains_key("auth1"));
498    }
499
500    #[test]
501    fn test_use_errors_deserialize() {
502        let json = r#"{"errors": {"error1": {"type": "http://example.com/errors", "title": "Not Found", "status": 404}}}"#;
503        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
504        assert!(use_.errors.is_some());
505        let errors = use_.errors.as_ref().unwrap();
506        assert!(errors.contains_key("error1"));
507    }
508
509    #[test]
510    fn test_use_retries_deserialize() {
511        let json = r#"{"retries": {"retry1": {"delay": {"seconds": 5}, "limit": {"attempt": {"count": 3}}}}}"#;
512        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
513        assert!(use_.retries.is_some());
514        let retries = use_.retries.as_ref().unwrap();
515        assert!(retries.contains_key("retry1"));
516    }
517
518    #[test]
519    fn test_use_extensions_deserialize() {
520        let json = r#"{"extensions": [{"ext1": {"extend": "call"}}]}"#;
521        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
522        assert!(use_.extensions.is_some());
523    }
524
525    #[test]
526    fn test_use_functions_deserialize() {
527        let json = r#"{"functions": {"func1": {"set": {"result": "ok"}}}}"#;
528        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
529        assert!(use_.functions.is_some());
530        let funcs = use_.functions.as_ref().unwrap();
531        assert!(funcs.contains_key("func1"));
532    }
533
534    #[test]
535    fn test_use_authentications_roundtrip() {
536        let json = r#"{"authentications": {"auth1": {"basic": {"username": "alice", "password": "secret"}}}}"#;
537        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
538        let serialized = serde_json::to_string(&use_).unwrap();
539        let deserialized: ComponentDefinitionCollection =
540            serde_json::from_str(&serialized).unwrap();
541        assert_eq!(use_, deserialized);
542    }
543
544    #[test]
545    fn test_use_errors_roundtrip() {
546        let json = r#"{"errors": {"error1": {"type": "http://example.com/errors", "title": "Not Found", "status": 404}}}"#;
547        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
548        let serialized = serde_json::to_string(&use_).unwrap();
549        let deserialized: ComponentDefinitionCollection =
550            serde_json::from_str(&serialized).unwrap();
551        assert_eq!(use_, deserialized);
552    }
553
554    #[test]
555    fn test_use_retries_roundtrip() {
556        let json = r#"{"retries": {"retry1": {"delay": {"seconds": 5}, "limit": {"attempt": {"count": 3}}}}}"#;
557        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
558        let serialized = serde_json::to_string(&use_).unwrap();
559        let deserialized: ComponentDefinitionCollection =
560            serde_json::from_str(&serialized).unwrap();
561        assert_eq!(use_, deserialized);
562    }
563
564    #[test]
565    fn test_use_secrets_roundtrip() {
566        let json = r#"{"secrets": ["secret1", "secret2"]}"#;
567        let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
568        let serialized = serde_json::to_string(&use_).unwrap();
569        let deserialized: ComponentDefinitionCollection =
570            serde_json::from_str(&serialized).unwrap();
571        assert_eq!(use_, deserialized);
572    }
573}