1use crate::id::BlockId;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::error::Error as StdError;
11use std::fmt;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct Edge {
17 pub edge_type: EdgeType,
19 pub target: BlockId,
21 #[serde(default, skip_serializing_if = "EdgeMetadata::is_empty")]
23 pub metadata: EdgeMetadata,
24 pub created_at: DateTime<Utc>,
26}
27
28impl Edge {
29 pub fn new(edge_type: EdgeType, target: BlockId) -> Self {
31 Self {
32 edge_type,
33 target,
34 metadata: EdgeMetadata::default(),
35 created_at: Utc::now(),
36 }
37 }
38
39 pub fn with_metadata(mut self, metadata: EdgeMetadata) -> Self {
41 self.metadata = metadata;
42 self
43 }
44
45 pub fn with_confidence(mut self, confidence: f32) -> Self {
47 self.metadata.confidence = Some(confidence);
48 self
49 }
50
51 pub fn with_description(mut self, description: impl Into<String>) -> Self {
53 self.metadata.description = Some(description.into());
54 self
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum EdgeType {
62 DerivedFrom,
65 Supersedes,
67 TransformedFrom,
69
70 References,
73 CitedBy,
75 LinksTo,
77
78 Supports,
81 Contradicts,
83 Elaborates,
85 Summarizes,
87
88 ParentOf,
91 ChildOf,
93 SiblingOf,
95 PreviousSibling,
97 NextSibling,
99
100 VersionOf,
103 AlternativeOf,
105 TranslationOf,
107
108 Custom(String),
110}
111
112impl EdgeType {
113 pub fn inverse(&self) -> Option<EdgeType> {
115 match self {
116 EdgeType::References => Some(EdgeType::CitedBy),
117 EdgeType::CitedBy => Some(EdgeType::References),
118 EdgeType::DerivedFrom => None, EdgeType::Supersedes => None,
120 EdgeType::ParentOf => Some(EdgeType::ChildOf),
121 EdgeType::ChildOf => Some(EdgeType::ParentOf),
122 EdgeType::PreviousSibling => Some(EdgeType::NextSibling),
123 EdgeType::NextSibling => Some(EdgeType::PreviousSibling),
124 EdgeType::Supports => None,
125 EdgeType::Contradicts => Some(EdgeType::Contradicts), EdgeType::SiblingOf => Some(EdgeType::SiblingOf), _ => None,
128 }
129 }
130
131 pub fn is_symmetric(&self) -> bool {
133 matches!(self, EdgeType::Contradicts | EdgeType::SiblingOf)
134 }
135
136 pub fn is_structural(&self) -> bool {
138 matches!(
139 self,
140 EdgeType::ParentOf
141 | EdgeType::ChildOf
142 | EdgeType::SiblingOf
143 | EdgeType::PreviousSibling
144 | EdgeType::NextSibling
145 )
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct EdgeTypeParseError(pub String);
151
152impl fmt::Display for EdgeTypeParseError {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "unknown edge type '{}'", self.0)
155 }
156}
157
158impl StdError for EdgeTypeParseError {}
159
160impl FromStr for EdgeType {
161 type Err = EdgeTypeParseError;
162
163 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
164 match s.to_lowercase().as_str() {
165 "derived_from" => Ok(EdgeType::DerivedFrom),
166 "supersedes" => Ok(EdgeType::Supersedes),
167 "transformed_from" => Ok(EdgeType::TransformedFrom),
168 "references" => Ok(EdgeType::References),
169 "cited_by" => Ok(EdgeType::CitedBy),
170 "links_to" => Ok(EdgeType::LinksTo),
171 "supports" => Ok(EdgeType::Supports),
172 "contradicts" => Ok(EdgeType::Contradicts),
173 "elaborates" => Ok(EdgeType::Elaborates),
174 "summarizes" => Ok(EdgeType::Summarizes),
175 "parent_of" => Ok(EdgeType::ParentOf),
176 "child_of" => Ok(EdgeType::ChildOf),
177 "sibling_of" => Ok(EdgeType::SiblingOf),
178 "previous_sibling" => Ok(EdgeType::PreviousSibling),
179 "next_sibling" => Ok(EdgeType::NextSibling),
180 "version_of" => Ok(EdgeType::VersionOf),
181 "alternative_of" => Ok(EdgeType::AlternativeOf),
182 "translation_of" => Ok(EdgeType::TranslationOf),
183 s if s.starts_with("custom:") => Ok(EdgeType::Custom(
184 s.strip_prefix("custom:").unwrap().to_string(),
185 )),
186 _ => Err(EdgeTypeParseError(s.to_string())),
187 }
188 }
189}
190
191impl EdgeType {
192 pub fn as_str(&self) -> String {
194 match self {
195 EdgeType::DerivedFrom => "derived_from".to_string(),
196 EdgeType::Supersedes => "supersedes".to_string(),
197 EdgeType::TransformedFrom => "transformed_from".to_string(),
198 EdgeType::References => "references".to_string(),
199 EdgeType::CitedBy => "cited_by".to_string(),
200 EdgeType::LinksTo => "links_to".to_string(),
201 EdgeType::Supports => "supports".to_string(),
202 EdgeType::Contradicts => "contradicts".to_string(),
203 EdgeType::Elaborates => "elaborates".to_string(),
204 EdgeType::Summarizes => "summarizes".to_string(),
205 EdgeType::ParentOf => "parent_of".to_string(),
206 EdgeType::ChildOf => "child_of".to_string(),
207 EdgeType::SiblingOf => "sibling_of".to_string(),
208 EdgeType::PreviousSibling => "previous_sibling".to_string(),
209 EdgeType::NextSibling => "next_sibling".to_string(),
210 EdgeType::VersionOf => "version_of".to_string(),
211 EdgeType::AlternativeOf => "alternative_of".to_string(),
212 EdgeType::TranslationOf => "translation_of".to_string(),
213 EdgeType::Custom(name) => format!("custom:{}", name),
214 }
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
220pub struct EdgeMetadata {
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub confidence: Option<f32>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub description: Option<String>,
227 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
229 pub custom: HashMap<String, serde_json::Value>,
230}
231
232impl EdgeMetadata {
233 pub fn new() -> Self {
234 Self::default()
235 }
236
237 pub fn is_empty(&self) -> bool {
238 self.confidence.is_none() && self.description.is_none() && self.custom.is_empty()
239 }
240
241 pub fn with_confidence(mut self, confidence: f32) -> Self {
242 self.confidence = Some(confidence.clamp(0.0, 1.0));
243 self
244 }
245
246 pub fn with_description(mut self, description: impl Into<String>) -> Self {
247 self.description = Some(description.into());
248 self
249 }
250
251 pub fn with_custom(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
252 self.custom.insert(key.into(), value);
253 self
254 }
255}
256
257#[derive(Debug, Clone, Default)]
259pub struct EdgeIndex {
260 outgoing: HashMap<BlockId, Vec<(EdgeType, BlockId)>>,
262 incoming: HashMap<BlockId, Vec<(EdgeType, BlockId)>>,
264}
265
266impl EdgeIndex {
267 pub fn new() -> Self {
268 Self::default()
269 }
270
271 pub fn add_edge(&mut self, source: &BlockId, edge: &Edge) {
273 self.outgoing
275 .entry(*source)
276 .or_default()
277 .push((edge.edge_type.clone(), edge.target));
278
279 if let Some(inv) = edge.edge_type.inverse() {
281 self.incoming
282 .entry(edge.target)
283 .or_default()
284 .push((inv, *source));
285 } else {
286 self.incoming
288 .entry(edge.target)
289 .or_default()
290 .push((edge.edge_type.clone(), *source));
291 }
292 }
293
294 pub fn remove_edge(&mut self, source: &BlockId, target: &BlockId, edge_type: &EdgeType) {
296 if let Some(edges) = self.outgoing.get_mut(source) {
297 edges.retain(|(t, tgt)| !(t == edge_type && tgt == target));
298 if edges.is_empty() {
299 self.outgoing.remove(source);
300 }
301 }
302
303 let incoming_type = edge_type.inverse().unwrap_or_else(|| edge_type.clone());
304 if let Some(edges) = self.incoming.get_mut(target) {
305 edges.retain(|(t, src)| !(t == &incoming_type && src == source));
306 if edges.is_empty() {
307 self.incoming.remove(target);
308 }
309 }
310 }
311
312 pub fn remove_block(&mut self, block_id: &BlockId) {
314 if let Some(edges) = self.outgoing.remove(block_id) {
316 for (edge_type, target) in edges {
317 let _incoming_type = edge_type.inverse().unwrap_or(edge_type);
318 if let Some(incoming) = self.incoming.get_mut(&target) {
319 incoming.retain(|(_, src)| src != block_id);
320 }
321 }
322 }
323
324 if let Some(edges) = self.incoming.remove(block_id) {
326 for (_, source) in edges {
327 if let Some(outgoing) = self.outgoing.get_mut(&source) {
328 outgoing.retain(|(_, tgt)| tgt != block_id);
329 }
330 }
331 }
332 }
333
334 pub fn outgoing_from(&self, source: &BlockId) -> &[(EdgeType, BlockId)] {
336 self.outgoing
337 .get(source)
338 .map(|v| v.as_slice())
339 .unwrap_or(&[])
340 }
341
342 pub fn incoming_to(&self, target: &BlockId) -> &[(EdgeType, BlockId)] {
344 self.incoming
345 .get(target)
346 .map(|v| v.as_slice())
347 .unwrap_or(&[])
348 }
349
350 pub fn outgoing_of_type(&self, source: &BlockId, edge_type: &EdgeType) -> Vec<BlockId> {
352 self.outgoing
353 .get(source)
354 .map(|edges| {
355 edges
356 .iter()
357 .filter(|(t, _)| t == edge_type)
358 .map(|(_, tgt)| *tgt)
359 .collect()
360 })
361 .unwrap_or_default()
362 }
363
364 pub fn incoming_of_type(&self, target: &BlockId, edge_type: &EdgeType) -> Vec<BlockId> {
366 self.incoming
367 .get(target)
368 .map(|edges| {
369 edges
370 .iter()
371 .filter(|(t, _)| t == edge_type)
372 .map(|(_, src)| *src)
373 .collect()
374 })
375 .unwrap_or_default()
376 }
377
378 pub fn has_edge(&self, source: &BlockId, target: &BlockId, edge_type: &EdgeType) -> bool {
380 self.outgoing
381 .get(source)
382 .map(|edges| edges.iter().any(|(t, tgt)| t == edge_type && tgt == target))
383 .unwrap_or(false)
384 }
385
386 pub fn edge_count(&self) -> usize {
388 self.outgoing.values().map(|v| v.len()).sum()
389 }
390
391 pub fn clear(&mut self) {
393 self.outgoing.clear();
394 self.incoming.clear();
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 fn make_id(n: u8) -> BlockId {
403 BlockId::from_bytes([n; 12])
404 }
405
406 #[test]
407 fn test_edge_creation() {
408 let edge = Edge::new(EdgeType::References, make_id(2))
409 .with_confidence(0.95)
410 .with_description("Important reference");
411
412 assert_eq!(edge.edge_type, EdgeType::References);
413 assert_eq!(edge.metadata.confidence, Some(0.95));
414 }
415
416 #[test]
417 fn test_edge_type_inverse() {
418 assert_eq!(EdgeType::References.inverse(), Some(EdgeType::CitedBy));
419 assert_eq!(EdgeType::ParentOf.inverse(), Some(EdgeType::ChildOf));
420 assert_eq!(EdgeType::DerivedFrom.inverse(), None);
421 }
422
423 #[test]
424 fn test_edge_type_parse() {
425 assert_eq!(
426 EdgeType::from_str("references").unwrap(),
427 EdgeType::References
428 );
429 assert_eq!(
430 EdgeType::from_str("custom:my_type").unwrap(),
431 EdgeType::Custom("my_type".to_string())
432 );
433 }
434
435 #[test]
436 fn test_edge_index_add_remove() {
437 let mut index = EdgeIndex::new();
438 let source = make_id(1);
439 let target = make_id(2);
440 let edge = Edge::new(EdgeType::References, target);
441
442 index.add_edge(&source, &edge);
443 assert!(index.has_edge(&source, &target, &EdgeType::References));
444 assert_eq!(index.edge_count(), 1);
445
446 index.remove_edge(&source, &target, &EdgeType::References);
447 assert!(!index.has_edge(&source, &target, &EdgeType::References));
448 }
449
450 #[test]
451 fn test_edge_index_traversal() {
452 let mut index = EdgeIndex::new();
453 let a = make_id(1);
454 let b = make_id(2);
455 let c = make_id(3);
456
457 index.add_edge(&a, &Edge::new(EdgeType::References, b));
458 index.add_edge(&a, &Edge::new(EdgeType::References, c));
459 index.add_edge(&b, &Edge::new(EdgeType::Supports, c));
460
461 let refs = index.outgoing_of_type(&a, &EdgeType::References);
462 assert_eq!(refs.len(), 2);
463
464 let incoming = index.incoming_to(&c);
465 assert_eq!(incoming.len(), 2);
466 }
467
468 #[test]
469 fn test_edge_index_remove_block() {
470 let mut index = EdgeIndex::new();
471 let a = make_id(1);
472 let b = make_id(2);
473 let c = make_id(3);
474
475 index.add_edge(&a, &Edge::new(EdgeType::References, b));
476 index.add_edge(&b, &Edge::new(EdgeType::References, c));
477
478 index.remove_block(&b);
479
480 assert!(!index.has_edge(&a, &b, &EdgeType::References));
481 assert!(!index.has_edge(&b, &c, &EdgeType::References));
482 }
483}