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> {
127 let n = self.nodes.len();
128 let edge_count = self.edges.len();
129 for (index, e) in self.edges.iter().enumerate() {
130 if e.id != index {
131 return Err(GraphError::InvalidEdgeId {
132 index,
133 id: e.id,
134 len: edge_count,
135 });
136 }
137 for node in [e.source, e.target] {
138 if node >= n {
139 return Err(GraphError::InvalidEndpoint {
140 edge: e.id,
141 node,
142 len: n,
143 });
144 }
145 }
146 }
147 Ok(())
148 }
149
150 pub fn neighbors(&self, node: usize) -> Result<Vec<Neighbor<'_, W>>, GraphError> {
153 if node >= self.nodes.len() {
154 return Err(GraphError::NodeOutOfRange {
155 node,
156 count: self.nodes.len(),
157 });
158 }
159 let directed = self.is_directed();
160 let mut out = Vec::new();
161 for e in &self.edges {
162 if e.source == node {
163 out.push(Neighbor {
164 edge_id: e.id,
165 node: e.target,
166 weight: &e.weight,
167 });
168 } else if !directed && e.target == node {
169 out.push(Neighbor {
170 edge_id: e.id,
171 node: e.source,
172 weight: &e.weight,
173 });
174 }
175 }
176 out.sort_by_key(|adj| (adj.node, adj.edge_id));
177 Ok(out)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn empty_graph_is_valid() {
187 let g: Graph<(), ()> = Graph::new(Directedness::Undirected);
188 assert_eq!(g.node_count(), 0);
189 assert_eq!(g.edge_count(), 0);
190 assert!(g.validate().is_ok());
191 }
192
193 #[test]
194 fn add_edge_rejects_invalid_endpoint() {
195 let mut g: Graph<&str, u64> = Graph::with_nodes(vec!["a", "b"], Directedness::Directed);
196 let r = g.add_edge(0, 5, 1);
197 assert!(matches!(
198 r,
199 Err(GraphError::InvalidEndpoint { node: 5, .. })
200 ));
201 }
202
203 #[test]
204 fn validate_rejects_sparse_edge_id() {
205 let g = Graph {
206 nodes: vec![0, 1],
207 edges: vec![Edge {
208 id: 2,
209 source: 0,
210 target: 1,
211 weight: 7,
212 }],
213 directedness: Directedness::Directed,
214 };
215
216 assert!(matches!(
217 g.validate(),
218 Err(GraphError::InvalidEdgeId {
219 index: 0,
220 id: 2,
221 len: 1,
222 })
223 ));
224 }
225
226 #[test]
227 fn validate_rejects_duplicate_edge_id() {
228 let g = Graph {
229 nodes: vec![0, 1, 2],
230 edges: vec![
231 Edge {
232 id: 0,
233 source: 0,
234 target: 1,
235 weight: 7,
236 },
237 Edge {
238 id: 0,
239 source: 1,
240 target: 2,
241 weight: 9,
242 },
243 ],
244 directedness: Directedness::Directed,
245 };
246
247 assert!(matches!(
248 g.validate(),
249 Err(GraphError::InvalidEdgeId {
250 index: 1,
251 id: 0,
252 len: 2,
253 })
254 ));
255 }
256
257 #[test]
258 fn self_loop_and_multiedge_preserved() {
259 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1], Directedness::Undirected);
260 g.add_edge(0, 0, 1).unwrap(); g.add_edge(0, 1, 2).unwrap();
262 g.add_edge(0, 1, 3).unwrap(); assert_eq!(g.edge_count(), 3);
264 assert!(g.edges[0].is_self_loop());
265 }
266
267 #[test]
268 fn undirected_neighbors_expand_both_ways() {
269 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Undirected);
270 g.add_edge(0, 1, 10).unwrap();
271 g.add_edge(2, 1, 20).unwrap();
272 let n1: Vec<usize> = g.neighbors(1).unwrap().iter().map(|a| a.node).collect();
273 assert_eq!(n1, vec![0, 2]); }
275
276 #[test]
277 fn directed_neighbors_are_outgoing_only() {
278 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
279 g.add_edge(0, 1, 1).unwrap();
280 assert_eq!(g.neighbors(0).unwrap().len(), 1);
281 assert_eq!(g.neighbors(1).unwrap().len(), 0);
282 }
283}