use super::{GraphEventSource, SourceError};
use crate::events::{EventNodeInfo, GraphEvent};
use dotparser::dot;
pub struct DotSource {
content: String,
}
impl DotSource {
pub fn new(content: String) -> Self {
Self { content }
}
pub fn from_content(content: &str) -> Self {
Self::new(content.to_string())
}
}
impl GraphEventSource for DotSource {
fn source_name(&self) -> &'static str {
"DOT"
}
fn events(&self) -> Result<Vec<GraphEvent>, SourceError> {
let dotparser_events = dot::parse(&self.content);
let mut events = Vec::new();
for event in dotparser_events {
match event {
dotparser::GraphEvent::BatchStart => {
events.push(GraphEvent::BatchStart);
}
dotparser::GraphEvent::BatchEnd => {
events.push(GraphEvent::BatchEnd);
}
dotparser::GraphEvent::AddNode {
id,
label,
node_type,
properties,
} => {
let info = EventNodeInfo {
name: label.unwrap_or_else(|| id.clone()),
node_type: match node_type {
dotparser::NodeType::Custom(t) => Some(t),
_ => None,
},
level: match properties.position {
Some(dotparser::Position::Layer { level }) => level,
_ => 0,
},
};
events.push(GraphEvent::AddNode { id, info });
}
dotparser::GraphEvent::AddEdge { from, to, .. } => {
events.push(GraphEvent::AddEdge { from, to });
}
_ => {
}
}
}
Ok(events)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph_state::GraphState;
use std::collections::HashSet;
#[test]
fn test_simple_dot_to_events() {
let dot_content = r"
digraph {
A -> B;
B -> C;
}
";
let source = DotSource::from_content(dot_content);
let events = source.events().unwrap();
assert_eq!(events.len(), 7);
assert!(matches!(events.first(), Some(GraphEvent::BatchStart)));
assert!(matches!(events.last(), Some(GraphEvent::BatchEnd)));
let node_count = events
.iter()
.filter(|e| matches!(e, GraphEvent::AddNode { .. }))
.count();
let edge_count = events
.iter()
.filter(|e| matches!(e, GraphEvent::AddEdge { .. }))
.count();
assert_eq!(node_count, 3);
assert_eq!(edge_count, 2);
}
#[test]
fn test_duplicate_node_names() {
let dot_content = r#"
digraph {
Server -> Database;
Server -> Cache;
"Server" -> Queue;
}
"#;
let source = DotSource::from_content(dot_content);
let events = source.events().unwrap();
let node_events: Vec<_> = events
.iter()
.filter_map(|e| match e {
GraphEvent::AddNode { id, .. } => Some(id.clone()),
_ => None,
})
.collect();
let unique_ids: HashSet<_> = node_events.iter().cloned().collect();
assert_eq!(node_events.len(), unique_ids.len());
}
#[test]
fn test_event_stream_with_attributes() {
let dot_content = r#"
digraph {
A [type="team", level="2"];
B [type="user", level="1"];
C [type="user", level="1"];
A -> B;
A -> C;
B -> C;
}
"#;
let source = DotSource::from_content(dot_content);
let events = source.events().unwrap();
let mut state = GraphState::new();
state.process_events(events);
let event_graph = state.as_graph_data();
assert_eq!(event_graph.graph.node_count(), 3);
assert_eq!(event_graph.graph.edge_count(), 3);
assert!(event_graph.node_map.contains_key("A"));
assert!(event_graph.node_map.contains_key("B"));
assert!(event_graph.node_map.contains_key("C"));
let a_idx = event_graph.node_map["A"];
let a_node = &event_graph.graph[a_idx];
assert_eq!(a_node.name, "A");
assert_eq!(a_node.node_type, Some("team".to_string()));
assert_eq!(a_node.level, 2);
let b_idx = event_graph.node_map["B"];
let b_node = &event_graph.graph[b_idx];
assert_eq!(b_node.name, "B");
assert_eq!(b_node.node_type, Some("user".to_string()));
assert_eq!(b_node.level, 1);
}
}