//! Graph edge (relationship) definition. use super::node::NodeId; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use std::sync::atomic::{AtomicU64, Ordering}; /// Unique edge identifier. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct EdgeId(pub [u8; 32]); impl EdgeId { /// Creates a new unique edge ID. pub fn new() -> Self { static COUNTER: AtomicU64 = AtomicU64::new(1); let id = COUNTER.fetch_add(1, Ordering::SeqCst); let mut bytes = [0u8; 32]; bytes[..8].copy_from_slice(&id.to_be_bytes()); let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() as u64; bytes[8..16].copy_from_slice(&now.to_be_bytes()); EdgeId(*blake3::hash(&bytes).as_bytes()) } /// Creates from raw bytes. pub fn from_bytes(bytes: [u8; 32]) -> Self { EdgeId(bytes) } /// Creates from hex string. pub fn from_hex(hex: &str) -> Option { let bytes = hex::decode(hex).ok()?; if bytes.len() != 32 { return None; } let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes); Some(EdgeId(arr)) } /// Returns the bytes. pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } /// Converts to hex string. pub fn to_hex(&self) -> String { hex::encode(self.0) } } impl Default for EdgeId { fn default() -> Self { Self::new() } } impl std::fmt::Display for EdgeId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "edge_{}", hex::encode(&self.0[..8])) } } /// An edge (relationship) in the graph. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Edge { /// Unique edge ID. pub id: EdgeId, /// Source node ID. pub source: NodeId, /// Target node ID. pub target: NodeId, /// Edge type (relationship type), e.g., "FRIEND", "OWNS". pub edge_type: String, /// Properties stored as JSON. pub properties: JsonValue, /// Whether this is a directed edge. pub directed: bool, /// Weight for path-finding algorithms. pub weight: f64, /// Creation timestamp. pub created_at: u64, } impl Edge { /// Creates a new directed edge. pub fn new( source: NodeId, target: NodeId, edge_type: impl Into, properties: JsonValue, ) -> Self { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() as u64; Self { id: EdgeId::new(), source, target, edge_type: edge_type.into(), properties, directed: true, weight: 1.0, created_at: now, } } /// Creates an undirected edge. pub fn undirected( source: NodeId, target: NodeId, edge_type: impl Into, properties: JsonValue, ) -> Self { let mut edge = Self::new(source, target, edge_type, properties); edge.directed = false; edge } /// Sets the weight for this edge. pub fn with_weight(mut self, weight: f64) -> Self { self.weight = weight; self } /// Returns the other end of this edge from the given node. pub fn other_end(&self, from: &NodeId) -> Option { if &self.source == from { Some(self.target) } else if &self.target == from && !self.directed { Some(self.source) } else if &self.target == from { // For directed edges, can't traverse backward None } else { None } } /// Checks if this edge connects the given node (as source or target). pub fn connects(&self, node: &NodeId) -> bool { &self.source == node || &self.target == node } /// Checks if this edge connects two specific nodes. pub fn connects_pair(&self, a: &NodeId, b: &NodeId) -> bool { (&self.source == a && &self.target == b) || (!self.directed && &self.source == b && &self.target == a) } /// Gets a property value. pub fn get_property(&self, key: &str) -> Option<&JsonValue> { self.properties.get(key) } /// Sets a property value. pub fn set_property(&mut self, key: &str, value: JsonValue) { if let Some(obj) = self.properties.as_object_mut() { obj.insert(key.to_string(), value); } } /// Checks if the edge matches a property filter. pub fn matches_properties(&self, filter: &JsonValue) -> bool { if let (Some(filter_obj), Some(props_obj)) = (filter.as_object(), self.properties.as_object()) { for (key, expected) in filter_obj { if let Some(actual) = props_obj.get(key) { if actual != expected { return false; } } else { return false; } } true } else { filter == &self.properties || filter == &JsonValue::Object(serde_json::Map::new()) } } } /// Builder for creating edges. pub struct EdgeBuilder { source: NodeId, target: NodeId, edge_type: String, properties: serde_json::Map, directed: bool, weight: f64, } impl EdgeBuilder { /// Creates a new edge builder. pub fn new(source: NodeId, target: NodeId, edge_type: impl Into) -> Self { Self { source, target, edge_type: edge_type.into(), properties: serde_json::Map::new(), directed: true, weight: 1.0, } } /// Sets the edge as undirected. pub fn undirected(mut self) -> Self { self.directed = false; self } /// Sets the weight. pub fn weight(mut self, weight: f64) -> Self { self.weight = weight; self } /// Sets a property. pub fn property(mut self, key: impl Into, value: impl Into) -> Self { self.properties.insert(key.into(), value.into()); self } /// Builds the edge. pub fn build(self) -> Edge { let mut edge = Edge::new( self.source, self.target, self.edge_type, JsonValue::Object(self.properties), ); edge.directed = self.directed; edge.weight = self.weight; edge } } #[cfg(test)] mod tests { use super::*; #[test] fn test_edge_id() { let id1 = EdgeId::new(); let id2 = EdgeId::new(); assert_ne!(id1, id2); let hex = id1.to_hex(); let id3 = EdgeId::from_hex(&hex).unwrap(); assert_eq!(id1, id3); } #[test] fn test_edge_creation() { let source = NodeId::new(); let target = NodeId::new(); let edge = Edge::new(source, target, "FRIEND", serde_json::json!({"since": 2020})); assert_eq!(edge.source, source); assert_eq!(edge.target, target); assert_eq!(edge.edge_type, "FRIEND"); assert!(edge.directed); } #[test] fn test_edge_builder() { let source = NodeId::new(); let target = NodeId::new(); let edge = EdgeBuilder::new(source, target, "OWNS") .undirected() .weight(2.5) .property("percentage", 50) .build(); assert!(!edge.directed); assert_eq!(edge.weight, 2.5); assert_eq!( edge.get_property("percentage"), Some(&serde_json::json!(50)) ); } #[test] fn test_edge_other_end() { let source = NodeId::new(); let target = NodeId::new(); // Directed edge let directed = Edge::new(source, target, "A", serde_json::json!({})); assert_eq!(directed.other_end(&source), Some(target)); assert_eq!(directed.other_end(&target), None); // Can't traverse backward // Undirected edge let undirected = Edge::undirected(source, target, "B", serde_json::json!({})); assert_eq!(undirected.other_end(&source), Some(target)); assert_eq!(undirected.other_end(&target), Some(source)); } #[test] fn test_edge_connects() { let a = NodeId::new(); let b = NodeId::new(); let c = NodeId::new(); let edge = Edge::new(a, b, "LINK", serde_json::json!({})); assert!(edge.connects(&a)); assert!(edge.connects(&b)); assert!(!edge.connects(&c)); assert!(edge.connects_pair(&a, &b)); assert!(!edge.connects_pair(&b, &a)); // Directed } }