pub mod def;
pub mod parser;
pub mod context;
pub mod template;
pub mod rule_engine;
pub mod engine;
pub mod executors;
pub mod persistence;
pub mod registry;
pub use def::{
WorkflowDef,
NodeDef,
EdgeDef,
NodeType,
FailureStrategy,
InputDef,
OutputDef,
BranchDef,
ParallelBranchDef,
};
pub use parser::{
parse_workflow,
parse_workflow_from_file,
to_yaml,
};
pub use context::{
WorkflowContext,
WorkflowStatus,
NodeStatus,
NodeExecution,
};
pub use template::{
TemplateRenderer,
render as render_template,
};
pub use rule_engine::{
Rule,
RuleEngine,
ValidationResult,
evaluate_expression,
};
pub use engine::{
WorkflowEngine,
WorkflowEvent,
EventListener,
TaskExecutor,
DefaultTaskExecutor,
};
pub use persistence::{
WorkflowPersistence,
};
pub use registry::{
WorkflowRegistry,
WorkflowInfo,
WorkflowSource,
};
pub use executors::{
NodeExecutor,
AiExecutor,
AiExecutorConfig,
ToolExecutor,
ToolExecutorConfig,
ProxyExecutor,
ConditionExecutor,
ValidateExecutor,
ValidateExecutorConfig,
CompositeExecutor,
CompositeMode,
ExecutorFactory,
};
#[cfg(test)]
mod integration_tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_parse_and_validate_workflow() {
let yaml = r#"
id: integration-test
name: Integration Test Workflow
version: "1.0.0"
description: A test workflow for integration testing
inputs:
- name: user_name
type: string
required: true
- name: count
type: number
required: false
default: 1
outputs:
- name: result
value: "{{output}}"
variables:
greeting: "Hello"
nodes:
- id: start
type: start
name: Start
- id: process
type: task
name: Process
task: process_task
params:
name: "{{user_name}}"
count: "{{count}}"
on_failure:
type: retry
max_attempts: 3
interval_ms: 1000
timeout_ms: 30000
- id: end
type: end
name: End
edges:
- from: start
to: process
- from: process
to: end
"#;
let workflow = parse_workflow(yaml).unwrap();
assert_eq!(workflow.id, "integration-test");
assert_eq!(workflow.nodes.len(), 3);
assert_eq!(workflow.edges.len(), 2);
assert!(workflow.validate().is_ok());
}
#[tokio::test]
async fn test_workflow_execution() {
let workflow_def = WorkflowDef {
id: "test-exec".to_string(),
name: "Test Execution".to_string(),
version: "1.0.0".to_string(),
description: None,
inputs: vec![],
outputs: vec![],
nodes: vec![
NodeDef {
id: "start".to_string(),
node_type: NodeType::Start,
name: "Start".to_string(),
description: None,
task: None,
params: HashMap::new(),
on_failure: FailureStrategy::Abort,
timeout_ms: None,
branches: None,
parallel_branches: None,
workflow: None,
wait_ms: None,
approvers: None,
},
NodeDef {
id: "end".to_string(),
node_type: NodeType::End,
name: "End".to_string(),
description: None,
task: None,
params: HashMap::new(),
on_failure: FailureStrategy::Abort,
timeout_ms: None,
branches: None,
parallel_branches: None,
workflow: None,
wait_ms: None,
approvers: None,
},
],
edges: vec![EdgeDef {
id: "e1".to_string(),
from: "start".to_string(),
to: "end".to_string(),
condition: None,
label: None,
}],
variables: HashMap::new(),
default_failure_strategy: FailureStrategy::Abort,
timeout_ms: None,
};
let engine = WorkflowEngine::new(workflow_def).unwrap();
let context = engine.run(HashMap::new()).await.unwrap();
assert_eq!(context.status, WorkflowStatus::Completed);
}
#[test]
fn test_template_rendering() {
let mut vars = HashMap::new();
vars.insert("name".to_string(), serde_json::json!("World"));
vars.insert("count".to_string(), serde_json::json!(42));
let result = render_template("Hello, {{name}}! Count: {{count}}", &vars).unwrap();
assert_eq!(result, "Hello, World! Count: 42");
}
#[test]
fn test_rule_validation() {
let mut engine = RuleEngine::new();
let mut context = HashMap::new();
context.insert("status".to_string(), serde_json::json!("success"));
context.insert("count".to_string(), serde_json::json!(10));
let rule = Rule::All {
rules: vec![
Rule::Equals {
field: "status".to_string(),
value: serde_json::json!("success"),
},
Rule::GreaterThan {
field: "count".to_string(),
value: 5.0,
},
],
};
let result = engine.validate(&rule, &context).unwrap();
assert!(result.passed);
}
}