新增: 初始化 DevFlow 项目仓库
Tauri 2 + Vue 3 + Vite 6 桌面应用,Rust workspace 含 13 个 crate (df-ai / df-storage / df-workflow / df-core / df-execute 等)。 核心能力:AI 聊天 agentic 循环(工具调用+人工审批)、工作流引擎、 任务/想法/项目/阶段管理、可追溯性,及配套前端组件。
This commit is contained in:
147
crates/df-workflow/src/state.rs
Normal file
147
crates/df-workflow/src/state.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
//! 节点状态机 — 管理节点的状态转换
|
||||
//!
|
||||
//! 合法转换:Pending → Running,Running → Completed / Failed,
|
||||
//! 非法转换返回错误,避免状态被随意覆盖。
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use df_core::types::{NodeId, NodeStatus};
|
||||
|
||||
/// 节点状态机
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StateMachine {
|
||||
/// 节点状态映射
|
||||
states: HashMap<NodeId, NodeStatus>,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
/// 创建状态机
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
states: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取节点状态
|
||||
pub fn get(&self, node_id: &NodeId) -> NodeStatus {
|
||||
self.states
|
||||
.get(node_id)
|
||||
.cloned()
|
||||
.unwrap_or(NodeStatus::Pending)
|
||||
}
|
||||
|
||||
/// 判断状态转换是否合法
|
||||
fn is_legal(from: &NodeStatus, to: &NodeStatus) -> bool {
|
||||
matches!(
|
||||
(from, to),
|
||||
(NodeStatus::Pending, NodeStatus::Running)
|
||||
| (NodeStatus::Running, NodeStatus::Completed)
|
||||
| (NodeStatus::Running, NodeStatus::Failed)
|
||||
)
|
||||
}
|
||||
|
||||
/// 状态转换 — 校验合法性后更新,非法转换返回错误
|
||||
pub fn transition(&mut self, node_id: NodeId, target: NodeStatus) -> anyhow::Result<()> {
|
||||
let current = self.get(&node_id);
|
||||
if !Self::is_legal(¤t, &target) {
|
||||
anyhow::bail!(
|
||||
"节点 {} 状态转换非法:{} -> {}",
|
||||
node_id,
|
||||
current.as_str(),
|
||||
target.as_str()
|
||||
);
|
||||
}
|
||||
self.states.insert(node_id, target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 设置节点为运行中(仅允许 Pending -> Running)
|
||||
pub fn set_running(&mut self, node_id: NodeId) -> anyhow::Result<()> {
|
||||
self.transition(node_id, NodeStatus::Running)
|
||||
}
|
||||
|
||||
/// 设置节点为已完成(仅允许 Running -> Completed)
|
||||
pub fn set_completed(&mut self, node_id: NodeId) -> anyhow::Result<()> {
|
||||
self.transition(node_id, NodeStatus::Completed)
|
||||
}
|
||||
|
||||
/// 设置节点为失败(仅允许 Running -> Failed)
|
||||
pub fn set_failed(&mut self, node_id: NodeId) -> anyhow::Result<()> {
|
||||
self.transition(node_id, NodeStatus::Failed)
|
||||
}
|
||||
|
||||
/// 设置节点为等待中(暂未纳入转换校验)
|
||||
pub fn set_waiting(&mut self, node_id: NodeId) {
|
||||
self.states.insert(node_id, NodeStatus::Waiting);
|
||||
}
|
||||
|
||||
/// 设置节点为已跳过(暂未纳入转换校验)
|
||||
pub fn set_skipped(&mut self, node_id: NodeId) {
|
||||
self.states.insert(node_id, NodeStatus::Skipped);
|
||||
}
|
||||
|
||||
/// 获取所有状态快照
|
||||
pub fn snapshot(&self) -> &HashMap<NodeId, NodeStatus> {
|
||||
&self.states
|
||||
}
|
||||
|
||||
/// 检查节点是否被取消
|
||||
pub fn is_cancelled(&self, node_id: &NodeId) -> bool {
|
||||
matches!(self.get(node_id), NodeStatus::Cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StateMachine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_legal_transitions() {
|
||||
let mut sm = StateMachine::new();
|
||||
let id = "node-a".to_string();
|
||||
|
||||
// Pending -> Running -> Completed 全程合法
|
||||
assert!(sm.set_running(id.clone()).is_ok());
|
||||
assert_eq!(sm.get(&id), NodeStatus::Running);
|
||||
assert!(sm.set_completed(id.clone()).is_ok());
|
||||
assert_eq!(sm.get(&id), NodeStatus::Completed);
|
||||
|
||||
// Running -> Failed 合法
|
||||
let id2 = "node-b".to_string();
|
||||
sm.set_running(id2.clone()).unwrap();
|
||||
assert!(sm.set_failed(id2.clone()).is_ok());
|
||||
assert_eq!(sm.get(&id2), NodeStatus::Failed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_illegal_transitions_rejected() {
|
||||
let mut sm = StateMachine::new();
|
||||
let id = "node-a".to_string();
|
||||
|
||||
// Pending -> Completed 非法
|
||||
let err = sm.set_completed(id.clone()).unwrap_err();
|
||||
assert!(err.to_string().contains("状态转换非法"));
|
||||
assert!(err.to_string().contains("node-a"));
|
||||
// 状态保持不变
|
||||
assert_eq!(sm.get(&id), NodeStatus::Pending);
|
||||
|
||||
// Pending -> Failed 非法
|
||||
assert!(sm.set_failed(id.clone()).is_err());
|
||||
|
||||
// Completed 为终态,不允许再转 Running
|
||||
sm.set_running(id.clone()).unwrap();
|
||||
sm.set_completed(id.clone()).unwrap();
|
||||
assert!(sm.set_running(id.clone()).is_err());
|
||||
|
||||
// Running -> Running 重复置位非法
|
||||
let id2 = "node-b".to_string();
|
||||
sm.set_running(id2.clone()).unwrap();
|
||||
assert!(sm.set_running(id2.clone()).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user