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
18pub const DEFAULT_NAMESPACE: &str = "default";
20fn default_namespace() -> String {
22 DEFAULT_NAMESPACE.to_string()
23}
24
25pub const LATEST_DSL_VERSION: &str = "1.0.1";
27fn default_dsl_version() -> String {
29 LATEST_DSL_VERSION.to_string()
30}
31
32fn default_runtime_expression_language() -> String {
34 RuntimeExpressionLanguage::JQ.to_string()
35}
36
37string_constants! {
38 RuntimeExpressionLanguage {
40 JQ => "jq",
41 JAVASCRIPT => "js",
42 }
43}
44
45#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
47pub struct WorkflowDefinition {
48 pub document: WorkflowDefinitionMetadata,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub input: Option<InputDataModelDefinition>,
54
55 #[serde(rename = "use", skip_serializing_if = "Option::is_none")]
57 pub use_: Option<ComponentDefinitionCollection>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub timeout: Option<OneOfTimeoutDefinitionOrReference>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub output: Option<OutputDataModelDefinition>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub context: Option<ContextDataModelDefinition>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub schedule: Option<WorkflowScheduleDefinition>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub evaluate: Option<RuntimeExpressionEvaluationConfiguration>,
78
79 #[serde(rename = "do")]
81 pub do_: Map<String, TaskDefinition>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub metadata: Option<HashMap<String, Value>>,
86}
87impl WorkflowDefinition {
88 pub fn new(document: WorkflowDefinitionMetadata) -> Self {
90 Self {
91 document,
92 ..Default::default()
93 }
94 }
95}
96
97#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
99pub struct WorkflowDefinitionMetadata {
100 pub dsl: String,
102
103 #[serde(default = "default_namespace")]
107 pub namespace: String,
108
109 pub name: String,
111
112 pub version: String,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub title: Option<String>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub summary: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub tags: Option<HashMap<String, String>>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub metadata: Option<HashMap<String, Value>>,
130}
131impl WorkflowDefinitionMetadata {
132 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#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
156pub struct ContextDataModelDefinition {
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub schema: Option<SchemaDefinition>,
160
161 #[serde(rename = "as", skip_serializing_if = "Option::is_none")]
163 pub as_: Option<serde_json::Value>,
164}
165
166#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
168pub struct WorkflowScheduleDefinition {
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub every: Option<Duration>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub cron: Option<String>,
176
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub after: Option<Duration>,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub on: Option<EventConsumptionStrategyDefinition>,
184}
185
186string_constants! {
187 RuntimeExpressionEvaluationMode {
189 STRICT => "strict",
190 LOOSE => "loose",
191 }
192}
193
194string_constants! {
195 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#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct RuntimeExpressionEvaluationConfiguration {
214 #[serde(default = "default_runtime_expression_language")]
216 pub language: String,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub mode: Option<String>,
221}
222
223#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
225pub struct ComponentDefinitionCollection {
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub authentications: Option<HashMap<String, ReferenceableAuthenticationPolicy>>,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub catalogs: Option<HashMap<String, CatalogDefinition>>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub errors: Option<HashMap<String, ErrorDefinition>>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub extensions: Option<Vec<HashMap<String, ExtensionDefinition>>>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub functions: Option<HashMap<String, TaskDefinition>>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub retries: Option<HashMap<String, RetryPolicyDefinition>>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub secrets: Option<Vec<String>>,
253
254 #[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 #[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 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 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 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 #[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}