use crate::models::authentication::*;
use crate::models::catalog::*;
use crate::models::duration::*;
use crate::models::error::*;
use crate::models::event::*;
use crate::models::extension::*;
use crate::models::input::*;
use crate::models::map::*;
use crate::models::output::*;
use crate::models::retry::*;
use crate::models::schema::SchemaDefinition;
use crate::models::task::*;
use crate::models::timeout::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub const DEFAULT_NAMESPACE: &str = "default";
fn default_namespace() -> String {
DEFAULT_NAMESPACE.to_string()
}
pub const LATEST_DSL_VERSION: &str = "1.0.1";
fn default_dsl_version() -> String {
LATEST_DSL_VERSION.to_string()
}
fn default_runtime_expression_language() -> String {
RuntimeExpressionLanguage::JQ.to_string()
}
string_constants! {
RuntimeExpressionLanguage {
JQ => "jq",
JAVASCRIPT => "js",
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkflowDefinition {
pub document: WorkflowDefinitionMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<InputDataModelDefinition>,
#[serde(rename = "use", skip_serializing_if = "Option::is_none")]
pub use_: Option<ComponentDefinitionCollection>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<OneOfTimeoutDefinitionOrReference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<OutputDataModelDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<ContextDataModelDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<WorkflowScheduleDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub evaluate: Option<RuntimeExpressionEvaluationConfiguration>,
#[serde(rename = "do")]
pub do_: Map<String, TaskDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, Value>>,
}
impl WorkflowDefinition {
pub fn new(document: WorkflowDefinitionMetadata) -> Self {
Self {
document,
..Default::default()
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkflowDefinitionMetadata {
pub dsl: String,
#[serde(default = "default_namespace")]
pub namespace: String,
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, Value>>,
}
impl WorkflowDefinitionMetadata {
pub fn new(
namespace: &str,
name: &str,
version: &str,
title: Option<String>,
summary: Option<String>,
tags: Option<HashMap<String, String>>,
) -> Self {
Self {
dsl: default_dsl_version(),
namespace: namespace.to_owned(),
name: name.to_owned(),
version: version.to_owned(),
title,
summary,
tags,
metadata: None,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContextDataModelDefinition {
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<SchemaDefinition>,
#[serde(rename = "as", skip_serializing_if = "Option::is_none")]
pub as_: Option<serde_json::Value>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkflowScheduleDefinition {
#[serde(skip_serializing_if = "Option::is_none")]
pub every: Option<Duration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cron: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<Duration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on: Option<EventConsumptionStrategyDefinition>,
}
string_constants! {
RuntimeExpressionEvaluationMode {
STRICT => "strict",
LOOSE => "loose",
}
}
string_constants! {
RuntimeExpressions {
RUNTIME => "runtime",
WORKFLOW => "workflow",
CONTEXT => "context",
ITEM => "item",
INDEX => "index",
OUTPUT => "output",
SECRET => "secret",
TASK => "task",
INPUT => "input",
ERROR => "error",
AUTHORIZATION => "authorization",
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeExpressionEvaluationConfiguration {
#[serde(default = "default_runtime_expression_language")]
pub language: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct ComponentDefinitionCollection {
#[serde(skip_serializing_if = "Option::is_none")]
pub authentications: Option<HashMap<String, ReferenceableAuthenticationPolicy>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub catalogs: Option<HashMap<String, CatalogDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errors: Option<HashMap<String, ErrorDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Vec<HashMap<String, ExtensionDefinition>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub functions: Option<HashMap<String, TaskDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retries: Option<HashMap<String, RetryPolicyDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secrets: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeouts: Option<HashMap<String, TimeoutDefinition>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workflow_metadata_deserialize() {
let json = r#"{
"dsl": "1.0.0",
"namespace": "test-ns",
"name": "test-workflow",
"version": "1.0.0",
"title": "Test Workflow",
"summary": "A test workflow"
}"#;
let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
assert_eq!(meta.dsl, "1.0.0");
assert_eq!(meta.namespace, "test-ns");
assert_eq!(meta.name, "test-workflow");
assert_eq!(meta.version, "1.0.0");
assert_eq!(meta.title, Some("Test Workflow".to_string()));
}
#[test]
fn test_workflow_metadata_defaults() {
let json = r#"{
"dsl": "1.0.0",
"name": "minimal",
"version": "0.1.0"
}"#;
let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
assert_eq!(meta.dsl, "1.0.0");
assert_eq!(meta.namespace, "default");
}
#[test]
fn test_workflow_metadata_roundtrip() {
let json = r#"{
"dsl": "1.0.0",
"namespace": "test",
"name": "myflow",
"version": "1.0.0"
}"#;
let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&meta).unwrap();
let deserialized: WorkflowDefinitionMetadata = serde_json::from_str(&serialized).unwrap();
assert_eq!(meta, deserialized);
}
#[test]
fn test_schedule_cron() {
let json = r#"{"cron": "0 0 * * *"}"#;
let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
assert_eq!(schedule.cron, Some("0 0 * * *".to_string()));
assert!(schedule.every.is_none());
}
#[test]
fn test_schedule_every() {
let json = r#"{"every": {"minutes": 30}}"#;
let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
assert!(schedule.every.is_some());
assert!(schedule.cron.is_none());
}
#[test]
fn test_schedule_roundtrip() {
let json = r#"{"cron": "0 0 * * *"}"#;
let schedule: WorkflowScheduleDefinition = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&schedule).unwrap();
let deserialized: WorkflowScheduleDefinition = serde_json::from_str(&serialized).unwrap();
assert_eq!(schedule, deserialized);
}
#[test]
fn test_component_collection_deserialize() {
let json = r#"{
"secrets": ["dbPassword", "apiKey"],
"authentications": {
"basicAuth": {"basic": {"username": "admin", "password": "secret"}}
}
}"#;
let components: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(components.secrets.is_some());
assert!(components.authentications.is_some());
}
#[test]
fn test_component_collection_roundtrip() {
let json = r#"{
"secrets": ["dbPassword"]
}"#;
let components: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&components).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(components, deserialized);
}
#[test]
fn test_context_data_model_deserialize() {
let json = r#"{
"schema": {"format": "json", "document": {"type": "object"}},
"as": {"sessionId": "abc123"}
}"#;
let context: ContextDataModelDefinition = serde_json::from_str(json).unwrap();
assert!(context.schema.is_some());
assert!(context.as_.is_some());
}
#[test]
fn test_runtime_expression_config() {
let json = r#"{
"language": "jq",
"mode": "strict"
}"#;
let config: RuntimeExpressionEvaluationConfiguration = serde_json::from_str(json).unwrap();
assert_eq!(config.language, "jq");
assert_eq!(config.mode, Some("strict".to_string()));
}
#[test]
fn test_runtime_expression_config_defaults() {
let json = r#"{}"#;
let config: RuntimeExpressionEvaluationConfiguration = serde_json::from_str(json).unwrap();
assert_eq!(config.language, "jq");
assert!(config.mode.is_none());
}
#[test]
fn test_use_definition_comprehensive_deserialize() {
let json = r#"{
"secrets": ["secret1", "secret2"],
"timeouts": {"timeout1": {"after": "PT1M"}}
}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.secrets.is_some());
let secrets = use_.secrets.as_ref().unwrap();
assert_eq!(secrets.len(), 2);
assert!(secrets.contains(&"secret1".to_string()));
assert!(secrets.contains(&"secret2".to_string()));
assert!(use_.timeouts.is_some());
let timeouts = use_.timeouts.as_ref().unwrap();
assert!(timeouts.contains_key("timeout1"));
}
#[test]
fn test_use_definition_minimal() {
let json = r#"{
"secrets": ["mySecret"]
}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.secrets.is_some());
assert!(use_.authentications.is_none());
assert!(use_.errors.is_none());
assert!(use_.extensions.is_none());
assert!(use_.retries.is_none());
assert!(use_.timeouts.is_none());
assert!(use_.catalogs.is_none());
}
#[test]
fn test_use_definition_empty() {
let json = r#"{}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.authentications.is_none());
assert!(use_.secrets.is_none());
}
#[test]
fn test_use_definition_with_catalogs_roundtrip() {
let json = r#"{
"catalogs": {"default": {"endpoint": "http://example.com/catalog"}}
}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
#[test]
fn test_use_definition_with_timeouts_roundtrip() {
let json = r#"{
"timeouts": {"short": {"after": "PT10S"}, "long": {"after": "PT1H"}}
}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
#[test]
fn test_document_tags_and_metadata() {
let json = r#"{
"dsl": "1.0.0",
"namespace": "example-namespace",
"name": "example-name",
"version": "1.0.0",
"title": "Example Workflow",
"summary": "This is a sample workflow document.",
"tags": {"env": "prod", "team": "workflow"},
"metadata": {"author": "John Doe", "created": "2025-01-01"}
}"#;
let meta: WorkflowDefinitionMetadata = serde_json::from_str(json).unwrap();
assert_eq!(meta.dsl, "1.0.0");
assert_eq!(meta.namespace, "example-namespace");
assert_eq!(meta.name, "example-name");
assert_eq!(meta.version, "1.0.0");
assert_eq!(meta.title, Some("Example Workflow".to_string()));
assert_eq!(
meta.summary,
Some("This is a sample workflow document.".to_string())
);
assert!(meta.tags.is_some());
let tags = meta.tags.as_ref().unwrap();
assert_eq!(tags.get("env").map(|s| s.as_str()), Some("prod"));
assert_eq!(tags.get("team").map(|s| s.as_str()), Some("workflow"));
assert!(meta.metadata.is_some());
let md = meta.metadata.as_ref().unwrap();
assert!(md.contains_key("author"));
}
#[test]
fn test_use_authentications_deserialize() {
let json = r#"{"authentications": {"auth1": {"basic": {"username": "alice", "password": "secret"}}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.authentications.is_some());
let auths = use_.authentications.as_ref().unwrap();
assert!(auths.contains_key("auth1"));
}
#[test]
fn test_use_errors_deserialize() {
let json = r#"{"errors": {"error1": {"type": "http://example.com/errors", "title": "Not Found", "status": 404}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.errors.is_some());
let errors = use_.errors.as_ref().unwrap();
assert!(errors.contains_key("error1"));
}
#[test]
fn test_use_retries_deserialize() {
let json = r#"{"retries": {"retry1": {"delay": {"seconds": 5}, "limit": {"attempt": {"count": 3}}}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.retries.is_some());
let retries = use_.retries.as_ref().unwrap();
assert!(retries.contains_key("retry1"));
}
#[test]
fn test_use_extensions_deserialize() {
let json = r#"{"extensions": [{"ext1": {"extend": "call"}}]}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.extensions.is_some());
}
#[test]
fn test_use_functions_deserialize() {
let json = r#"{"functions": {"func1": {"set": {"result": "ok"}}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
assert!(use_.functions.is_some());
let funcs = use_.functions.as_ref().unwrap();
assert!(funcs.contains_key("func1"));
}
#[test]
fn test_use_authentications_roundtrip() {
let json = r#"{"authentications": {"auth1": {"basic": {"username": "alice", "password": "secret"}}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
#[test]
fn test_use_errors_roundtrip() {
let json = r#"{"errors": {"error1": {"type": "http://example.com/errors", "title": "Not Found", "status": 404}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
#[test]
fn test_use_retries_roundtrip() {
let json = r#"{"retries": {"retry1": {"delay": {"seconds": 5}, "limit": {"attempt": {"count": 3}}}}}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
#[test]
fn test_use_secrets_roundtrip() {
let json = r#"{"secrets": ["secret1", "secret2"]}"#;
let use_: ComponentDefinitionCollection = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&use_).unwrap();
let deserialized: ComponentDefinitionCollection =
serde_json::from_str(&serialized).unwrap();
assert_eq!(use_, deserialized);
}
}