//! DAG(有向无环图)定义与拓扑排序 use std::collections::{HashMap, HashSet, VecDeque}; use df_types::error::Result; use df_types::types::NodeId; use crate::node::Node; /// 图的边:从 source 到 target #[derive(Debug, Clone)] pub struct Edge { pub source: NodeId, pub target: NodeId, /// 边的条件表达式(可选) pub condition: Option, } /// DAG 结构 pub struct Dag { /// 节点集合 pub nodes: HashMap>, /// 边集合 pub edges: Vec, /// 节点级配置(DagDef.nodes[id].config 提取自 build_dag,run 时与全局 config deep_merge) /// /// 语义:节点级覆盖全局级。DagExecutor 构造 NodeContext 时执行 /// `deep_merge(initial_config, node_configs[id])`,节点定义里写的同名 key 优先于 /// run_workflow 传入的全局 config。 pub node_configs: HashMap, } impl Dag { /// 创建空 DAG pub fn new() -> Self { Self { nodes: HashMap::new(), edges: Vec::new(), node_configs: HashMap::new(), } } /// 添加节点 pub fn add_node(&mut self, id: NodeId, node: Box) { self.nodes.insert(id, node); } /// 添加节点(附带节点级配置) /// /// 节点级配置在 DagExecutor 构造 NodeContext 时覆盖全局 config(deep_merge)。 pub fn add_node_with_config( &mut self, id: NodeId, node: Box, config: serde_json::Value, ) { self.nodes.insert(id.clone(), node); self.node_configs.insert(id, config); } /// 添加边 pub fn add_edge(&mut self, source: NodeId, target: NodeId) { self.edges.push(Edge { source, target, condition: None, }); } /// 添加带条件的边 pub fn add_edge_with_condition( &mut self, source: NodeId, target: NodeId, condition: String, ) { self.edges.push(Edge { source, target, condition: Some(condition), }); } /// 拓扑排序 — 返回按执行顺序排列的节点 ID 层级 /// /// 返回 Vec>,每层内的节点可以并行执行。 /// /// 复杂度:O(V+E)。一次性遍历边构建邻接索引(出边表)与入度表, /// BFS 分层只走索引查询(每条边仅被访问一次),不再为每个节点全表扫描边集合。 pub fn topological_layers(&self) -> Result>> { let node_ids: HashSet = self.nodes.keys().cloned().collect(); let mut in_degree: HashMap = HashMap::new(); // 邻接出边表:source → 直接后继列表(仅含两端均在 nodes 内的有效边) let mut adjacency_out: HashMap> = HashMap::new(); // 初始化入度(确保每个节点都有表项,便于后续 O(1) 修改) for id in &node_ids { in_degree.insert(id.clone(), 0); } // 单次 O(E) 遍历:同时构建入度表与出边表 for edge in &self.edges { // 仅收录两端均为已注册节点的边,跳过野节点 if node_ids.contains(&edge.source) && node_ids.contains(&edge.target) { *in_degree.get_mut(&edge.target).unwrap() += 1; adjacency_out .entry(edge.source.clone()) .or_default() .push(edge.target.clone()); } } // BFS 分层 let mut layers = Vec::new(); let mut queue: VecDeque = in_degree .iter() .filter(|(_, °)| deg == 0) .map(|(id, _)| id.clone()) .collect(); let mut processed = 0usize; while !queue.is_empty() { let mut layer: Vec = Vec::new(); let layer_size = queue.len(); for _ in 0..layer_size { if let Some(id) = queue.pop_front() { layer.push(id.clone()); // 出边已索引,O(出度) 遍历而非 O(E) 全表扫描 if let Some(succs) = adjacency_out.get(&id) { for succ in succs { let deg = in_degree.get_mut(succ).unwrap(); *deg -= 1; if *deg == 0 { queue.push_back(succ.clone()); } } } processed += 1; } } layers.push(layer); } if processed != node_ids.len() { return Err(df_types::error::Error::Workflow( "DAG 中存在环,无法进行拓扑排序".to_string(), )); } Ok(layers) } } impl Default for Dag { fn default() -> Self { Self::new() } } /// 深度合并两个 JSON 值:节点级 `node` 覆盖全局级 `global`。 /// /// 规则: /// - 两端均为 Object:递归合并(同 key 时节点级覆盖全局级,新增 key 各自保留)。 /// - 非两端 Object:节点级直接覆盖全局级(包括 `Null`,符合 JSON 显式空语义)。 /// - 节点级为 `Null` 的语义是「显式置空」,按非 Object 规则覆盖(直接返回 Null); /// 若调用方希望「跳过节点配置」,应在构造 Dag 时不写入该 key(node_configs 缺失即用全局)。 /// /// 用途:DagExecutor 构造 NodeContext 时 `deep_merge(initial_config, node_config)`, /// 让节点定义里写的 NodeDef.config 优先于 run_workflow 传入的全局 config。 pub fn deep_merge(global: &serde_json::Value, node: &serde_json::Value) -> serde_json::Value { use serde_json::Value; match (global, node) { (Value::Object(g), Value::Object(n)) => { let mut merged = g.clone(); for (k, nv) in n { // 同 key 递归(支持嵌套对象局部覆盖);不同类型时递归退化为「节点级直接覆盖」 let gv = merged.get(k).cloned(); let merged_v = match gv { Some(gv) => deep_merge(&gv, nv), None => nv.clone(), }; merged.insert(k.clone(), merged_v); } Value::Object(merged) } // 非两端 Object:节点级覆盖全局级(含 Null、标量、数组) _ => node.clone(), } } #[cfg(test)] mod deep_merge_tests { use super::deep_merge; use serde_json::json; #[test] fn node_overrides_global_scalar() { assert_eq!(deep_merge(&json!({"a": 1}), &json!({"a": 2})), json!({"a": 2})); } #[test] fn node_adds_new_key() { assert_eq!( deep_merge(&json!({"a": 1}), &json!({"b": 2})), json!({"a": 1, "b": 2}) ); } #[test] fn nested_object_recursive_merge() { assert_eq!( deep_merge(&json!({"o": {"x": 1, "y": 2}}), &json!({"o": {"y": 9}})), json!({"o": {"x": 1, "y": 9}}) ); } #[test] fn empty_node_config_is_noop() { // 核心保现有调用方行为:节点 config 为空对象 → 结果等同全局(零行为破坏) let global = json!({"title": "确认", "options": ["同意"]}); assert_eq!(deep_merge(&global, &json!({})), global); } #[test] fn missing_node_config_treated_as_noop() { // DagExecutor 对缺失 key 的节点不调 deep_merge(直接用 global),此处覆盖语义对照: // 若强行把缺失视作 Null,应覆盖(显式空语义),与「缺失」不同(缺失走 if let Some 分支)。 let global = json!({"a": 1}); assert_eq!(deep_merge(&global, &json!(null)), json!(null)); } #[test] fn array_replaces_not_concat() { // 非两端 Object(数组)→ 节点级直接覆盖,不拼接 assert_eq!( deep_merge(&json!({"arr": [1, 2]}), &json!({"arr": [3]})), json!({"arr": [3]}) ); } }