jellyflow-runtime 0.1.0

Headless store, rules, schema, profile, and change pipeline for Jellyflow.
Documentation
use crate::io::NodeGraphInteractionState;
use crate::rules::{DeletePlan, Diagnostic};
use jellyflow_core::core::{EdgeId, Graph, NodeId};
use jellyflow_core::ops::GraphOp;

use super::builder::DeleteOpBuilder;
use super::diagnostics::rejected;
use super::selection::DeleteSelection;
use super::validation::DeletePolicyValidator;

pub(super) struct DeletePlanner<'a> {
    graph: &'a Graph,
    state: &'a NodeGraphInteractionState,
}

impl<'a> DeletePlanner<'a> {
    pub(super) fn new(graph: &'a Graph, state: &'a NodeGraphInteractionState) -> Self {
        Self { graph, state }
    }

    pub(super) fn plan(
        self,
        nodes: impl IntoIterator<Item = NodeId>,
        edges: impl IntoIterator<Item = EdgeId>,
    ) -> DeletePlan {
        let selection = DeleteSelection::from_requested(self.graph, nodes, edges);
        if selection.is_empty() {
            return DeletePlan::accept();
        }

        let diagnostics = DeletePolicyValidator::new(self.graph, self.state).validate(&selection);
        if !diagnostics.is_empty() {
            return rejected(diagnostics);
        }

        match self.build_ops(&selection) {
            Ok(ops) => DeletePlan::from_ops(ops),
            Err(diagnostic) => rejected(vec![diagnostic]),
        }
    }

    fn build_ops(&self, selection: &DeleteSelection) -> Result<Vec<GraphOp>, Diagnostic> {
        let mut builder = DeleteOpBuilder::new(self.graph);

        for node_id in selection.nodes() {
            builder.remove_node(*node_id)?;
        }

        for edge_id in selection.edges() {
            if selection.edge_is_cascaded(edge_id) || !builder.has_edge(edge_id) {
                continue;
            }

            builder.remove_edge(*edge_id)?;
        }

        Ok(builder.into_ops())
    }
}