use std::collections::HashMap;
use petgraph::graph::{DiGraph, NodeIndex};
use serde::{Deserialize, Serialize};
use crate::error::GraphError;
use crate::model::AeoNode;
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EdgeKind {
DeclaresPeer,
CitesAuthority,
}
#[derive(Debug, Default)]
pub struct AeoGraph {
graph: DiGraph<AeoNode, EdgeKind>,
index: HashMap<String, NodeIndex>,
}
impl AeoGraph {
pub fn from_jsonl(raw: &str) -> Result<Self, GraphError> {
let mut graph = Self::default();
for (line_idx, line) in raw.lines().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
let node: AeoNode = serde_json::from_str(line).map_err(|err| GraphError::JsonLine {
line: line_idx + 1,
source: err,
})?;
graph.upsert(node);
}
graph.wire_edges();
Ok(graph)
}
pub fn upsert(&mut self, node: AeoNode) -> NodeIndex {
if let Some(&idx) = self.index.get(&node.id) {
self.graph[idx] = node;
return idx;
}
let id = node.id.clone();
let idx = self.graph.add_node(node);
self.index.insert(id, idx);
idx
}
pub fn wire_edges(&mut self) {
let snapshot: Vec<(NodeIndex, AeoNode)> = self
.graph
.node_indices()
.map(|i| (i, self.graph[i].clone()))
.collect();
for (from_idx, node) in &snapshot {
if let Some(peers) = node.body.get("peers").and_then(|v| v.as_array()) {
for peer in peers {
if let Some(peer_id) = peer.get("id").and_then(|v| v.as_str()) {
if let Some(&peer_idx) = self.index.get(peer_id) {
self.graph
.add_edge(*from_idx, peer_idx, EdgeKind::DeclaresPeer);
}
}
}
}
if let Some(sources) = node
.body
.get("authority")
.and_then(|v| v.get("primary_sources"))
.and_then(|v| v.as_array())
{
for src in sources {
if let Some(url) = src.as_str() {
if let Some(&src_idx) = self.index.get(url) {
if src_idx != *from_idx {
self.graph
.add_edge(*from_idx, src_idx, EdgeKind::CitesAuthority);
}
}
}
}
}
}
}
pub fn node(&self, id: &str) -> Option<&AeoNode> {
self.index.get(id).map(|&i| &self.graph[i])
}
pub fn nodes(&self) -> impl Iterator<Item = &AeoNode> {
self.graph.node_indices().map(|i| &self.graph[i])
}
pub fn node_count(&self) -> usize {
self.graph.node_count()
}
pub fn edge_count(&self) -> usize {
self.graph.edge_count()
}
pub(crate) fn idx(&self, id: &str) -> Option<NodeIndex> {
self.index.get(id).copied()
}
pub(crate) fn raw(&self) -> &DiGraph<AeoNode, EdgeKind> {
&self.graph
}
}