Skip to main content

matrixcode_core/workflow/
mod.rs

1//! Workflow Module
2//!
3//! 工作流引擎核心框架,提供基于 YAML 定义的 DAG 工作流管理。
4//!
5//! # 模块结构
6//!
7//! - `def`: 工作流定义结构(WorkflowDef, NodeDef, EdgeDef 等)
8//! - `parser`: YAML 解析器
9//! - `context`: 运行时状态管理
10//! - `template`: 模板渲染引擎({{var}} 替换)
11//! - `rule_engine`: 验证规则引擎
12//! - `engine`: 状态机引擎
13//! - `executors`: 节点执行器(AI、工具、条件、验证)
14//!
15//! # 快速开始
16//!
17//! ```rust,ignore
18//! use matrixcode::workflow::{parse_workflow, WorkflowEngine, WorkflowContext};
19//!
20//! // 解析工作流定义
21//! let yaml = r#"
22//! id: hello-workflow
23//! name: Hello Workflow
24//! nodes:
25//!   - id: start
26//!     type: start
27//!     name: Start
28//!   - id: greet
29//!     type: task
30//!     name: Greet
31//!     task: greet
32//!   - id: end
33//!     type: end
34//!     name: End
35//! edges:
36//!   - from: start
37//!     to: greet
38//!   - from: greet
39//!     to: end
40//! "#;
41//!
42//! let workflow = parse_workflow(yaml)?;
43//!
44//! // 创建引擎并运行
45//! let engine = WorkflowEngine::new(workflow)?;
46//! let context = engine.run(HashMap::new()).await?;
47//!
48//! println!("Workflow status: {:?}", context.status);
49//! ```
50
51pub mod context;
52pub mod def;
53pub mod engine;
54pub mod executors;
55pub mod parser;
56pub mod persistence;
57pub mod registry;
58pub mod rule_engine;
59pub mod template;
60
61// 重导出公共接口
62pub use def::{
63    BranchDef, EdgeDef, FailureStrategy, InputDef, NodeDef, NodeType, OutputDef, ParallelBranchDef,
64    WorkflowDef,
65};
66
67pub use parser::{parse_workflow, parse_workflow_from_file, to_yaml};
68
69pub use context::{NodeExecution, NodeStatus, WorkflowContext, WorkflowStatus};
70
71pub use template::{TemplateRenderer, render as render_template};
72
73pub use rule_engine::{Rule, RuleEngine, ValidationResult, evaluate_expression};
74
75pub use engine::{DefaultTaskExecutor, EventListener, TaskExecutor, WorkflowEngine, WorkflowEvent};
76
77pub use persistence::WorkflowPersistence;
78
79pub use registry::{WorkflowInfo, WorkflowRegistry, WorkflowSource};
80
81pub use executors::{
82    AiExecutor, AiExecutorConfig, CompositeExecutor, CompositeMode, ConditionExecutor,
83    ExecutorFactory, NodeExecutor, ProxyExecutor, ToolExecutor, ToolExecutorConfig,
84    ValidateExecutor, ValidateExecutorConfig,
85};
86
87#[cfg(test)]
88mod integration_tests {
89    use super::*;
90    use std::collections::HashMap;
91
92    #[test]
93    fn test_parse_and_validate_workflow() {
94        let yaml = r#"
95id: integration-test
96name: Integration Test Workflow
97version: "1.0.0"
98description: A test workflow for integration testing
99inputs:
100  - name: user_name
101    type: string
102    required: true
103  - name: count
104    type: number
105    required: false
106    default: 1
107outputs:
108  - name: result
109    value: "{{output}}"
110variables:
111  greeting: "Hello"
112nodes:
113  - id: start
114    type: start
115    name: Start
116  - id: process
117    type: task
118    name: Process
119    task: process_task
120    params:
121      name: "{{user_name}}"
122      count: "{{count}}"
123    on_failure:
124      type: retry
125      max_attempts: 3
126      interval_ms: 1000
127    timeout_ms: 30000
128  - id: end
129    type: end
130    name: End
131edges:
132  - from: start
133    to: process
134  - from: process
135    to: end
136"#;
137        let workflow = parse_workflow(yaml).unwrap();
138        assert_eq!(workflow.id, "integration-test");
139        assert_eq!(workflow.nodes.len(), 3);
140        assert_eq!(workflow.edges.len(), 2);
141        assert!(workflow.validate().is_ok());
142    }
143
144    #[tokio::test]
145    async fn test_workflow_execution() {
146        let workflow_def = WorkflowDef {
147            id: "test-exec".to_string(),
148            name: "Test Execution".to_string(),
149            version: "1.0.0".to_string(),
150            description: None,
151            inputs: vec![],
152            outputs: vec![],
153            nodes: vec![
154                NodeDef {
155                    id: "start".to_string(),
156                    node_type: NodeType::Start,
157                    name: "Start".to_string(),
158                    description: None,
159                    task: None,
160                    params: HashMap::new(),
161                    on_failure: FailureStrategy::Abort,
162                    timeout_ms: None,
163                    branches: None,
164                    parallel_branches: None,
165                    workflow: None,
166                    wait_ms: None,
167                    approvers: None,
168                },
169                NodeDef {
170                    id: "end".to_string(),
171                    node_type: NodeType::End,
172                    name: "End".to_string(),
173                    description: None,
174                    task: None,
175                    params: HashMap::new(),
176                    on_failure: FailureStrategy::Abort,
177                    timeout_ms: None,
178                    branches: None,
179                    parallel_branches: None,
180                    workflow: None,
181                    wait_ms: None,
182                    approvers: None,
183                },
184            ],
185            edges: vec![EdgeDef {
186                id: "e1".to_string(),
187                from: "start".to_string(),
188                to: "end".to_string(),
189                condition: None,
190                label: None,
191            }],
192            variables: HashMap::new(),
193            default_failure_strategy: FailureStrategy::Abort,
194            timeout_ms: None,
195        };
196
197        let engine = WorkflowEngine::new(workflow_def).unwrap();
198        let context = engine.run(HashMap::new()).await.unwrap();
199
200        assert_eq!(context.status, WorkflowStatus::Completed);
201    }
202
203    #[test]
204    fn test_template_rendering() {
205        let mut vars = HashMap::new();
206        vars.insert("name".to_string(), serde_json::json!("World"));
207        vars.insert("count".to_string(), serde_json::json!(42));
208
209        let result = render_template("Hello, {{name}}! Count: {{count}}", &vars).unwrap();
210        assert_eq!(result, "Hello, World! Count: 42");
211    }
212
213    #[test]
214    fn test_rule_validation() {
215        let mut engine = RuleEngine::new();
216        let mut context = HashMap::new();
217        context.insert("status".to_string(), serde_json::json!("success"));
218        context.insert("count".to_string(), serde_json::json!(10));
219
220        let rule = Rule::All {
221            rules: vec![
222                Rule::Equals {
223                    field: "status".to_string(),
224                    value: serde_json::json!("success"),
225                },
226                Rule::GreaterThan {
227                    field: "count".to_string(),
228                    value: 5.0,
229                },
230            ],
231        };
232
233        let result = engine.validate(&rule, &context).unwrap();
234        assert!(result.passed);
235    }
236}