jellyflow_runtime/runtime/
commit.rs1use serde::{Deserialize, Deserializer, Serialize};
7
8use jellyflow_core::ops::{GraphMutationFootprint, GraphOp, GraphTransaction};
9
10#[derive(Debug, Clone, Default, Serialize)]
15pub struct NodeGraphPatch {
16 transaction: GraphTransaction,
18 #[serde(default, skip_serializing_if = "GraphMutationFootprint::is_empty")]
20 footprint: GraphMutationFootprint,
21}
22
23impl NodeGraphPatch {
24 pub fn new(transaction: GraphTransaction) -> Self {
25 let footprint = transaction.footprint();
26 Self {
27 transaction,
28 footprint,
29 }
30 }
31
32 pub fn transaction(&self) -> &GraphTransaction {
33 &self.transaction
34 }
35
36 pub fn footprint(&self) -> &GraphMutationFootprint {
37 &self.footprint
38 }
39
40 pub fn into_transaction(self) -> GraphTransaction {
41 self.transaction
42 }
43
44 pub fn into_parts(self) -> (GraphTransaction, GraphMutationFootprint) {
45 (self.transaction, self.footprint)
46 }
47
48 pub fn ops(&self) -> &[GraphOp] {
49 self.transaction.ops()
50 }
51
52 pub fn is_empty(&self) -> bool {
53 self.transaction.is_empty()
54 }
55}
56
57impl From<GraphTransaction> for NodeGraphPatch {
58 fn from(transaction: GraphTransaction) -> Self {
59 Self::new(transaction)
60 }
61}
62
63impl<'de> Deserialize<'de> for NodeGraphPatch {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: Deserializer<'de>,
67 {
68 #[derive(Deserialize)]
69 struct NodeGraphPatchWire {
70 #[serde(default)]
71 transaction: GraphTransaction,
72 #[serde(default)]
73 footprint: Option<GraphMutationFootprint>,
74 }
75
76 let wire = NodeGraphPatchWire::deserialize(deserializer)?;
77 let _ = wire.footprint;
78 Ok(Self::new(wire.transaction))
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use jellyflow_core::core::{CanvasPoint, NodeId};
86
87 #[test]
88 fn patch_caches_transaction_footprint() {
89 let node = NodeId::from_u128(1);
90 let tx = GraphTransaction::from_ops([GraphOp::SetNodePos {
91 id: node,
92 from: CanvasPoint { x: 0.0, y: 0.0 },
93 to: CanvasPoint { x: 10.0, y: 20.0 },
94 }]);
95
96 let patch = NodeGraphPatch::new(tx.clone());
97
98 assert_eq!(patch.footprint(), &tx.footprint());
99 assert!(patch.footprint().nodes.contains(&node));
100 }
101
102 #[test]
103 fn patch_deserialization_rebuilds_missing_or_stale_footprint() {
104 let node = NodeId::from_u128(1);
105 let tx = GraphTransaction::from_ops([GraphOp::SetNodePos {
106 id: node,
107 from: CanvasPoint { x: 0.0, y: 0.0 },
108 to: CanvasPoint { x: 10.0, y: 20.0 },
109 }]);
110 let encoded = serde_json::json!({ "transaction": tx });
111
112 let patch: NodeGraphPatch = serde_json::from_value(encoded).expect("deserialize patch");
113
114 assert!(patch.footprint().nodes.contains(&node));
115 }
116}