use crate::{Edge, GraphError, Node, NodeId, Result, SourceSpan};
use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Graph {
nodes: Vec<Node>,
edges: Vec<Edge>,
}
impl Graph {
pub fn try_from_parts(
nodes: impl IntoIterator<Item = Node>,
edges: impl IntoIterator<Item = Edge>,
) -> Result<Self> {
let mut builder = GraphBuilder::new();
for node in nodes {
builder.add_node(node)?;
}
for edge in edges {
builder.add_edge(edge)?;
}
builder.build()
}
#[must_use]
pub fn nodes(&self) -> &[Node] {
&self.nodes
}
#[must_use]
pub fn edges(&self) -> &[Edge] {
&self.edges
}
#[must_use]
pub fn node(&self, id: &str) -> Option<&Node> {
self.nodes
.binary_search_by(|node| node.id.as_str().cmp(id))
.ok()
.map(|index| &self.nodes[index])
}
pub fn outgoing<'graph>(
&'graph self,
id: &'graph NodeId,
) -> impl Iterator<Item = &'graph Edge> {
self.edges.iter().filter(move |edge| &edge.source == id)
}
pub fn incoming<'graph>(
&'graph self,
id: &'graph NodeId,
) -> impl Iterator<Item = &'graph Edge> {
self.edges.iter().filter(move |edge| &edge.target == id)
}
#[must_use]
pub const fn node_count(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub const fn edge_count(&self) -> usize {
self.edges.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.nodes.is_empty() && self.edges.is_empty()
}
#[must_use]
pub fn into_parts(self) -> (Vec<Node>, Vec<Edge>) {
(self.nodes, self.edges)
}
}
#[derive(Deserialize)]
struct GraphWire {
nodes: Vec<Node>,
edges: Vec<Edge>,
}
impl<'de> Deserialize<'de> for Graph {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire = GraphWire::deserialize(deserializer)?;
Self::try_from_parts(wire.nodes, wire.edges).map_err(D::Error::custom)
}
}
#[derive(Debug, Default)]
pub struct GraphBuilder {
nodes: BTreeMap<NodeId, Node>,
edges: BTreeSet<Edge>,
}
impl GraphBuilder {
#[must_use]
pub const fn new() -> Self {
Self {
nodes: BTreeMap::new(),
edges: BTreeSet::new(),
}
}
pub fn add_node(&mut self, node: Node) -> Result<&mut Self> {
if let Some(span) = &node.span {
validate_span(span)?;
}
if let Some(language) = &node.language {
validate_language(language)?;
}
if let Some(existing) = self.nodes.get(&node.id) {
if existing == &node {
return Ok(self);
}
return Err(GraphError::ConflictingNode {
id: node.id.to_string(),
});
}
self.nodes.insert(node.id.clone(), node);
Ok(self)
}
pub fn add_edge(&mut self, edge: Edge) -> Result<&mut Self> {
if edge.provenance.extractor.is_empty() {
return Err(GraphError::EmptyExtractor);
}
if let Some(span) = &edge.provenance.span {
validate_span(span)?;
}
self.edges.insert(edge);
Ok(self)
}
pub fn build(self) -> Result<Graph> {
for edge in &self.edges {
if !self.nodes.contains_key(&edge.source) {
return Err(GraphError::MissingEdgeSource {
id: edge.source.to_string(),
});
}
if !self.nodes.contains_key(&edge.target) {
return Err(GraphError::MissingEdgeTarget {
id: edge.target.to_string(),
});
}
}
Ok(Graph {
nodes: self.nodes.into_values().collect(),
edges: self.edges.into_iter().collect(),
})
}
}
fn validate_language(language: &str) -> Result<()> {
if language.is_empty() || language.trim() != language {
return Err(GraphError::InvalidKind {
category: "language",
value: language.to_owned(),
});
}
Ok(())
}
fn validate_span(span: &SourceSpan) -> Result<()> {
if span.file.is_empty() {
return Err(GraphError::InvalidSpan {
file: span.file.clone(),
reason: "file must not be empty",
});
}
if span.start.line == 0 || span.start.column == 0 || span.end.line == 0 || span.end.column == 0
{
return Err(GraphError::InvalidSpan {
file: span.file.clone(),
reason: "positions are one-based",
});
}
if span.end < span.start {
return Err(GraphError::InvalidSpan {
file: span.file.clone(),
reason: "end precedes start",
});
}
Ok(())
}