bounded_graph 0.3.0

A thin newtype wrapper for `petgraph` to assist in the creation of graphs with restrictions on their edges
Documentation
use crate::error::{BoundedAcyclicGraphError, BoundedGraphError};
use crate::{BoundedAcyclicDiGraph, FixedEdgeCount};
use petgraph::graph::NodeIndex;

#[test]
fn test_source_rejected_display() {
    let err: BoundedGraphError =
        BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    assert_eq!(
        format!("{}", err),
        "source node 0 rejected the outgoing edge"
    );
}

#[test]
fn test_target_rejected_display() {
    let err: BoundedGraphError =
        BoundedGraphError::target_rejected(NodeIndex::new(0), NodeIndex::new(1));
    assert_eq!(
        format!("{}", err),
        "target node 1 rejected the incoming edge"
    );
}

#[test]
fn test_both_rejected_display() {
    let err: BoundedGraphError =
        BoundedGraphError::both_rejected(NodeIndex::new(0), NodeIndex::new(1));
    assert_eq!(
        format!("{}", err),
        "both nodes rejected the edge (source: 0, target: 1)"
    );
}

#[test]
fn test_node_not_found_display() {
    let err: BoundedGraphError = BoundedGraphError::not_found(Some(NodeIndex::new(0)), None);
    assert_eq!(format!("{}", err), "source node 0 not found");

    let err: BoundedGraphError = BoundedGraphError::not_found(None, Some(NodeIndex::new(1)));
    assert_eq!(format!("{}", err), "target node 1 not found");

    let err: BoundedGraphError =
        BoundedGraphError::not_found(Some(NodeIndex::new(0)), Some(NodeIndex::new(1)));
    assert_eq!(format!("{}", err), "nodes not found (source: 0, target: 1)");
}

#[test]
fn test_bounded_graph_error_trait() {
    use std::error::Error;
    let err: BoundedGraphError =
        BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    // Test that it implements Error trait
    let _: &dyn Error = &err;
    // source() should return None for our simple error type
    assert!(err.source().is_none());
}

#[test]
fn test_bounded_graph_error_debug() {
    let err: BoundedGraphError =
        BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let debug = format!("{:?}", err);
    assert!(debug.contains("EdgeRejected"));
}

#[test]
fn test_bounded_graph_error_clone() {
    let err: BoundedGraphError =
        BoundedGraphError::both_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let cloned = err.clone();
    assert_eq!(err, cloned);
}

#[test]
fn test_bounded_graph_error_equality() {
    let err1: BoundedGraphError =
        BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let err2: BoundedGraphError =
        BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let err3: BoundedGraphError =
        BoundedGraphError::target_rejected(NodeIndex::new(0), NodeIndex::new(1));

    assert_eq!(err1, err2);
    assert_ne!(err1, err3);
}

// ===== BoundedAcyclicGraphError Tests =====

#[test]
fn test_bounded_acyclic_error_bounded_variant_display() {
    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::source_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    assert_eq!(
        format!("{}", err),
        "source node 0 rejected the outgoing edge"
    );

    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::target_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    assert_eq!(
        format!("{}", err),
        "target node 1 rejected the incoming edge"
    );

    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::both_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    assert_eq!(
        format!("{}", err),
        "both nodes rejected the edge (source: 0, target: 1)"
    );

    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::not_found(
        Some(NodeIndex::new(0)),
        None,
    ));
    assert_eq!(format!("{}", err), "source node 0 not found");
}

#[test]
fn test_bounded_acyclic_error_acyclic_variant_display() {
    use petgraph::acyclic::AcyclicEdgeError;

    let err: BoundedAcyclicGraphError<u32> =
        BoundedAcyclicGraphError::Acyclic(AcyclicEdgeError::SelfLoop);
    assert_eq!(format!("{}", err), "adding edge would create a self-loop");

    let err: BoundedAcyclicGraphError =
        BoundedAcyclicGraphError::Acyclic(AcyclicEdgeError::InvalidEdge);
    assert_eq!(format!("{}", err), "could not add edge to underlying graph");

    // Note: Cycle variant is tested via integration tests since we can't construct
    // a Cycle directly (it has private fields), but the display implementation
    // is covered by actual graph operations that produce cycle errors
}

#[test]
fn test_bounded_acyclic_error_source() {
    use std::error::Error;

    // Bounded variant should return the inner BoundedGraphError as source
    let bounded_err = BoundedGraphError::source_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let err = BoundedAcyclicGraphError::<u32>::Bounded(bounded_err.clone());

    let source = err.source();
    assert!(source.is_some());

    // Verify the source is the BoundedGraphError
    let source_err = source.unwrap().downcast_ref::<BoundedGraphError>();
    assert!(source_err.is_some());
    assert_eq!(source_err.unwrap(), &bounded_err);
}

#[test]
fn test_bounded_acyclic_error_acyclic_no_source() {
    use petgraph::acyclic::AcyclicEdgeError;
    use std::error::Error;

    // Acyclic variant should return None for source
    let err = BoundedAcyclicGraphError::<u32>::Acyclic(AcyclicEdgeError::SelfLoop);
    assert!(err.source().is_none());
}

#[test]
fn test_bounded_acyclic_error_from_bounded_graph_error() {
    let bounded_err: BoundedGraphError =
        BoundedGraphError::target_rejected(NodeIndex::new(0), NodeIndex::new(1));
    let acyclic_err: BoundedAcyclicGraphError<u32> = bounded_err.clone().into();

    match acyclic_err {
        BoundedAcyclicGraphError::Bounded(err) => assert_eq!(err, bounded_err),
        _ => panic!("Expected Bounded variant"),
    }
}

#[test]
fn test_bounded_acyclic_error_from_acyclic_edge_error() {
    use petgraph::acyclic::AcyclicEdgeError;

    let acyclic_err = AcyclicEdgeError::SelfLoop;
    let bounded_acyclic_err: BoundedAcyclicGraphError = acyclic_err.into();

    match bounded_acyclic_err {
        BoundedAcyclicGraphError::Acyclic(AcyclicEdgeError::SelfLoop) => {}
        _ => panic!("Expected Acyclic variant with SelfLoop"),
    }
}

#[test]
fn test_bounded_acyclic_error_debug() {
    use petgraph::acyclic::AcyclicEdgeError;

    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::source_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    let debug = format!("{:?}", err);
    assert!(debug.contains("Bounded"));
    assert!(debug.contains("EdgeRejected"));

    let err = BoundedAcyclicGraphError::<u32>::Acyclic(AcyclicEdgeError::SelfLoop);
    let debug = format!("{:?}", err);
    assert!(debug.contains("Acyclic"));
    assert!(debug.contains("SelfLoop"));
}

#[test]
fn test_bounded_acyclic_error_clone() {
    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::both_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    let cloned = err.clone();
    assert_eq!(err, cloned);
}

#[test]
fn test_bounded_acyclic_error_equality() {
    use petgraph::acyclic::AcyclicEdgeError;

    let err1 = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::source_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    let err2 = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::source_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    let err3 = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::target_rejected(
        NodeIndex::new(0),
        NodeIndex::new(1),
    ));
    let err4 = BoundedAcyclicGraphError::<u32>::Acyclic(AcyclicEdgeError::SelfLoop);

    assert_eq!(err1, err2);
    assert_ne!(err1, err3);
    assert_ne!(err1, err4);
}

#[test]
fn test_bounded_acyclic_error_trait_object() {
    use std::error::Error;

    let err = BoundedAcyclicGraphError::<u32>::Bounded(BoundedGraphError::not_found(
        Some(NodeIndex::new(0)),
        None,
    ));
    let _: &dyn Error = &err;

    // Should be able to convert to trait object
    let boxed: Box<dyn Error> = Box::new(err);
    assert!(boxed.to_string().contains("source node 0 not found"));
}

#[test]
fn test_bounded_acyclic_error_cycle_display() {
    // Integration test: Create an actual cycle to test the Cycle variant display message
    let mut graph = BoundedAcyclicDiGraph::<FixedEdgeCount<10>, ()>::new();

    // Create nodes forming a potential cycle: n1 -> n2 -> n3
    let n1 = graph.add_node(FixedEdgeCount::empty());
    let n2 = graph.add_node(FixedEdgeCount::empty());
    let n3 = graph.add_node(FixedEdgeCount::empty());

    // Add edges to form a path
    graph.add_edge(n1, n2, ()).unwrap();
    graph.add_edge(n2, n3, ()).unwrap();

    // Try to close the cycle: n3 -> n1 (this should fail and give us a Cycle error)
    let result = graph.add_edge(n3, n1, ());

    match result {
        Err(err) => {
            let error_message = format!("{}", err);
            assert_eq!(error_message, "adding edge would create a cycle");
        }
        Ok(_) => panic!("Expected cycle error but edge was added successfully"),
    }
}