use std::collections::HashMap; use crate::dto::{EdgeDef, NodeDef, NodeType}; /// 内存中的流程图模型,用于执行引擎。 #[derive(Debug, Clone)] pub struct FlowGraph { /// node_id → FlowNode pub nodes: HashMap, /// edge_id → FlowEdge pub edges: HashMap, /// node_id → 从该节点出发的边列表 pub outgoing: HashMap>, /// node_id → 到达该节点的边列表 pub incoming: HashMap>, /// StartEvent 的 node_id pub start_node_id: Option, /// 所有 EndEvent 的 node_id pub end_node_ids: Vec, } /// 内存中的节点模型。 #[derive(Debug, Clone)] pub struct FlowNode { pub id: String, pub node_type: NodeType, pub name: String, pub assignee_id: Option, pub candidate_groups: Option>, pub service_type: Option, pub service_config: Option, } /// 内存中的边模型。 #[derive(Debug, Clone)] pub struct FlowEdge { pub id: String, pub source: String, pub target: String, pub condition: Option, pub label: Option, } impl FlowGraph { /// 从 DTO 节点和边列表构建 FlowGraph。 pub fn build(nodes: &[NodeDef], edges: &[EdgeDef]) -> Self { let mut graph = FlowGraph { nodes: HashMap::new(), edges: HashMap::new(), outgoing: HashMap::new(), incoming: HashMap::new(), start_node_id: None, end_node_ids: Vec::new(), }; for n in nodes { let flow_node = FlowNode { id: n.id.clone(), node_type: n.node_type.clone(), name: n.name.clone(), assignee_id: n.assignee_id, candidate_groups: n.candidate_groups.clone(), service_type: n.service_type.clone(), service_config: n.service_config.clone(), }; if n.node_type == NodeType::StartEvent { graph.start_node_id = Some(n.id.clone()); } if n.node_type == NodeType::EndEvent { graph.end_node_ids.push(n.id.clone()); } graph.nodes.insert(n.id.clone(), flow_node); graph.outgoing.insert(n.id.clone(), Vec::new()); graph.incoming.insert(n.id.clone(), Vec::new()); } for e in edges { graph.edges.insert( e.id.clone(), FlowEdge { id: e.id.clone(), source: e.source.clone(), target: e.target.clone(), condition: e.condition.clone(), label: e.label.clone(), }, ); if let Some(out) = graph.outgoing.get_mut(&e.source) { out.push(e.id.clone()); } if let Some(inc) = graph.incoming.get_mut(&e.target) { inc.push(e.id.clone()); } } graph } /// 获取节点的出边。 pub fn get_outgoing_edges(&self, node_id: &str) -> Vec<&FlowEdge> { self.outgoing .get(node_id) .map(|edge_ids| { edge_ids .iter() .filter_map(|eid| self.edges.get(eid)) .collect() }) .unwrap_or_default() } /// 获取节点的入边。 pub fn get_incoming_edges(&self, node_id: &str) -> Vec<&FlowEdge> { self.incoming .get(node_id) .map(|edge_ids| { edge_ids .iter() .filter_map(|eid| self.edges.get(eid)) .collect() }) .unwrap_or_default() } } #[cfg(test)] mod tests { use super::*; use crate::dto::{EdgeDef, NodeDef, NodeType}; fn make_node(id: &str, node_type: NodeType) -> NodeDef { NodeDef { id: id.to_string(), node_type, name: id.to_string(), assignee_id: None, candidate_groups: None, service_type: None, service_config: None, position: None, } } fn make_edge(id: &str, source: &str, target: &str) -> EdgeDef { EdgeDef { id: id.to_string(), source: source.to_string(), target: target.to_string(), condition: None, label: None, } } // ---- build ---- #[test] fn build_empty_graph() { let graph = FlowGraph::build(&[], &[]); assert!(graph.nodes.is_empty()); assert!(graph.edges.is_empty()); assert!(graph.start_node_id.is_none()); assert!(graph.end_node_ids.is_empty()); } #[test] fn build_identifies_start_and_end() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("task1", NodeType::UserTask), make_node("end", NodeType::EndEvent), ]; let edges = vec![ make_edge("e1", "start", "task1"), make_edge("e2", "task1", "end"), ]; let graph = FlowGraph::build(&nodes, &edges); assert_eq!(graph.start_node_id, Some("start".to_string())); assert_eq!(graph.end_node_ids, vec!["end".to_string()]); assert_eq!(graph.nodes.len(), 3); assert_eq!(graph.edges.len(), 2); } #[test] fn build_multiple_end_events() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("gw", NodeType::ExclusiveGateway), make_node("end1", NodeType::EndEvent), make_node("end2", NodeType::EndEvent), ]; let edges = vec![ make_edge("e1", "start", "gw"), make_edge("e2", "gw", "end1"), make_edge("e3", "gw", "end2"), ]; let graph = FlowGraph::build(&nodes, &edges); assert_eq!(graph.end_node_ids.len(), 2); assert!(graph.end_node_ids.contains(&"end1".to_string())); assert!(graph.end_node_ids.contains(&"end2".to_string())); } #[test] fn build_copies_node_properties() { let user_id = { let ts = uuid::Timestamp::now(uuid::NoContext); uuid::Uuid::new_v7(ts) }; let nodes = vec![NodeDef { id: "task".to_string(), node_type: NodeType::UserTask, name: "审批".to_string(), assignee_id: Some(user_id), candidate_groups: Some(vec!["managers".to_string()]), service_type: None, service_config: None, position: None, }]; let graph = FlowGraph::build(&nodes, &[]); let node = graph.nodes.get("task").unwrap(); assert_eq!(node.name, "审批"); assert_eq!(node.assignee_id, Some(user_id)); assert_eq!(node.candidate_groups, Some(vec!["managers".to_string()])); } #[test] fn build_edge_with_condition_and_label() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("end", NodeType::EndEvent), ]; let edges = vec![EdgeDef { id: "e1".to_string(), source: "start".to_string(), target: "end".to_string(), condition: Some("amount > 1000".to_string()), label: Some("高额审批".to_string()), }]; let graph = FlowGraph::build(&nodes, &edges); let edge = graph.edges.get("e1").unwrap(); assert_eq!(edge.condition, Some("amount > 1000".to_string())); assert_eq!(edge.label, Some("高额审批".to_string())); } #[test] fn build_edge_to_unknown_node_still_recorded() { let nodes = vec![make_node("start", NodeType::StartEvent)]; // edge target "missing" 不在 nodes 中,但 edge 仍被记录 let edges = vec![make_edge("e1", "start", "missing")]; let graph = FlowGraph::build(&nodes, &edges); assert_eq!(graph.edges.len(), 1); // outgoing 为 start 有 e1,但 incoming["missing"] 不存在 assert_eq!(graph.outgoing.get("start").unwrap().len(), 1); assert!(graph.incoming.get("missing").is_none()); } // ---- get_outgoing_edges ---- #[test] fn outgoing_edges_for_known_node() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("end", NodeType::EndEvent), ]; let edges = vec![make_edge("e1", "start", "end")]; let graph = FlowGraph::build(&nodes, &edges); let out = graph.get_outgoing_edges("start"); assert_eq!(out.len(), 1); assert_eq!(out[0].target, "end"); } #[test] fn outgoing_edges_for_unknown_node_empty() { let graph = FlowGraph::build(&[], &[]); assert!(graph.get_outgoing_edges("nonexistent").is_empty()); } #[test] fn outgoing_edges_parallel_gateway_fan_out() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("fork", NodeType::ParallelGateway), make_node("a", NodeType::UserTask), make_node("b", NodeType::ServiceTask), make_node("end", NodeType::EndEvent), ]; let edges = vec![ make_edge("e1", "start", "fork"), make_edge("e2", "fork", "a"), make_edge("e3", "fork", "b"), ]; let graph = FlowGraph::build(&nodes, &edges); let out = graph.get_outgoing_edges("fork"); assert_eq!(out.len(), 2); let targets: Vec<&str> = out.iter().map(|e| e.target.as_str()).collect(); assert!(targets.contains(&"a")); assert!(targets.contains(&"b")); } // ---- get_incoming_edges ---- #[test] fn incoming_edges_for_known_node() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("end", NodeType::EndEvent), ]; let edges = vec![make_edge("e1", "start", "end")]; let graph = FlowGraph::build(&nodes, &edges); let inc = graph.get_incoming_edges("end"); assert_eq!(inc.len(), 1); assert_eq!(inc[0].source, "start"); } #[test] fn incoming_edges_for_unknown_node_empty() { let graph = FlowGraph::build(&[], &[]); assert!(graph.get_incoming_edges("nonexistent").is_empty()); } #[test] fn incoming_edges_join_gateway() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("a", NodeType::UserTask), make_node("b", NodeType::ServiceTask), make_node("join", NodeType::ParallelGateway), make_node("end", NodeType::EndEvent), ]; let edges = vec![ make_edge("e1", "start", "a"), make_edge("e2", "start", "b"), make_edge("e3", "a", "join"), make_edge("e4", "b", "join"), make_edge("e5", "join", "end"), ]; let graph = FlowGraph::build(&nodes, &edges); let inc = graph.get_incoming_edges("join"); assert_eq!(inc.len(), 2); let sources: Vec<&str> = inc.iter().map(|e| e.source.as_str()).collect(); assert!(sources.contains(&"a")); assert!(sources.contains(&"b")); } // ---- start/end 节点无入/出边 ---- #[test] fn start_node_has_no_incoming() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("end", NodeType::EndEvent), ]; let edges = vec![make_edge("e1", "start", "end")]; let graph = FlowGraph::build(&nodes, &edges); assert!(graph.get_incoming_edges("start").is_empty()); assert_eq!(graph.get_outgoing_edges("start").len(), 1); } #[test] fn end_node_has_no_outgoing() { let nodes = vec![ make_node("start", NodeType::StartEvent), make_node("end", NodeType::EndEvent), ]; let edges = vec![make_edge("e1", "start", "end")]; let graph = FlowGraph::build(&nodes, &edges); assert!(graph.get_outgoing_edges("end").is_empty()); assert_eq!(graph.get_incoming_edges("end").len(), 1); } }