aeo_graph_explorer/
graph.rs1use std::collections::HashMap;
4
5use petgraph::graph::{DiGraph, NodeIndex};
6use serde::{Deserialize, Serialize};
7
8use crate::error::GraphError;
9use crate::model::AeoNode;
10
11#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
13#[serde(rename_all = "snake_case")]
14pub enum EdgeKind {
15 DeclaresPeer,
17 CitesAuthority,
19}
20
21#[derive(Debug, Default)]
23pub struct AeoGraph {
24 graph: DiGraph<AeoNode, EdgeKind>,
25 index: HashMap<String, NodeIndex>,
26}
27
28impl AeoGraph {
29 pub fn from_jsonl(raw: &str) -> Result<Self, GraphError> {
33 let mut graph = Self::default();
34 for (line_idx, line) in raw.lines().enumerate() {
35 let line = line.trim();
36 if line.is_empty() {
37 continue;
38 }
39 let node: AeoNode = serde_json::from_str(line).map_err(|err| GraphError::JsonLine {
40 line: line_idx + 1,
41 source: err,
42 })?;
43 graph.upsert(node);
44 }
45 graph.wire_edges();
46 Ok(graph)
47 }
48
49 pub fn upsert(&mut self, node: AeoNode) -> NodeIndex {
52 if let Some(&idx) = self.index.get(&node.id) {
53 self.graph[idx] = node;
54 return idx;
55 }
56 let id = node.id.clone();
57 let idx = self.graph.add_node(node);
58 self.index.insert(id, idx);
59 idx
60 }
61
62 pub fn wire_edges(&mut self) {
64 let snapshot: Vec<(NodeIndex, AeoNode)> = self
66 .graph
67 .node_indices()
68 .map(|i| (i, self.graph[i].clone()))
69 .collect();
70
71 for (from_idx, node) in &snapshot {
72 if let Some(peers) = node.body.get("peers").and_then(|v| v.as_array()) {
74 for peer in peers {
75 if let Some(peer_id) = peer.get("id").and_then(|v| v.as_str()) {
76 if let Some(&peer_idx) = self.index.get(peer_id) {
77 self.graph
78 .add_edge(*from_idx, peer_idx, EdgeKind::DeclaresPeer);
79 }
80 }
81 }
82 }
83 if let Some(sources) = node
88 .body
89 .get("authority")
90 .and_then(|v| v.get("primary_sources"))
91 .and_then(|v| v.as_array())
92 {
93 for src in sources {
94 if let Some(url) = src.as_str() {
95 if let Some(&src_idx) = self.index.get(url) {
96 if src_idx != *from_idx {
97 self.graph
98 .add_edge(*from_idx, src_idx, EdgeKind::CitesAuthority);
99 }
100 }
101 }
102 }
103 }
104 }
105 }
106
107 pub fn node(&self, id: &str) -> Option<&AeoNode> {
109 self.index.get(id).map(|&i| &self.graph[i])
110 }
111
112 pub fn nodes(&self) -> impl Iterator<Item = &AeoNode> {
114 self.graph.node_indices().map(|i| &self.graph[i])
115 }
116
117 pub fn node_count(&self) -> usize {
119 self.graph.node_count()
120 }
121
122 pub fn edge_count(&self) -> usize {
124 self.graph.edge_count()
125 }
126
127 pub(crate) fn idx(&self, id: &str) -> Option<NodeIndex> {
128 self.index.get(id).copied()
129 }
130
131 pub(crate) fn raw(&self) -> &DiGraph<AeoNode, EdgeKind> {
132 &self.graph
133 }
134}