bounded_graph 0.3.0

A thin newtype wrapper for `petgraph` to assist in the creation of graphs with restrictions on their edges
Documentation
#[cfg(feature = "serde-1")]
use serde::{Deserialize, Serialize};

use std::error::Error;
use std::fmt::{self, Display, Formatter};

use petgraph::graph::{DefaultIx, IndexType, NodeIndex};

/// Error type for bounded graph operations.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
pub enum BoundedGraphError<Ix: IndexType = DefaultIx> {
    /// One or both nodes rejected the edge.
    ///
    /// The node indices indicate which nodes were involved in the rejection.
    /// If `source_rejected` is true, the source node rejected adding the outgoing edge.
    /// If `target_rejected` is true, the target node rejected adding the incoming edge.
    EdgeRejected {
        source: NodeIndex<Ix>,
        target: NodeIndex<Ix>,
        source_rejected: bool,
        target_rejected: bool,
    },
    /// One or both nodes do not exist in the graph.
    ///
    /// `source` is `Some` if the source node doesn't exist.
    /// `target` is `Some` if the target node doesn't exist.
    NodeNotFound {
        source: Option<NodeIndex<Ix>>,
        target: Option<NodeIndex<Ix>>,
    },
}

impl<Ix: IndexType> BoundedGraphError<Ix> {
    /// Creates an error indicating the source node rejected the edge.
    pub fn source_rejected(source: NodeIndex<Ix>, target: NodeIndex<Ix>) -> Self {
        Self::EdgeRejected {
            source,
            target,
            source_rejected: true,
            target_rejected: false,
        }
    }

    /// Creates an error indicating the target node rejected the edge.
    pub fn target_rejected(source: NodeIndex<Ix>, target: NodeIndex<Ix>) -> Self {
        Self::EdgeRejected {
            source,
            target,
            source_rejected: false,
            target_rejected: true,
        }
    }

    /// Creates an error indicating both nodes rejected the edge.
    pub fn both_rejected(source: NodeIndex<Ix>, target: NodeIndex<Ix>) -> Self {
        Self::EdgeRejected {
            source,
            target,
            source_rejected: true,
            target_rejected: true,
        }
    }

    /// Creates an error indicating one or both nodes were not found.
    pub fn not_found(source: Option<NodeIndex<Ix>>, target: Option<NodeIndex<Ix>>) -> Self {
        Self::NodeNotFound { source, target }
    }
}

impl<Ix: IndexType> Display for BoundedGraphError<Ix> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::EdgeRejected {
                source,
                target,
                source_rejected,
                target_rejected,
            } => match (source_rejected, target_rejected) {
                (true, true) => write!(
                    f,
                    "both nodes rejected the edge (source: {:?}, target: {:?})",
                    source.index(),
                    target.index()
                ),
                (true, false) => write!(
                    f,
                    "source node {:?} rejected the outgoing edge",
                    source.index()
                ),
                (false, true) => write!(
                    f,
                    "target node {:?} rejected the incoming edge",
                    target.index()
                ),
                (false, false) => write!(f, "edge rejected (internal error: neither node flagged)"),
            },
            Self::NodeNotFound { source, target } => match (source, target) {
                (Some(s), Some(t)) => {
                    write!(
                        f,
                        "nodes not found (source: {:?}, target: {:?})",
                        s.index(),
                        t.index()
                    )
                }
                (Some(s), None) => write!(f, "source node {:?} not found", s.index()),
                (None, Some(t)) => write!(f, "target node {:?} not found", t.index()),
                (None, None) => write!(f, "nodes not found"),
            },
        }
    }
}

impl<Ix: IndexType> Error for BoundedGraphError<Ix> {}

/// Error type for bounded acyclic graph operations.
///
/// This error type represents failures when adding edges to a bounded acyclic graph,
/// which can fail due to either node capacity constraints or cycle prevention.
#[derive(Debug, Clone, PartialEq)]
pub enum BoundedAcyclicGraphError<Ix: IndexType = DefaultIx> {
    /// The edge violates node capacity constraints (bounded limits).
    Bounded(BoundedGraphError<Ix>),
    /// The edge violates acyclic constraints (would create cycle, self-loop, etc.).
    Acyclic(petgraph::acyclic::AcyclicEdgeError<NodeIndex<Ix>>),
}

impl<Ix: IndexType> Display for BoundedAcyclicGraphError<Ix> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::Bounded(err) => write!(f, "{}", err),
            Self::Acyclic(petgraph::acyclic::AcyclicEdgeError::Cycle(_)) => {
                write!(f, "adding edge would create a cycle")
            }
            Self::Acyclic(petgraph::acyclic::AcyclicEdgeError::SelfLoop) => {
                write!(f, "adding edge would create a self-loop")
            }
            Self::Acyclic(petgraph::acyclic::AcyclicEdgeError::InvalidEdge) => {
                write!(f, "could not add edge to underlying graph")
            }
        }
    }
}

impl<Ix: IndexType> Error for BoundedAcyclicGraphError<Ix>
where
    Ix: fmt::Debug,
{
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Bounded(err) => Some(err),
            Self::Acyclic(_) => None,
        }
    }
}

impl<Ix: IndexType> From<BoundedGraphError<Ix>> for BoundedAcyclicGraphError<Ix> {
    fn from(err: BoundedGraphError<Ix>) -> Self {
        Self::Bounded(err)
    }
}

impl<Ix: IndexType> From<petgraph::acyclic::AcyclicEdgeError<NodeIndex<Ix>>>
    for BoundedAcyclicGraphError<Ix>
{
    fn from(err: petgraph::acyclic::AcyclicEdgeError<NodeIndex<Ix>>) -> Self {
        Self::Acyclic(err)
    }
}