jellyflow-core 0.1.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use crate::core::{NodeId, Port, PortId};
use crate::ops::{GraphOp, GraphTransaction};

use super::GraphMutationPlanner;
use crate::ops::mutation::collect::{
    bindings_for_port_removal, incident_edges_for_port, remove_edge_ops_for_port,
};
use crate::ops::mutation::{GraphMutationError, PortInsert};

impl GraphMutationPlanner<'_> {
    pub fn add_port_tx(
        &self,
        id: PortId,
        port: Port,
        insert: PortInsert,
        label: impl Into<String>,
    ) -> Result<GraphTransaction, GraphMutationError> {
        Ok(GraphTransaction::new()
            .with_label(label)
            .with_ops(self.add_port_ops(id, port, insert)?))
    }

    pub fn add_port_ops(
        &self,
        id: PortId,
        port: Port,
        insert: PortInsert,
    ) -> Result<Vec<GraphOp>, GraphMutationError> {
        let node_id = port.node;
        if self.graph.ports.contains_key(&id) {
            return Err(GraphMutationError::PortAlreadyExists(id));
        }
        let node = self
            .graph
            .nodes
            .get(&node_id)
            .ok_or(GraphMutationError::MissingNode(node_id))?;
        if node.ports.contains(&id) {
            return Err(GraphMutationError::DuplicateNodePort {
                node: node_id,
                port: id,
            });
        }

        let order = NodePortOrderEdit::insert(node_id, &node.ports, id, insert)?;

        Ok(vec![
            GraphOp::AddPort { id, port },
            GraphOp::SetNodePorts {
                id: node_id,
                from: order.from,
                to: order.to,
            },
        ])
    }

    pub fn remove_port_op(&self, id: PortId) -> Result<GraphOp, GraphMutationError> {
        let port = self
            .graph
            .ports
            .get(&id)
            .cloned()
            .ok_or(GraphMutationError::MissingPort(id))?;

        let edges = incident_edges_for_port(self.graph, id);
        Ok(GraphOp::RemovePort {
            id,
            port,
            bindings: bindings_for_port_removal(self.graph, id, &edges),
            edges,
        })
    }

    pub fn remove_port_ops(&self, id: PortId) -> Result<Vec<GraphOp>, GraphMutationError> {
        let remove_op = self.remove_port_op(id)?;
        let mut ops = Vec::new();

        if let GraphOp::RemovePort { port, .. } = &remove_op
            && let Some(node) = self.graph.nodes.get(&port.node)
        {
            let order = NodePortOrderEdit::remove(&node.ports, id);
            if order.from != order.to {
                ops.push(GraphOp::SetNodePorts {
                    id: port.node,
                    from: order.from,
                    to: order.to,
                });
            }
        }

        ops.push(remove_op);
        Ok(ops)
    }

    pub fn disconnect_port_ops(&self, id: PortId) -> Result<Vec<GraphOp>, GraphMutationError> {
        self.graph
            .ports
            .get(&id)
            .ok_or(GraphMutationError::MissingPort(id))?;

        Ok(remove_edge_ops_for_port(self.graph, id))
    }

    pub fn remove_port_tx(
        &self,
        id: PortId,
        label: impl Into<String>,
    ) -> Result<GraphTransaction, GraphMutationError> {
        Ok(GraphTransaction::new()
            .with_label(label)
            .with_ops(self.remove_port_ops(id)?))
    }
}

struct NodePortOrderEdit {
    from: Vec<PortId>,
    to: Vec<PortId>,
}

impl NodePortOrderEdit {
    fn insert(
        node: NodeId,
        existing: &[PortId],
        inserted: PortId,
        insert: PortInsert,
    ) -> Result<Self, GraphMutationError> {
        let from = existing.to_vec();
        let mut to = from.clone();
        match insert {
            PortInsert::Append => to.push(inserted),
            PortInsert::At(index) => {
                if index > to.len() {
                    return Err(GraphMutationError::PortInsertOutOfBounds {
                        node,
                        index,
                        len: to.len(),
                    });
                }
                to.insert(index, inserted);
            }
        }

        Ok(Self { from, to })
    }

    fn remove(existing: &[PortId], removed: PortId) -> Self {
        let from = existing.to_vec();
        let mut to = from.clone();
        to.retain(|id| *id != removed);

        Self { from, to }
    }
}