1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[repr(u8)]
9pub enum EdgeKind {
10 DocToChunk = 0,
12
13 ChunkToEmbedding = 1,
15
16 ChunkToChunk = 2,
18
19 DocToSummary = 3,
21
22 DocToDoc = 4,
24}
25
26impl EdgeKind {
27 pub fn from_u8(value: u8) -> Option<Self> {
29 match value {
30 0 => Some(Self::DocToChunk),
31 1 => Some(Self::ChunkToEmbedding),
32 2 => Some(Self::ChunkToChunk),
33 3 => Some(Self::DocToSummary),
34 4 => Some(Self::DocToDoc),
35 _ => None,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct Edge {
46 pub source: Uuid,
48
49 pub target: Uuid,
51
52 pub kind: EdgeKind,
54
55 pub weight: Option<f32>,
58
59 pub metadata: Option<String>,
61}
62
63impl Edge {
64 pub fn new(source: Uuid, target: Uuid, kind: EdgeKind) -> Self {
66 Self {
67 source,
68 target,
69 kind,
70 weight: None,
71 metadata: None,
72 }
73 }
74
75 pub fn with_weight(source: Uuid, target: Uuid, kind: EdgeKind, weight: f32) -> Self {
77 Self {
78 source,
79 target,
80 kind,
81 weight: Some(weight),
82 metadata: None,
83 }
84 }
85
86 pub fn doc_to_chunk(doc_id: Uuid, chunk_id: Uuid) -> Self {
88 Self::new(doc_id, chunk_id, EdgeKind::DocToChunk)
89 }
90
91 pub fn chunk_to_embedding(chunk_id: Uuid, embedding_id: Uuid) -> Self {
93 Self::new(chunk_id, embedding_id, EdgeKind::ChunkToEmbedding)
94 }
95}
96
97impl Eq for Edge {}
98
99impl std::hash::Hash for Edge {
100 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101 self.source.hash(state);
102 self.target.hash(state);
103 self.kind.hash(state);
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_edge_creation() {
113 let src = Uuid::from_bytes([1u8; 16]);
114 let tgt = Uuid::from_bytes([2u8; 16]);
115
116 let edge = Edge::doc_to_chunk(src, tgt);
117 assert_eq!(edge.source, src);
118 assert_eq!(edge.target, tgt);
119 assert_eq!(edge.kind, EdgeKind::DocToChunk);
120 assert!(edge.weight.is_none());
121 assert!(edge.metadata.is_none());
122 }
123
124 #[test]
125 fn test_weighted_edge() {
126 let edge = Edge::with_weight(
127 Uuid::from_bytes([1u8; 16]),
128 Uuid::from_bytes([2u8; 16]),
129 EdgeKind::ChunkToChunk,
130 0.85,
131 );
132
133 assert!((edge.weight.unwrap() - 0.85).abs() < 0.001);
134 }
135
136 #[test]
137 fn test_edge_kind_roundtrip() {
138 for kind in [
139 EdgeKind::DocToChunk,
140 EdgeKind::ChunkToEmbedding,
141 EdgeKind::ChunkToChunk,
142 EdgeKind::DocToSummary,
143 EdgeKind::DocToDoc,
144 ] {
145 let value = kind as u8;
146 assert_eq!(EdgeKind::from_u8(value), Some(kind));
147 }
148 }
149
150 #[test]
151 fn test_edge_with_metadata() {
152 let mut edge = Edge::new(
153 Uuid::from_bytes([1u8; 16]),
154 Uuid::from_bytes([2u8; 16]),
155 EdgeKind::ChunkToChunk,
156 );
157 edge.metadata = Some("related by topic".to_string());
158 assert_eq!(edge.metadata.as_deref(), Some("related by topic"));
159 }
160
161 #[test]
164 fn test_edge_new_doc_to_chunk() {
165 let doc_id = Uuid::from_bytes([1u8; 16]);
166 let chunk_id = Uuid::from_bytes([2u8; 16]);
167
168 let edge = Edge::doc_to_chunk(doc_id, chunk_id);
169
170 assert_eq!(edge.source, doc_id);
171 assert_eq!(edge.target, chunk_id);
172 assert_eq!(edge.kind, EdgeKind::DocToChunk);
173 }
174
175 #[test]
176 fn test_edge_new_chunk_to_embedding() {
177 let chunk_id = Uuid::from_bytes([1u8; 16]);
178 let embedding_id = Uuid::from_bytes([2u8; 16]);
179
180 let edge = Edge::chunk_to_embedding(chunk_id, embedding_id);
181
182 assert_eq!(edge.source, chunk_id);
183 assert_eq!(edge.target, embedding_id);
184 assert_eq!(edge.kind, EdgeKind::ChunkToEmbedding);
185 }
186
187 #[test]
188 fn test_edge_new_chunk_to_chunk() {
189 let chunk1 = Uuid::from_bytes([1u8; 16]);
190 let chunk2 = Uuid::from_bytes([2u8; 16]);
191
192 let edge = Edge::new(chunk1, chunk2, EdgeKind::ChunkToChunk);
193
194 assert_eq!(edge.source, chunk1);
195 assert_eq!(edge.target, chunk2);
196 assert_eq!(edge.kind, EdgeKind::ChunkToChunk);
197 }
198
199 #[test]
200 fn test_edge_with_weight() {
201 let edge = Edge::with_weight(
202 Uuid::from_bytes([1u8; 16]),
203 Uuid::from_bytes([2u8; 16]),
204 EdgeKind::ChunkToChunk,
205 0.95,
206 );
207
208 assert!(edge.weight.is_some());
209 assert!((edge.weight.unwrap() - 0.95).abs() < 0.001);
210 }
211
212 #[test]
213 fn test_edge_custom_kind() {
214 let kinds = vec![
216 EdgeKind::DocToChunk,
217 EdgeKind::ChunkToEmbedding,
218 EdgeKind::ChunkToChunk,
219 EdgeKind::DocToSummary,
220 EdgeKind::DocToDoc,
221 ];
222
223 for kind in kinds {
224 let edge = Edge::new(
225 Uuid::from_bytes([1u8; 16]),
226 Uuid::from_bytes([2u8; 16]),
227 kind,
228 );
229 assert_eq!(edge.kind, kind);
230 }
231 }
232
233 #[test]
234 fn test_edge_canonical_bytes() {
235 let edge = Edge::new(
236 Uuid::from_bytes([1u8; 16]),
237 Uuid::from_bytes([2u8; 16]),
238 EdgeKind::DocToChunk,
239 );
240
241 assert_eq!(edge.source.as_bytes().len(), 16);
243 assert_eq!(edge.target.as_bytes().len(), 16);
244 }
245
246 #[test]
247 fn test_edge_uniqueness_constraint() {
248 let edge1 = Edge::new(
250 Uuid::from_bytes([1u8; 16]),
251 Uuid::from_bytes([2u8; 16]),
252 EdgeKind::DocToChunk,
253 );
254 let edge2 = Edge::new(
255 Uuid::from_bytes([1u8; 16]),
256 Uuid::from_bytes([2u8; 16]),
257 EdgeKind::DocToChunk,
258 );
259 let edge3 = Edge::new(
260 Uuid::from_bytes([1u8; 16]),
261 Uuid::from_bytes([2u8; 16]),
262 EdgeKind::ChunkToChunk, );
264
265 assert_eq!(edge1, edge2);
267 assert_ne!(edge1, edge3);
269 }
270
271 #[test]
272 fn test_edge_kind_serialization() {
273 for kind in [
275 EdgeKind::DocToChunk,
276 EdgeKind::ChunkToEmbedding,
277 EdgeKind::ChunkToChunk,
278 EdgeKind::DocToSummary,
279 EdgeKind::DocToDoc,
280 ] {
281 let value = kind as u8;
282 assert_eq!(EdgeKind::from_u8(value), Some(kind));
283 }
284
285 assert_eq!(EdgeKind::from_u8(255), None);
287 }
288
289 #[test]
290 fn test_edge_hash() {
291 use std::collections::HashSet;
292
293 let mut set = HashSet::new();
294 let edge1 = Edge::new(
295 Uuid::from_bytes([1u8; 16]),
296 Uuid::from_bytes([2u8; 16]),
297 EdgeKind::DocToChunk,
298 );
299 let edge2 = Edge::new(
300 Uuid::from_bytes([1u8; 16]),
301 Uuid::from_bytes([2u8; 16]),
302 EdgeKind::DocToChunk,
303 );
304
305 set.insert(edge1.clone());
306 assert!(set.contains(&edge2)); }
308}