Skip to main content

jellyflow_runtime/runtime/delete/
types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::rules::Diagnostic;
4use crate::runtime::store::DispatchError;
5use jellyflow_core::core::{EdgeId, NodeId};
6
7/// Default transaction label used for committed delete-selection updates.
8pub const DELETE_SELECTION_TRANSACTION_LABEL: &str = "delete selection";
9
10/// Node/edge ids participating in a delete request.
11#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
12pub struct DeleteElements {
13    #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    pub nodes: Vec<NodeId>,
15    #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    pub edges: Vec<EdgeId>,
17}
18
19impl DeleteElements {
20    pub fn new(
21        nodes: impl IntoIterator<Item = NodeId>,
22        edges: impl IntoIterator<Item = EdgeId>,
23    ) -> Self {
24        let mut elements = Self {
25            nodes: nodes.into_iter().collect(),
26            edges: edges.into_iter().collect(),
27        };
28        elements.sort_dedup();
29        elements
30    }
31
32    pub fn is_empty(&self) -> bool {
33        self.nodes.is_empty() && self.edges.is_empty()
34    }
35
36    pub fn nodes(&self) -> &[NodeId] {
37        &self.nodes
38    }
39
40    pub fn edges(&self) -> &[EdgeId] {
41        &self.edges
42    }
43
44    pub fn into_parts(self) -> (Vec<NodeId>, Vec<EdgeId>) {
45        (self.nodes, self.edges)
46    }
47
48    fn sort_dedup(&mut self) {
49        self.nodes.sort_unstable();
50        self.nodes.dedup();
51        self.edges.sort_unstable();
52        self.edges.dedup();
53    }
54}
55
56/// Delete request an adapter can pass to an async `onBeforeDelete`-style hook.
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct PreDeleteRequest {
59    pub requested: DeleteElements,
60    pub planned: DeleteElements,
61}
62
63impl PreDeleteRequest {
64    pub fn new(requested: DeleteElements, planned: DeleteElements) -> Self {
65        Self { requested, planned }
66    }
67
68    pub fn requested(&self) -> &DeleteElements {
69        &self.requested
70    }
71
72    pub fn planned(&self) -> &DeleteElements {
73        &self.planned
74    }
75}
76
77/// Adapter decision after an async `onBeforeDelete`-style hook resolves.
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(tag = "decision", rename_all = "snake_case")]
80pub enum PreDeleteResolution {
81    /// Commit the preflight's planned delete set.
82    Accept,
83    /// Cancel deletion.
84    Veto,
85    /// Commit a replacement delete set after normal policy validation.
86    Replace { elements: DeleteElements },
87}
88
89impl PreDeleteResolution {
90    pub fn accept() -> Self {
91        Self::Accept
92    }
93
94    pub fn veto() -> Self {
95        Self::Veto
96    }
97
98    pub fn replace(
99        nodes: impl IntoIterator<Item = NodeId>,
100        edges: impl IntoIterator<Item = EdgeId>,
101    ) -> Self {
102        Self::Replace {
103            elements: DeleteElements::new(nodes, edges),
104        }
105    }
106}
107
108/// Error returned when a delete-selection request could not be committed.
109#[derive(Debug, thiserror::Error)]
110pub enum DeleteSelectionError {
111    /// Rules rejected the selected elements.
112    #[error("delete selection was rejected")]
113    Rejected {
114        /// Diagnostics produced by the delete rules.
115        diagnostics: Vec<Diagnostic>,
116    },
117    /// Store dispatch failed after rules accepted the delete plan.
118    #[error(transparent)]
119    Dispatch(#[from] DispatchError),
120}
121
122impl DeleteSelectionError {
123    /// Returns rule diagnostics when the request was rejected by delete policy.
124    pub fn diagnostics(&self) -> Option<&[Diagnostic]> {
125        match self {
126            Self::Rejected { diagnostics } => Some(diagnostics),
127            Self::Dispatch(_) => None,
128        }
129    }
130}