jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use std::collections::BTreeSet;

use crate::core::{
    Binding, BindingEndpoint, BindingId, Edge, EdgeId, Graph, GraphLocalBindingTarget, GroupId,
    NodeId, Port, PortId, StickyNoteId,
};
use crate::ops::GraphOp;

pub(super) fn incident_edges_for_port(graph: &Graph, id: PortId) -> Vec<(EdgeId, Edge)> {
    let mut edges: Vec<(EdgeId, Edge)> = graph
        .edges
        .iter()
        .filter_map(|(edge_id, edge)| {
            if edge.from == id || edge.to == id {
                Some((*edge_id, edge.clone()))
            } else {
                None
            }
        })
        .collect();
    edges.sort_by_key(|(edge_id, _)| *edge_id);
    edges
}

pub(super) fn remove_edge_ops_for_port(graph: &Graph, id: PortId) -> Vec<GraphOp> {
    incident_edges_for_port(graph, id)
        .into_iter()
        .map(|(id, edge)| GraphOp::RemoveEdge {
            id,
            edge,
            bindings: bindings_for_edge(graph, id),
        })
        .collect()
}

pub(super) fn ports_for_node(graph: &Graph, id: NodeId) -> Vec<(PortId, Port)> {
    let mut ports: Vec<(PortId, Port)> = graph
        .ports
        .iter()
        .filter_map(|(port_id, port)| {
            if port.node == id {
                Some((*port_id, port.clone()))
            } else {
                None
            }
        })
        .collect();
    ports.sort_by_key(|(port_id, _)| *port_id);
    ports
}

pub(super) fn incident_edges_for_ports(
    graph: &Graph,
    port_ids: &BTreeSet<PortId>,
) -> Vec<(EdgeId, Edge)> {
    let mut edges: Vec<(EdgeId, Edge)> = graph
        .edges
        .iter()
        .filter_map(|(edge_id, edge)| {
            if port_ids.contains(&edge.from) || port_ids.contains(&edge.to) {
                Some((*edge_id, edge.clone()))
            } else {
                None
            }
        })
        .collect();
    edges.sort_by_key(|(edge_id, _)| *edge_id);
    edges
}

pub(super) fn detached_nodes_for_group(
    graph: &Graph,
    id: GroupId,
) -> Vec<(NodeId, Option<GroupId>)> {
    let mut detached: Vec<(NodeId, Option<GroupId>)> = graph
        .nodes
        .iter()
        .filter_map(|(node_id, node)| (node.parent == Some(id)).then_some((*node_id, node.parent)))
        .collect();
    detached.sort_by_key(|(node_id, _)| *node_id);
    detached
}

pub(super) fn bindings_for_node(graph: &Graph, id: NodeId) -> Vec<(BindingId, Binding)> {
    bindings_for_target(
        graph,
        |target| matches!(target, GraphLocalBindingTarget::Node { id: target_id } if target_id == id),
    )
}

pub(super) fn bindings_for_node_removal(
    graph: &Graph,
    node: NodeId,
    ports: &[(PortId, Port)],
    edges: &[(EdgeId, Edge)],
) -> Vec<(BindingId, Binding)> {
    collect_binding_union([
        bindings_for_node(graph, node),
        ports
            .iter()
            .flat_map(|(port_id, _)| bindings_for_port(graph, *port_id))
            .collect(),
        edges
            .iter()
            .flat_map(|(edge_id, _)| bindings_for_edge(graph, *edge_id))
            .collect(),
    ])
}

pub(super) fn bindings_for_port(graph: &Graph, id: PortId) -> Vec<(BindingId, Binding)> {
    bindings_for_target(
        graph,
        |target| matches!(target, GraphLocalBindingTarget::Port { id: target_id } if target_id == id),
    )
}

pub(super) fn bindings_for_port_removal(
    graph: &Graph,
    port: PortId,
    edges: &[(EdgeId, Edge)],
) -> Vec<(BindingId, Binding)> {
    collect_binding_union([
        bindings_for_port(graph, port),
        edges
            .iter()
            .flat_map(|(edge_id, _)| bindings_for_edge(graph, *edge_id))
            .collect(),
    ])
}

pub(super) fn bindings_for_edge(graph: &Graph, id: EdgeId) -> Vec<(BindingId, Binding)> {
    bindings_for_target(
        graph,
        |target| matches!(target, GraphLocalBindingTarget::Edge { id: target_id } if target_id == id),
    )
}

pub(super) fn bindings_for_group(graph: &Graph, id: GroupId) -> Vec<(BindingId, Binding)> {
    bindings_for_target(
        graph,
        |target| matches!(target, GraphLocalBindingTarget::Group { id: target_id } if target_id == id),
    )
}

pub(super) fn bindings_for_sticky_note(
    graph: &Graph,
    id: StickyNoteId,
) -> Vec<(BindingId, Binding)> {
    bindings_for_target(
        graph,
        |target| matches!(target, GraphLocalBindingTarget::StickyNote { id: target_id } if target_id == id),
    )
}

fn bindings_for_target(
    graph: &Graph,
    mut matches_target: impl FnMut(GraphLocalBindingTarget) -> bool,
) -> Vec<(BindingId, Binding)> {
    let mut bindings: Vec<(BindingId, Binding)> = graph
        .bindings
        .iter()
        .filter_map(|(binding_id, binding)| {
            let subject_matches = endpoint_matches_target(&binding.subject, &mut matches_target);
            let target_matches = endpoint_matches_target(&binding.target, &mut matches_target);
            (subject_matches || target_matches).then_some((*binding_id, binding.clone()))
        })
        .collect();
    bindings.sort_by_key(|(binding_id, _)| *binding_id);
    bindings
}

fn endpoint_matches_target(
    endpoint: &BindingEndpoint,
    matches_target: &mut impl FnMut(GraphLocalBindingTarget) -> bool,
) -> bool {
    endpoint.graph_local_target().is_some_and(matches_target)
}

fn collect_binding_union<const N: usize>(
    binding_sets: [Vec<(BindingId, Binding)>; N],
) -> Vec<(BindingId, Binding)> {
    let mut seen = BTreeSet::new();
    let mut out = Vec::new();
    for (binding_id, binding) in binding_sets.into_iter().flatten() {
        if seen.insert(binding_id) {
            out.push((binding_id, binding));
        }
    }
    out.sort_by_key(|(binding_id, _)| *binding_id);
    out
}