jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use crate::core::NodeExtent;

use super::{GraphOp, GraphTransaction};

pub fn find_non_finite_in_tx(tx: &GraphTransaction) -> Option<(String, String)> {
    find_tx_sanity_issue(
        tx,
        "tx.non_finite",
        "non-finite geometry",
        NonFiniteGeometry::op_field,
    )
}

pub fn find_invalid_size_in_tx(tx: &GraphTransaction) -> Option<(String, String)> {
    find_tx_sanity_issue(
        tx,
        "tx.invalid_size",
        "invalid size",
        InvalidNodeSize::op_field,
    )
}

fn find_tx_sanity_issue(
    tx: &GraphTransaction,
    code: &'static str,
    label: &'static str,
    mut op_field: impl FnMut(&GraphOp) -> Option<&'static str>,
) -> Option<(String, String)> {
    for (ix, op) in tx.ops().iter().enumerate() {
        if let Some(field) = op_field(op) {
            return Some((
                code.to_string(),
                format!("transaction contains {label} at op[{ix}] ({field})"),
            ));
        }
    }
    None
}

struct NonFiniteGeometry;

impl NonFiniteGeometry {
    fn op_field(op: &GraphOp) -> Option<&'static str> {
        match op {
            GraphOp::AddNode { node, .. } => node
                .size
                .and_then(|s| (!s.is_finite()).then_some("AddNode.node.size"))
                .or_else(|| {
                    node.origin
                        .and_then(|origin| (!origin.is_finite()).then_some("AddNode.node.origin"))
                })
                .or_else(|| (!node.pos.is_finite()).then_some("AddNode.node.pos"))
                .or_else(|| match node.extent {
                    Some(NodeExtent::Rect { rect }) => {
                        (!rect.is_finite()).then_some("AddNode.node.extent.rect")
                    }
                    Some(NodeExtent::Parent) | None => None,
                }),
            GraphOp::AddGroup { group, .. } => (!group.rect.is_finite()).then_some("AddGroup.rect"),
            GraphOp::AddStickyNote { note, .. } => {
                (!note.rect.is_finite()).then_some("AddStickyNote.rect")
            }

            GraphOp::SetNodePos { to, .. } => (!to.is_finite()).then_some("SetNodePos.to"),
            GraphOp::SetNodeOrigin { to, .. } => {
                to.and_then(|origin| (!origin.is_finite()).then_some("SetNodeOrigin.to"))
            }
            GraphOp::SetGroupRect { to, .. } => (!to.is_finite()).then_some("SetGroupRect.to"),
            GraphOp::SetStickyNoteRect { to, .. } => {
                (!to.is_finite()).then_some("SetStickyNoteRect.to")
            }
            GraphOp::SetNodeSize { to, .. } => {
                to.and_then(|s| (!s.is_finite()).then_some("SetNodeSize.to"))
            }
            GraphOp::SetNodeExtent { to, .. } => match to {
                Some(NodeExtent::Rect { rect }) => {
                    (!rect.is_finite()).then_some("SetNodeExtent.to.rect")
                }
                Some(NodeExtent::Parent) | None => None,
            },

            GraphOp::AddImport { .. }
            | GraphOp::RemoveImport { .. }
            | GraphOp::SetImportAlias { .. }
            | GraphOp::SetSymbolName { .. }
            | GraphOp::SetSymbolType { .. }
            | GraphOp::SetSymbolDefaultValue { .. }
            | GraphOp::SetNodeSelectable { .. }
            | GraphOp::SetNodeFocusable { .. }
            | GraphOp::SetNodeDraggable { .. }
            | GraphOp::SetNodeConnectable { .. }
            | GraphOp::SetNodeDeletable { .. }
            | GraphOp::SetNodeExpandParent { .. }
            | GraphOp::SetNodeHidden { .. }
            | GraphOp::SetPortConnectable { .. }
            | GraphOp::SetPortConnectableStart { .. }
            | GraphOp::SetPortConnectableEnd { .. }
            | GraphOp::SetPortType { .. }
            | GraphOp::SetPortData { .. }
            | GraphOp::SetEdgeSelectable { .. }
            | GraphOp::SetEdgeFocusable { .. }
            | GraphOp::SetEdgeHidden { .. }
            | GraphOp::SetEdgeInteractionWidth { .. }
            | GraphOp::SetEdgeDeletable { .. }
            | GraphOp::SetEdgeReconnectable { .. }
            | GraphOp::RemoveNode { .. }
            | GraphOp::SetNodeKind { .. }
            | GraphOp::SetNodeKindVersion { .. }
            | GraphOp::SetNodeParent { .. }
            | GraphOp::SetNodeCollapsed { .. }
            | GraphOp::SetNodePorts { .. }
            | GraphOp::SetNodeData { .. }
            | GraphOp::SetGroupTitle { .. }
            | GraphOp::SetGroupColor { .. }
            | GraphOp::SetStickyNoteText { .. }
            | GraphOp::SetStickyNoteColor { .. }
            | GraphOp::AddBinding { .. }
            | GraphOp::RemoveBinding { .. }
            | GraphOp::SetBindingSubject { .. }
            | GraphOp::SetBindingTarget { .. }
            | GraphOp::SetBindingKind { .. }
            | GraphOp::SetBindingMeta { .. }
            | GraphOp::AddPort { .. }
            | GraphOp::RemovePort { .. }
            | GraphOp::AddEdge { .. }
            | GraphOp::RemoveEdge { .. }
            | GraphOp::SetEdgeEndpoints { .. }
            | GraphOp::SetEdgeKind { .. }
            | GraphOp::AddSymbol { .. }
            | GraphOp::RemoveSymbol { .. }
            | GraphOp::SetSymbolMeta { .. }
            | GraphOp::RemoveGroup { .. }
            | GraphOp::RemoveStickyNote { .. } => None,
        }
    }
}

struct InvalidNodeSize;

impl InvalidNodeSize {
    fn op_field(op: &GraphOp) -> Option<&'static str> {
        match op {
            GraphOp::AddNode { node, .. } => node
                .size
                .and_then(|s| (!s.is_positive_finite()).then_some("AddNode.node.size")),
            GraphOp::SetNodeSize { to, .. } => {
                to.and_then(|s| (!s.is_positive_finite()).then_some("SetNodeSize.to"))
            }
            _ => None,
        }
    }
}