Files
DevFlow/crates/df-workflow/src/dag_def.rs
绝尘 566bbcb394 修复: run_workflow空dag反序列化失败(nodes/edges加serde default)
BUG-260621-01: 前端推进链传空 dag {} 时,DagDef.nodes/edges 无 serde
default 致 Tauri IPC 入口报 missing field nodes,挡在 workflow.rs:91
template_for 选模板逻辑之前。加 #[serde(default)] 后 {} 反序列化成空
DagDef(HashMap/Vec Default 均空集合)→ is_empty() 进选模板分支。非空 dag
零回归。2 单测防回归,df-workflow test 51 passed。
2026-06-22 01:08:26 +08:00

114 lines
3.5 KiB
Rust

//! DAG 定义 — 可序列化/反序列化,用于持久化和模板
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// DAG 定义 — 可序列化/反序列化,用于持久化和模板
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DagDef {
// BUG-260621-01: nodes/edges 加 #[serde(default)]。
// 前端 run_workflow 推进链传空 dag `{}`(设计意图:空 dag + target_status →
// workflow.rs:91 dag.nodes.is_empty() 进 template_for 自动选模板)。原无 default
// 时,`{}` 在 Tauri IPC 入口反序列化 DagDef 即报 `missing field nodes`,挡在选模板
// 逻辑之前。加 default 后 `{}` → 空 DagDef(HashMap/Vec 的 Default 均空集合),
// 顺利进 :91 选模板分支。非空 dag 零回归(serde default 仅补缺失字段)。
#[serde(default)]
pub nodes: HashMap<String, NodeDef>,
#[serde(default)]
pub edges: Vec<EdgeDef>,
}
/// 节点定义
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NodeDef {
pub id: String,
pub node_type: String,
pub config: serde_json::Value,
#[serde(default)]
pub label: Option<String>,
}
/// 边定义
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct EdgeDef {
pub source: String,
pub target: String,
#[serde(default)]
pub condition: Option<String>,
}
impl DagDef {
/// 创建空的 DAG 定义
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
edges: Vec::new(),
}
}
/// 添加节点定义
pub fn add_node(&mut self, id: impl Into<String>, node_type: impl Into<String>, config: serde_json::Value) {
let id = id.into();
self.nodes.insert(id.clone(), NodeDef {
id,
node_type: node_type.into(),
config,
label: None,
});
}
/// 添加边定义
pub fn add_edge(&mut self, source: impl Into<String>, target: impl Into<String>) {
self.edges.push(EdgeDef {
source: source.into(),
target: target.into(),
condition: None,
});
}
/// 添加带条件的边定义
pub fn add_edge_with_condition(
&mut self,
source: impl Into<String>,
target: impl Into<String>,
condition: impl Into<String>,
) {
self.edges.push(EdgeDef {
source: source.into(),
target: target.into(),
condition: Some(condition.into()),
});
}
}
impl Default for DagDef {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
// BUG-260621-01: 前端 run_workflow 推进链传空 dag `{}`,serde 必须能反序列化
// 成空 DagDef(非报 missing field),后续 workflow.rs:91 dag.nodes.is_empty()
// 进 template_for 选模板。nodes/edges 加 #[serde(default)] 前此用例会失败。
#[test]
fn empty_json_deserializes_to_empty_dagdef() {
let dag: DagDef = serde_json::from_str("{}").expect("空 JSON 应反序列化为空 DagDef");
assert!(dag.nodes.is_empty());
assert!(dag.edges.is_empty());
}
// 零回归:非空 dag 反序列化行为不变(serde default 仅补缺失字段)。
#[test]
fn nonempty_dag_deserializes_unchanged() {
let json = r#"{"nodes":{"n1":{"id":"n1","node_type":"echo","config":{}}},"edges":[]}"#;
let dag: DagDef = serde_json::from_str(json).expect("非空 dag 反序列化");
assert_eq!(dag.nodes.len(), 1);
assert!(dag.nodes.contains_key("n1"));
assert!(dag.edges.is_empty());
}
}