use crate::error::StorageError;
use crate::value::{EdgeId, Value, VertexId};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AlgorithmError {
#[error("vertex not found: {0:?}")]
VertexNotFound(VertexId),
#[error("negative weight cycle detected")]
NegativeWeightCycle,
#[error("no path exists between {from:?} and {to:?}")]
NoPath { from: VertexId, to: VertexId },
#[error("storage error: {0}")]
Storage(#[from] StorageError),
#[error("weight property '{0}' not found or not numeric")]
InvalidWeight(String),
#[error("depth limit exceeded: {0}")]
DepthLimitExceeded(u32),
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathResult {
pub vertices: Vec<VertexId>,
pub edges: Vec<EdgeId>,
pub weight: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Out,
In,
Both,
}
pub type WeightFn = Box<dyn Fn(EdgeId, &HashMap<String, Value>) -> Option<f64> + Send + Sync>;
pub fn unit_weight() -> WeightFn {
Box::new(|_, _| Some(1.0))
}
pub fn property_weight(key: String) -> WeightFn {
Box::new(move |_, props| {
props.get(&key).and_then(|v| match v {
Value::Int(n) => Some(*n as f64),
Value::Float(f) => Some(*f),
_ => None,
})
})
}
pub trait Visitor {
fn discover(&mut self, _vertex: VertexId, _depth: u32) -> bool {
true
}
fn finish(&mut self, _vertex: VertexId, _depth: u32) {}
}
pub struct NoopVisitor;
impl Visitor for NoopVisitor {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_algorithm_error_display() {
let e = AlgorithmError::VertexNotFound(VertexId(42));
assert!(e.to_string().contains("42"));
let e = AlgorithmError::NegativeWeightCycle;
assert!(e.to_string().contains("negative weight"));
let e = AlgorithmError::NoPath {
from: VertexId(1),
to: VertexId(2),
};
assert!(e.to_string().contains("no path"));
let e = AlgorithmError::InvalidWeight("weight".to_string());
assert!(e.to_string().contains("weight"));
let e = AlgorithmError::DepthLimitExceeded(10);
assert!(e.to_string().contains("10"));
}
#[test]
fn test_path_result_clone_eq() {
let p = PathResult {
vertices: vec![VertexId(1), VertexId(2)],
edges: vec![EdgeId(10)],
weight: 1.0,
};
assert_eq!(p, p.clone());
}
#[test]
fn test_direction_copy() {
let d = Direction::Out;
let d2 = d;
assert_eq!(d, d2);
assert_eq!(Direction::In, Direction::In);
assert_eq!(Direction::Both, Direction::Both);
assert_ne!(Direction::Out, Direction::In);
}
#[test]
fn test_unit_weight() {
let w = unit_weight();
assert_eq!(w(EdgeId(0), &HashMap::new()), Some(1.0));
}
#[test]
fn test_property_weight() {
let w = property_weight("cost".to_string());
let mut props = HashMap::new();
props.insert("cost".to_string(), Value::Int(5));
assert_eq!(w(EdgeId(0), &props), Some(5.0));
props.insert("cost".to_string(), Value::Float(3.14));
assert_eq!(w(EdgeId(0), &props), Some(3.14));
props.insert("cost".to_string(), Value::String("nope".to_string()));
assert_eq!(w(EdgeId(0), &props), None);
assert_eq!(w(EdgeId(0), &HashMap::new()), None);
}
#[test]
fn test_noop_visitor() {
let mut v = NoopVisitor;
assert!(v.discover(VertexId(1), 0));
v.finish(VertexId(1), 0); }
#[test]
fn test_storage_error_conversion() {
let se = StorageError::VertexNotFound(VertexId(99));
let ae: AlgorithmError = se.into();
assert!(matches!(ae, AlgorithmError::Storage(_)));
}
}