jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use crate::core::{Binding, BindingId, Edge, EdgeId, Graph, Port, PortId};
use crate::ops::GraphOp;

use super::ApplyError;
use super::resources::{remove_bindings_exact, remove_edge_exact};

pub(super) fn apply_port_op(graph: &mut Graph, op: &GraphOp) -> Result<(), ApplyError> {
    match op {
        GraphOp::AddPort { id, port } => apply_add_port(graph, *id, port)?,
        GraphOp::RemovePort {
            id,
            port,
            edges,
            bindings,
        } => apply_remove_port(graph, *id, port, edges, bindings)?,
        GraphOp::SetPortConnectable { id, to, .. } => {
            port_mut(graph, *id)?.connectable = *to;
        }
        GraphOp::SetPortConnectableStart { id, to, .. } => {
            port_mut(graph, *id)?.connectable_start = *to;
        }
        GraphOp::SetPortConnectableEnd { id, to, .. } => {
            port_mut(graph, *id)?.connectable_end = *to;
        }
        GraphOp::SetPortType { id, to, .. } => {
            port_mut(graph, *id)?.ty = to.clone();
        }
        GraphOp::SetPortData { id, to, .. } => {
            port_mut(graph, *id)?.data = to.clone();
        }
        _ => unreachable!("non-port op routed to port apply"),
    }
    Ok(())
}

fn apply_add_port(graph: &mut Graph, id: PortId, port: &Port) -> Result<(), ApplyError> {
    if graph.ports.contains_key(&id) {
        return Err(ApplyError::PortAlreadyExists { id });
    }
    if !graph.nodes.contains_key(&port.node) {
        return Err(ApplyError::MissingNode { id: port.node });
    }
    graph.ports.insert(id, port.clone());
    Ok(())
}

fn apply_remove_port(
    graph: &mut Graph,
    id: PortId,
    port: &Port,
    edges: &[(EdgeId, Edge)],
    bindings: &[(BindingId, Binding)],
) -> Result<(), ApplyError> {
    let Some(current) = graph.ports.get(&id) else {
        return Err(ApplyError::MissingPort { id });
    };
    if current.node != port.node || current.key != port.key {
        return Err(ApplyError::RemovePortMismatch { id });
    }
    remove_bindings_exact(graph, bindings)?;
    for (edge_id, edge) in edges {
        remove_edge_exact(graph, *edge_id, edge)?;
    }
    graph.ports.remove(&id);
    if let Some(node) = graph.nodes.get_mut(&port.node) {
        node.ports.retain(|p| *p != id);
    }
    Ok(())
}

fn port_mut(graph: &mut Graph, id: PortId) -> Result<&mut Port, ApplyError> {
    graph
        .ports
        .get_mut(&id)
        .ok_or(ApplyError::MissingPort { id })
}