mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
107 lines
3.7 KiB
Rust
107 lines
3.7 KiB
Rust
//! petgraph-backed read-only view over a [`SurfaceMap`].
|
|
//!
|
|
//! The on-disk shape is two parallel `Vec`s (deterministic ordering,
|
|
//! byte-identical JSON), but downstream consumers — the Track G chain
|
|
//! composer, the `nyx surface` CLI walker — want graph queries:
|
|
//! neighbours, reachability, topological order. [`petgraph_view`]
|
|
//! constructs a `DiGraph<NodeRef<'_>, EdgeRef<'_>>` on demand without
|
|
//! cloning the underlying nodes or edges.
|
|
|
|
use super::{EdgeKind, SurfaceEdge, SurfaceMap, SurfaceNode};
|
|
use petgraph::graph::{DiGraph, NodeIndex};
|
|
use std::collections::HashMap;
|
|
|
|
/// Borrowed handle to one [`SurfaceNode`] inside the petgraph view.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct NodeRef<'a> {
|
|
pub idx: u32,
|
|
pub node: &'a SurfaceNode,
|
|
}
|
|
|
|
/// Borrowed handle to one [`SurfaceEdge`] inside the petgraph view.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct EdgeRef<'a> {
|
|
pub edge: &'a SurfaceEdge,
|
|
}
|
|
|
|
impl<'a> EdgeRef<'a> {
|
|
pub fn kind(&self) -> EdgeKind {
|
|
self.edge.kind
|
|
}
|
|
}
|
|
|
|
/// Materialise a petgraph view of `map`. Node indices in the returned
|
|
/// graph match `map.nodes` ordering 1:1, and the `lookup` map lets
|
|
/// callers translate from the surface index (`u32`) to the petgraph
|
|
/// [`NodeIndex`]. Walking edges respects `map.edges` order.
|
|
pub fn petgraph_view(map: &SurfaceMap) -> SurfaceGraphView<'_> {
|
|
let mut graph: DiGraph<NodeRef<'_>, EdgeRef<'_>> = DiGraph::new();
|
|
let mut lookup: HashMap<u32, NodeIndex> = HashMap::with_capacity(map.nodes.len());
|
|
for (i, node) in map.nodes.iter().enumerate() {
|
|
let nx = graph.add_node(NodeRef {
|
|
idx: i as u32,
|
|
node,
|
|
});
|
|
lookup.insert(i as u32, nx);
|
|
}
|
|
for edge in &map.edges {
|
|
if let (Some(&from), Some(&to)) = (lookup.get(&edge.from), lookup.get(&edge.to)) {
|
|
graph.add_edge(from, to, EdgeRef { edge });
|
|
}
|
|
}
|
|
SurfaceGraphView { graph, lookup }
|
|
}
|
|
|
|
/// petgraph view returned by [`petgraph_view`].
|
|
pub struct SurfaceGraphView<'a> {
|
|
pub graph: DiGraph<NodeRef<'a>, EdgeRef<'a>>,
|
|
pub lookup: HashMap<u32, NodeIndex>,
|
|
}
|
|
|
|
impl<'a> SurfaceGraphView<'a> {
|
|
/// Resolve a surface index back to its petgraph [`NodeIndex`].
|
|
pub fn node_index(&self, surface_idx: u32) -> Option<NodeIndex> {
|
|
self.lookup.get(&surface_idx).copied()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::entry_points::HttpMethod;
|
|
use crate::surface::{EntryPoint, Framework, SourceLocation};
|
|
|
|
#[test]
|
|
fn petgraph_view_preserves_indices() {
|
|
let mut m = SurfaceMap::new();
|
|
m.nodes.push(SurfaceNode::EntryPoint(EntryPoint {
|
|
location: SourceLocation::new("a.py", 1, 1),
|
|
framework: Framework::Flask,
|
|
method: HttpMethod::GET,
|
|
route: "/a".into(),
|
|
handler_name: "h".into(),
|
|
handler_location: SourceLocation::new("a.py", 2, 1),
|
|
auth_required: false,
|
|
}));
|
|
m.nodes.push(SurfaceNode::EntryPoint(EntryPoint {
|
|
location: SourceLocation::new("b.py", 1, 1),
|
|
framework: Framework::Flask,
|
|
method: HttpMethod::POST,
|
|
route: "/b".into(),
|
|
handler_name: "h".into(),
|
|
handler_location: SourceLocation::new("b.py", 2, 1),
|
|
auth_required: false,
|
|
}));
|
|
m.edges.push(SurfaceEdge {
|
|
from: 0,
|
|
to: 1,
|
|
kind: EdgeKind::Calls,
|
|
});
|
|
let view = petgraph_view(&m);
|
|
assert_eq!(view.graph.node_count(), 2);
|
|
assert_eq!(view.graph.edge_count(), 1);
|
|
let n0 = view.node_index(0).unwrap();
|
|
let n1 = view.node_index(1).unwrap();
|
|
assert!(view.graph.find_edge(n0, n1).is_some());
|
|
}
|
|
}
|