1use crate::edge::{Directedness, Edge};
4use crate::error::GraphError;
5
6#[derive(Debug, Clone, PartialEq)]
34pub struct Graph<N, W> {
35 pub nodes: Vec<N>,
37 pub edges: Vec<Edge<W>>,
39 pub directedness: Directedness,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Neighbor<'a, W> {
46 pub edge_id: usize,
48 pub node: usize,
50 pub weight: &'a W,
52}
53
54impl<N, W> Graph<N, W> {
55 pub fn new(directedness: Directedness) -> Self {
57 Graph {
58 nodes: Vec::new(),
59 edges: Vec::new(),
60 directedness,
61 }
62 }
63
64 pub fn with_nodes(nodes: Vec<N>, directedness: Directedness) -> Self {
66 Graph {
67 nodes,
68 edges: Vec::new(),
69 directedness,
70 }
71 }
72
73 pub fn node_count(&self) -> usize {
75 self.nodes.len()
76 }
77
78 pub fn edge_count(&self) -> usize {
80 self.edges.len()
81 }
82
83 pub fn is_directed(&self) -> bool {
85 matches!(self.directedness, Directedness::Directed)
86 }
87
88 pub fn add_node(&mut self, label: N) -> usize {
90 self.nodes.push(label);
91 self.nodes.len() - 1
92 }
93
94 pub fn add_edge(
96 &mut self,
97 source: usize,
98 target: usize,
99 weight: W,
100 ) -> Result<usize, GraphError> {
101 let n = self.nodes.len();
102 let id = self.edges.len();
103 for node in [source, target] {
104 if node >= n {
105 return Err(GraphError::InvalidEndpoint {
106 edge: id,
107 node,
108 len: n,
109 });
110 }
111 }
112 self.edges.push(Edge {
113 id,
114 source,
115 target,
116 weight,
117 });
118 Ok(id)
119 }
120
121 pub fn validate(&self) -> Result<(), GraphError> {
123 let n = self.nodes.len();
124 for e in &self.edges {
125 for node in [e.source, e.target] {
126 if node >= n {
127 return Err(GraphError::InvalidEndpoint {
128 edge: e.id,
129 node,
130 len: n,
131 });
132 }
133 }
134 }
135 Ok(())
136 }
137
138 pub fn neighbors(&self, node: usize) -> Result<Vec<Neighbor<'_, W>>, GraphError> {
141 if node >= self.nodes.len() {
142 return Err(GraphError::NodeOutOfRange {
143 node,
144 count: self.nodes.len(),
145 });
146 }
147 let directed = self.is_directed();
148 let mut out = Vec::new();
149 for e in &self.edges {
150 if e.source == node {
151 out.push(Neighbor {
152 edge_id: e.id,
153 node: e.target,
154 weight: &e.weight,
155 });
156 } else if !directed && e.target == node {
157 out.push(Neighbor {
158 edge_id: e.id,
159 node: e.source,
160 weight: &e.weight,
161 });
162 }
163 }
164 out.sort_by_key(|adj| (adj.node, adj.edge_id));
165 Ok(out)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn empty_graph_is_valid() {
175 let g: Graph<(), ()> = Graph::new(Directedness::Undirected);
176 assert_eq!(g.node_count(), 0);
177 assert_eq!(g.edge_count(), 0);
178 assert!(g.validate().is_ok());
179 }
180
181 #[test]
182 fn add_edge_rejects_invalid_endpoint() {
183 let mut g: Graph<&str, u64> = Graph::with_nodes(vec!["a", "b"], Directedness::Directed);
184 let r = g.add_edge(0, 5, 1);
185 assert!(matches!(
186 r,
187 Err(GraphError::InvalidEndpoint { node: 5, .. })
188 ));
189 }
190
191 #[test]
192 fn self_loop_and_multiedge_preserved() {
193 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1], Directedness::Undirected);
194 g.add_edge(0, 0, 1).unwrap(); g.add_edge(0, 1, 2).unwrap();
196 g.add_edge(0, 1, 3).unwrap(); assert_eq!(g.edge_count(), 3);
198 assert!(g.edges[0].is_self_loop());
199 }
200
201 #[test]
202 fn undirected_neighbors_expand_both_ways() {
203 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Undirected);
204 g.add_edge(0, 1, 10).unwrap();
205 g.add_edge(2, 1, 20).unwrap();
206 let n1: Vec<usize> = g.neighbors(1).unwrap().iter().map(|a| a.node).collect();
207 assert_eq!(n1, vec![0, 2]); }
209
210 #[test]
211 fn directed_neighbors_are_outgoing_only() {
212 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
213 g.add_edge(0, 1, 1).unwrap();
214 assert_eq!(g.neighbors(0).unwrap().len(), 1);
215 assert_eq!(g.neighbors(1).unwrap().len(), 0);
216 }
217}