use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GraphError {
#[error("Node {node} not found in graph with {graph_size} nodes. Context: {context}")]
NodeNotFound {
node: String,
graph_size: usize,
context: String,
},
#[error("Edge ({src_node}, {target}) not found in graph. Context: {context}")]
EdgeNotFound {
src_node: String,
target: String,
context: String,
},
#[error("Invalid parameter '{param}' with value '{value}'. Expected: {expected}. Context: {context}")]
InvalidParameter {
param: String,
value: String,
expected: String,
context: String,
},
#[error("Algorithm '{algorithm}' failed: {reason}. Iterations: {iterations}, Tolerance: {tolerance}")]
AlgorithmFailure {
algorithm: String,
reason: String,
iterations: usize,
tolerance: f64,
},
#[error("I/O error for path '{path}': {source}")]
IOError {
path: String,
#[source]
source: std::io::Error,
},
#[error("Memory error: requested {requested} bytes, available {available} bytes. Context: {context}")]
MemoryError {
requested: usize,
available: usize,
context: String,
},
#[error("Convergence error in '{algorithm}': completed {iterations} iterations with tolerance {tolerance}, threshold {threshold}")]
ConvergenceError {
algorithm: String,
iterations: usize,
tolerance: f64,
threshold: f64,
},
#[error("Graph structure error: expected {expected}, found {found}. Context: {context}")]
GraphStructureError {
expected: String,
found: String,
context: String,
},
#[error(
"No path found from {src_node} to {target} in graph with {nodes} nodes and {edges} edges"
)]
NoPath {
src_node: String,
target: String,
nodes: usize,
edges: usize,
},
#[error(
"Cycle detected in graph starting from node {start_node}. Cycle length: {cycle_length}"
)]
CycleDetected {
start_node: String,
cycle_length: usize,
},
#[error("Linear algebra error in operation '{operation}': {details}")]
LinAlgError {
operation: String,
details: String,
},
#[error("Sparse matrix error: {details}")]
SparseError {
details: String,
},
#[error("Core module error: {0}")]
CoreError(#[from] scirs2_core::error::CoreError),
#[error("Serialization error for format '{format}': {details}")]
SerializationError {
format: String,
details: String,
},
#[error("Invalid attribute '{attribute}' for {target_type}: {details}")]
InvalidAttribute {
attribute: String,
target_type: String,
details: String,
},
#[error("Operation '{operation}' was cancelled after {elapsed_time} seconds")]
Cancelled {
operation: String,
elapsed_time: f64,
},
#[error("Concurrency error in '{operation}': {details}")]
ConcurrencyError {
operation: String,
details: String,
},
#[error("Format error: unsupported format '{format}' version {version}. Supported versions: {supported}")]
FormatError {
format: String,
version: String,
supported: String,
},
#[error("Invalid graph: {0}")]
InvalidGraph(String),
#[error("Algorithm error: {0}")]
AlgorithmError(String),
#[error("Computation error: {0}")]
ComputationError(String),
#[error("{0}")]
Other(String),
}
impl GraphError {
pub fn node_not_found<T: fmt::Display>(node: T) -> Self {
Self::NodeNotFound {
node: node.to_string(),
graph_size: 0,
context: "Node lookup operation".to_string(),
}
}
pub fn node_not_found_with_context<T: fmt::Display>(
node: T,
graph_size: usize,
context: &str,
) -> Self {
Self::NodeNotFound {
node: node.to_string(),
graph_size,
context: context.to_string(),
}
}
pub fn edge_not_found<S: fmt::Display, T: fmt::Display>(source: S, target: T) -> Self {
Self::EdgeNotFound {
src_node: source.to_string(),
target: target.to_string(),
context: "Edge lookup operation".to_string(),
}
}
pub fn edge_not_found_with_context<S: fmt::Display, T: fmt::Display>(
source: S,
target: T,
context: &str,
) -> Self {
Self::EdgeNotFound {
src_node: source.to_string(),
target: target.to_string(),
context: context.to_string(),
}
}
pub fn invalid_parameter<P: fmt::Display, V: fmt::Display, E: fmt::Display>(
param: P,
value: V,
expected: E,
) -> Self {
Self::InvalidParameter {
param: param.to_string(),
value: value.to_string(),
expected: expected.to_string(),
context: "Parameter validation".to_string(),
}
}
pub fn algorithm_failure<A: fmt::Display, R: fmt::Display>(
algorithm: A,
reason: R,
iterations: usize,
tolerance: f64,
) -> Self {
Self::AlgorithmFailure {
algorithm: algorithm.to_string(),
reason: reason.to_string(),
iterations,
tolerance,
}
}
pub fn memory_error(requested: usize, available: usize, context: &str) -> Self {
Self::MemoryError {
requested,
available,
context: context.to_string(),
}
}
pub fn convergence_error<A: fmt::Display>(
algorithm: A,
iterations: usize,
tolerance: f64,
threshold: f64,
) -> Self {
Self::ConvergenceError {
algorithm: algorithm.to_string(),
iterations,
tolerance,
threshold,
}
}
pub fn graph_structure_error<E: fmt::Display, F: fmt::Display>(
expected: E,
found: F,
context: &str,
) -> Self {
Self::GraphStructureError {
expected: expected.to_string(),
found: found.to_string(),
context: context.to_string(),
}
}
pub fn no_path<S: fmt::Display, T: fmt::Display>(
source: S,
target: T,
nodes: usize,
edges: usize,
) -> Self {
Self::NoPath {
src_node: source.to_string(),
target: target.to_string(),
nodes,
edges,
}
}
pub fn is_recoverable(&self) -> bool {
match self {
GraphError::NodeNotFound { .. } => true,
GraphError::EdgeNotFound { .. } => true,
GraphError::NoPath { .. } => true,
GraphError::InvalidParameter { .. } => true,
GraphError::ConvergenceError { .. } => true,
GraphError::Cancelled { .. } => true,
GraphError::AlgorithmFailure { .. } => false,
GraphError::GraphStructureError { .. } => false,
GraphError::CycleDetected { .. } => false,
GraphError::LinAlgError { .. } => false,
GraphError::SparseError { .. } => false,
GraphError::SerializationError { .. } => false,
GraphError::InvalidAttribute { .. } => true,
GraphError::ConcurrencyError { .. } => false,
GraphError::FormatError { .. } => false,
GraphError::InvalidGraph(_) => false,
GraphError::AlgorithmError(_) => false,
GraphError::MemoryError { .. } => false,
GraphError::IOError { .. } => false,
GraphError::CoreError(_) => false,
GraphError::ComputationError(_) => false,
GraphError::Other(_) => false,
}
}
pub fn recovery_suggestions(&self) -> Vec<String> {
match self {
GraphError::NodeNotFound { .. } => vec![
"Check that the node exists in the graph".to_string(),
"Verify node ID format and type".to_string(),
"Use graph.has_node() to check existence first".to_string(),
],
GraphError::EdgeNotFound { .. } => vec![
"Check that both nodes exist in the graph".to_string(),
"Verify edge direction for directed graphs".to_string(),
"Use graph.has_edge() to check existence first".to_string(),
],
GraphError::NoPath { .. } => vec![
"Check if graph is connected".to_string(),
"Verify that both nodes exist".to_string(),
"Consider using weakly connected components for directed graphs".to_string(),
],
GraphError::AlgorithmFailure { algorithm, .. } => match algorithm.as_str() {
"pagerank" => vec![
"Increase iteration limit".to_string(),
"Reduce tolerance threshold".to_string(),
"Check for disconnected components".to_string(),
],
"community_detection" => vec![
"Try different resolution parameters".to_string(),
"Ensure graph has edges".to_string(),
"Consider preprocessing to remove isolates".to_string(),
],
_ => vec!["Adjust algorithm parameters".to_string()],
},
GraphError::MemoryError { .. } => vec![
"Use streaming algorithms for large graphs".to_string(),
"Enable memory optimization features".to_string(),
"Process graph in smaller chunks".to_string(),
],
GraphError::ConvergenceError { .. } => vec![
"Increase maximum iterations".to_string(),
"Adjust tolerance threshold".to_string(),
"Check for numerical stability issues".to_string(),
],
_ => vec!["Check input parameters and graph structure".to_string()],
}
}
pub fn category(&self) -> &'static str {
match self {
GraphError::NodeNotFound { .. } | GraphError::EdgeNotFound { .. } => "lookup",
GraphError::InvalidParameter { .. } => "validation",
GraphError::AlgorithmFailure { .. } | GraphError::ConvergenceError { .. } => {
"algorithm"
}
GraphError::IOError { .. } => "io",
GraphError::MemoryError { .. } => "memory",
GraphError::GraphStructureError { .. } => "structure",
GraphError::NoPath { .. } => "connectivity",
GraphError::CycleDetected { .. } => "topology",
GraphError::SerializationError { .. } => "serialization",
GraphError::Cancelled { .. } => "cancellation",
GraphError::ConcurrencyError { .. } => "concurrency",
GraphError::FormatError { .. } => "format",
_ => "other",
}
}
}
pub type Result<T> = std::result::Result<T, GraphError>;
impl From<std::io::Error> for GraphError {
fn from(err: std::io::Error) -> Self {
GraphError::IOError {
path: "unknown".to_string(),
source: err,
}
}
}
pub struct ErrorContext {
operation: String,
graph_info: Option<(usize, usize)>, }
impl ErrorContext {
pub fn new(operation: &str) -> Self {
Self {
operation: operation.to_string(),
graph_info: None,
}
}
pub fn with_graph_info(mut self, nodes: usize, edges: usize) -> Self {
self.graph_info = Some((nodes, edges));
self
}
pub fn wrap<T>(self, result: Result<T>) -> Result<T> {
result.map_err(|err| self.add_context(err))
}
fn add_context(self, mut err: GraphError) -> GraphError {
match &mut err {
GraphError::NodeNotFound { context, .. } if context == "Node lookup operation" => {
*context = self.operation;
}
GraphError::EdgeNotFound { context, .. } if context == "Edge lookup operation" => {
*context = self.operation;
}
GraphError::InvalidParameter { context, .. } if context == "Parameter validation" => {
*context = self.operation;
}
GraphError::GraphStructureError { context, .. } => {
*context = self.operation;
}
_ => {}
}
err
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = GraphError::node_not_found(42);
assert!(matches!(err, GraphError::NodeNotFound { .. }));
assert!(err.is_recoverable());
assert_eq!(err.category(), "lookup");
}
#[test]
fn test_error_context() {
let _ctx = ErrorContext::new("PageRank computation").with_graph_info(100, 250);
let err = GraphError::convergence_error("pagerank", 100, 1e-3, 1e-6);
let suggestions = err.recovery_suggestions();
assert!(!suggestions.is_empty());
}
#[test]
fn test_error_categories() {
assert_eq!(GraphError::node_not_found(1).category(), "lookup");
assert_eq!(
GraphError::algorithm_failure("test", "failed", 0, 1e-6).category(),
"algorithm"
);
assert_eq!(
GraphError::memory_error(1000, 500, "test").category(),
"memory"
);
}
}