neco-nodegraph 0.1.0

necosystems series node graph data model with port-typed nodes and edges
Documentation
use neco_nodegraph::{GraphError, NodeGraph, Port, PortDirection, PortId};

fn port(id: &str, direction: PortDirection, type_tag: &str) -> Port {
    Port::new(PortId::new(id).expect("port id"), direction, type_tag).expect("port")
}

#[test]
fn add_and_query_edges() {
    let mut graph = NodeGraph::<&'static str, &'static str>::new();
    let source =
        graph.add_node_with_ports("source", vec![port("out", PortDirection::Output, "Fact")]);
    let target =
        graph.add_node_with_ports("target", vec![port("in", PortDirection::Input, "Fact")]);

    let edge_id = graph
        .add_edge(
            (source, PortId::new("out").expect("out id")),
            (target, PortId::new("in").expect("in id")),
            "edge",
        )
        .expect("edge must be added");

    assert_eq!(
        graph.connected((source, PortId::new("out").unwrap())),
        vec![edge_id]
    );
    assert_eq!(graph.incoming(target), vec![edge_id]);
    assert_eq!(graph.outgoing(source), vec![edge_id]);
    assert_eq!(graph.edge(edge_id).expect("edge").payload(), &"edge");
}

#[test]
fn remove_node_cascades_connected_edges() {
    let mut graph = NodeGraph::<u64, u64>::new();
    let source = graph.add_node_with_ports(1, vec![port("out", PortDirection::Output, "Fact")]);
    let target = graph.add_node_with_ports(2, vec![port("in", PortDirection::Input, "Fact")]);
    let edge_id = graph
        .add_edge(
            (source, PortId::new("out").unwrap()),
            (target, PortId::new("in").unwrap()),
            99,
        )
        .unwrap();

    let removed = graph.remove_node(target).expect("remove node");

    assert_eq!(removed.payload(), &2);
    assert!(graph.edge(edge_id).is_none());
    assert!(graph
        .connected((source, PortId::new("out").unwrap()))
        .is_empty());
}

#[test]
fn rejects_invalid_edges() {
    let mut graph = NodeGraph::<(), ()>::new();
    let a = graph.add_node_with_ports((), vec![port("out", PortDirection::Output, "Fact")]);
    let b = graph.add_node_with_ports((), vec![port("in", PortDirection::Input, "Other")]);

    let mismatch = graph
        .add_edge(
            (a, PortId::new("out").unwrap()),
            (b, PortId::new("in").unwrap()),
            (),
        )
        .expect_err("type mismatch must fail");
    assert!(matches!(mismatch, GraphError::TypeTagMismatch { .. }));

    let self_loop = graph
        .add_edge(
            (a, PortId::new("out").unwrap()),
            (a, PortId::new("out").unwrap()),
            (),
        )
        .expect_err("self loop must fail");
    assert_eq!(self_loop, GraphError::SelfLoop);
}

#[test]
fn rejects_duplicate_edge() {
    let mut graph = NodeGraph::<(), ()>::new();
    let source = graph.add_node_with_ports((), vec![port("out", PortDirection::Output, "Fact")]);
    let target = graph.add_node_with_ports((), vec![port("in", PortDirection::Input, "Fact")]);

    graph
        .add_edge(
            (source, PortId::new("out").unwrap()),
            (target, PortId::new("in").unwrap()),
            (),
        )
        .unwrap();

    let duplicate = graph
        .add_edge(
            (source, PortId::new("out").unwrap()),
            (target, PortId::new("in").unwrap()),
            (),
        )
        .expect_err("duplicate must fail");

    assert_eq!(duplicate, GraphError::DuplicateEdge);
}