From 7b5de2b702ee5d14a1ffb53ab94ce284ee902d6c Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 5 Sep 2026 20:39:57 -0400 Subject: [PATCH] refactor(ariadne): split graph artifact modules --- src/ariadne/BUILD | 6 +- src/ariadne/src/artifact.rs | 94 ++++++++++++ src/ariadne/src/graph.rs | 192 ++++++++++++++++++++++++ src/ariadne/src/lib.rs | 284 +----------------------------------- 4 files changed, 295 insertions(+), 281 deletions(-) create mode 100644 src/ariadne/src/artifact.rs create mode 100644 src/ariadne/src/graph.rs diff --git a/src/ariadne/BUILD b/src/ariadne/BUILD index 1513316..8c9b835 100644 --- a/src/ariadne/BUILD +++ b/src/ariadne/BUILD @@ -3,7 +3,11 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") rust_library( name = "ariadne_lib", crate_name = "ariadne", - srcs = ["src/lib.rs"], + srcs = [ + "src/artifact.rs", + "src/graph.rs", + "src/lib.rs", + ], deps = [ "//third_party/crates:serde", "//third_party/crates:serde_json", diff --git a/src/ariadne/src/artifact.rs b/src/ariadne/src/artifact.rs new file mode 100644 index 0000000..0543173 --- /dev/null +++ b/src/ariadne/src/artifact.rs @@ -0,0 +1,94 @@ +use std::error::Error; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::{Delta, Edge, Graph, Node}; + +pub fn write_graph_artifact( + directory: impl AsRef, + graph: &Graph, + delta: &[Delta], +) -> std::io::Result<()> { + let directory = directory.as_ref(); + fs::create_dir_all(directory)?; + + write_json_file(directory.join("metadata.json"), &graph.metadata)?; + write_jsonl_file(directory.join("nodes.jsonl"), graph.nodes.values())?; + write_jsonl_file(directory.join("edges.jsonl"), graph.edges.iter())?; + write_jsonl_file(directory.join("delta.jsonl"), delta.iter())?; + + Ok(()) +} + +pub fn read_graph_artifact(directory: impl AsRef) -> std::io::Result { + let directory = directory.as_ref(); + let metadata = read_json_file(directory.join("metadata.json"))?; + let nodes: Vec = read_jsonl_file(directory.join("nodes.jsonl"))?; + let edges: Vec = read_jsonl_file(directory.join("edges.jsonl"))?; + + let mut graph = Graph::new(metadata); + for node in nodes { + graph.add_node(node).map_err(invalid_data)?; + } + for edge in edges { + graph.add_edge(edge).map_err(invalid_data)?; + } + + Ok(graph) +} + +pub fn read_graph_delta(directory: impl AsRef) -> std::io::Result> { + read_jsonl_file(directory.as_ref().join("delta.jsonl")) +} + +fn write_json_file(path: impl AsRef, value: &impl Serialize) -> std::io::Result<()> { + let file = File::create(path)?; + let writer = BufWriter::new(file); + serde_json::to_writer_pretty(writer, value).map_err(invalid_data) +} + +fn read_json_file Deserialize<'de>>(path: impl AsRef) -> std::io::Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).map_err(invalid_data) +} + +fn write_jsonl_file<'a, T: 'a + Serialize>( + path: impl AsRef, + values: impl IntoIterator, +) -> std::io::Result<()> { + let file = File::create(path)?; + let mut writer = BufWriter::new(file); + + for value in values { + serde_json::to_writer(&mut writer, value).map_err(invalid_data)?; + writer.write_all(b"\n")?; + } + + Ok(()) +} + +fn read_jsonl_file Deserialize<'de>>( + path: impl AsRef, +) -> std::io::Result> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut values = Vec::new(); + + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + values.push(serde_json::from_str(&line).map_err(invalid_data)?); + } + + Ok(values) +} + +fn invalid_data(error: impl Error + Send + Sync + 'static) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::InvalidData, error) +} diff --git a/src/ariadne/src/graph.rs b/src/ariadne/src/graph.rs new file mode 100644 index 0000000..6826cd5 --- /dev/null +++ b/src/ariadne/src/graph.rs @@ -0,0 +1,192 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::error::Error; +use std::fmt::{Display, Formatter}; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +pub type Metadata = BTreeMap; + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct NodeId(String); + +impl NodeId { + pub fn new(value: impl Into) -> Self { + Self(value.into()) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From<&str> for NodeId { + fn from(value: &str) -> Self { + Self::new(value) + } +} + +impl From for NodeId { + fn from(value: String) -> Self { + Self::new(value) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct SourceRef { + pub path: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub line: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub column: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Node { + pub id: NodeId, + pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + pub payload: Value, +} + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct Edge { + pub from: NodeId, + pub to: NodeId, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "op", rename_all = "snake_case")] +pub enum Delta { + AddNode { node: Node }, + RemoveNode { node: Node }, + ReplaceNode { old: Node, new: Node }, + AddEdge { edge: Edge }, + RemoveEdge { edge: Edge }, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Graph { + pub metadata: Metadata, + pub nodes: BTreeMap, + pub edges: BTreeSet, +} + +impl Graph { + pub fn new(metadata: Metadata) -> Self { + Self { + metadata, + nodes: BTreeMap::new(), + edges: BTreeSet::new(), + } + } + + pub fn add_node(&mut self, node: Node) -> Result { + if self.nodes.contains_key(&node.id) { + return Err(GraphError::DuplicateNode { id: node.id }); + } + + self.nodes.insert(node.id.clone(), node.clone()); + Ok(Delta::AddNode { node }) + } + + pub fn remove_node(&mut self, id: &NodeId) -> Result, GraphError> { + let node = self + .nodes + .remove(id) + .ok_or_else(|| GraphError::MissingNode { id: id.clone() })?; + + let incident_edges: Vec<_> = self + .edges + .iter() + .filter(|edge| edge.from == *id || edge.to == *id) + .cloned() + .collect(); + + let mut deltas = Vec::with_capacity(incident_edges.len() + 1); + for edge in incident_edges { + self.edges.remove(&edge); + deltas.push(Delta::RemoveEdge { edge }); + } + deltas.push(Delta::RemoveNode { node }); + + Ok(deltas) + } + + pub fn replace_node(&mut self, node: Node) -> Result { + let old = self + .nodes + .get_mut(&node.id) + .ok_or_else(|| GraphError::MissingNode { + id: node.id.clone(), + })?; + let previous = std::mem::replace(old, node.clone()); + + Ok(Delta::ReplaceNode { + old: previous, + new: node, + }) + } + + pub fn add_edge(&mut self, edge: Edge) -> Result { + self.require_node(&edge.from)?; + self.require_node(&edge.to)?; + + if !self.edges.insert(edge.clone()) { + return Err(GraphError::DuplicateEdge { edge }); + } + + Ok(Delta::AddEdge { edge }) + } + + pub fn remove_edge(&mut self, edge: &Edge) -> Result { + if !self.edges.remove(edge) { + return Err(GraphError::MissingEdge { edge: edge.clone() }); + } + + Ok(Delta::RemoveEdge { edge: edge.clone() }) + } + + fn require_node(&self, id: &NodeId) -> Result<(), GraphError> { + if self.nodes.contains_key(id) { + Ok(()) + } else { + Err(GraphError::MissingNode { id: id.clone() }) + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum GraphError { + DuplicateNode { id: NodeId }, + MissingNode { id: NodeId }, + DuplicateEdge { edge: Edge }, + MissingEdge { edge: Edge }, +} + +impl Display for GraphError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + match self { + GraphError::DuplicateNode { id } => { + write!(formatter, "duplicate node `{}`", id.as_str()) + } + GraphError::MissingNode { id } => write!(formatter, "missing node `{}`", id.as_str()), + GraphError::DuplicateEdge { edge } => write!( + formatter, + "duplicate edge `{} -> {}`", + edge.from.as_str(), + edge.to.as_str() + ), + GraphError::MissingEdge { edge } => write!( + formatter, + "missing edge `{} -> {}`", + edge.from.as_str(), + edge.to.as_str() + ), + } + } +} + +impl Error for GraphError {} diff --git a/src/ariadne/src/lib.rs b/src/ariadne/src/lib.rs index 5e6ec8d..a34bfc0 100644 --- a/src/ariadne/src/lib.rs +++ b/src/ariadne/src/lib.rs @@ -1,284 +1,8 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::fs::{self, File}; -use std::io::{BufRead, BufReader, BufWriter, Write}; -use std::path::Path; +mod artifact; +mod graph; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -pub type Metadata = BTreeMap; - -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct NodeId(String); - -impl NodeId { - pub fn new(value: impl Into) -> Self { - Self(value.into()) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl From<&str> for NodeId { - fn from(value: &str) -> Self { - Self::new(value) - } -} - -impl From for NodeId { - fn from(value: String) -> Self { - Self::new(value) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct SourceRef { - pub path: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub line: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub column: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Node { - pub id: NodeId, - pub kind: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - pub payload: Value, -} - -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Edge { - pub from: NodeId, - pub to: NodeId, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(tag = "op", rename_all = "snake_case")] -pub enum Delta { - AddNode { node: Node }, - RemoveNode { node: Node }, - ReplaceNode { old: Node, new: Node }, - AddEdge { edge: Edge }, - RemoveEdge { edge: Edge }, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Graph { - pub metadata: Metadata, - pub nodes: BTreeMap, - pub edges: BTreeSet, -} - -impl Graph { - pub fn new(metadata: Metadata) -> Self { - Self { - metadata, - nodes: BTreeMap::new(), - edges: BTreeSet::new(), - } - } - - pub fn add_node(&mut self, node: Node) -> Result { - if self.nodes.contains_key(&node.id) { - return Err(GraphError::DuplicateNode { id: node.id }); - } - - self.nodes.insert(node.id.clone(), node.clone()); - Ok(Delta::AddNode { node }) - } - - pub fn remove_node(&mut self, id: &NodeId) -> Result, GraphError> { - let node = self - .nodes - .remove(id) - .ok_or_else(|| GraphError::MissingNode { id: id.clone() })?; - - let incident_edges: Vec<_> = self - .edges - .iter() - .filter(|edge| edge.from == *id || edge.to == *id) - .cloned() - .collect(); - - let mut deltas = Vec::with_capacity(incident_edges.len() + 1); - for edge in incident_edges { - self.edges.remove(&edge); - deltas.push(Delta::RemoveEdge { edge }); - } - deltas.push(Delta::RemoveNode { node }); - - Ok(deltas) - } - - pub fn replace_node(&mut self, node: Node) -> Result { - let old = self - .nodes - .get_mut(&node.id) - .ok_or_else(|| GraphError::MissingNode { - id: node.id.clone(), - })?; - let previous = std::mem::replace(old, node.clone()); - - Ok(Delta::ReplaceNode { - old: previous, - new: node, - }) - } - - pub fn add_edge(&mut self, edge: Edge) -> Result { - self.require_node(&edge.from)?; - self.require_node(&edge.to)?; - - if !self.edges.insert(edge.clone()) { - return Err(GraphError::DuplicateEdge { edge }); - } - - Ok(Delta::AddEdge { edge }) - } - - pub fn remove_edge(&mut self, edge: &Edge) -> Result { - if !self.edges.remove(edge) { - return Err(GraphError::MissingEdge { edge: edge.clone() }); - } - - Ok(Delta::RemoveEdge { edge: edge.clone() }) - } - - fn require_node(&self, id: &NodeId) -> Result<(), GraphError> { - if self.nodes.contains_key(id) { - Ok(()) - } else { - Err(GraphError::MissingNode { id: id.clone() }) - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum GraphError { - DuplicateNode { id: NodeId }, - MissingNode { id: NodeId }, - DuplicateEdge { edge: Edge }, - MissingEdge { edge: Edge }, -} - -impl Display for GraphError { - fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { - match self { - GraphError::DuplicateNode { id } => { - write!(formatter, "duplicate node `{}`", id.as_str()) - } - GraphError::MissingNode { id } => write!(formatter, "missing node `{}`", id.as_str()), - GraphError::DuplicateEdge { edge } => write!( - formatter, - "duplicate edge `{} -> {}`", - edge.from.as_str(), - edge.to.as_str() - ), - GraphError::MissingEdge { edge } => write!( - formatter, - "missing edge `{} -> {}`", - edge.from.as_str(), - edge.to.as_str() - ), - } - } -} - -impl Error for GraphError {} - -pub fn write_graph_artifact( - directory: impl AsRef, - graph: &Graph, - delta: &[Delta], -) -> std::io::Result<()> { - let directory = directory.as_ref(); - fs::create_dir_all(directory)?; - - write_json_file(directory.join("metadata.json"), &graph.metadata)?; - write_jsonl_file(directory.join("nodes.jsonl"), graph.nodes.values())?; - write_jsonl_file(directory.join("edges.jsonl"), graph.edges.iter())?; - write_jsonl_file(directory.join("delta.jsonl"), delta.iter())?; - - Ok(()) -} - -pub fn read_graph_artifact(directory: impl AsRef) -> std::io::Result { - let directory = directory.as_ref(); - let metadata = read_json_file(directory.join("metadata.json"))?; - let nodes: Vec = read_jsonl_file(directory.join("nodes.jsonl"))?; - let edges: Vec = read_jsonl_file(directory.join("edges.jsonl"))?; - - let mut graph = Graph::new(metadata); - for node in nodes { - graph.add_node(node).map_err(invalid_data)?; - } - for edge in edges { - graph.add_edge(edge).map_err(invalid_data)?; - } - - Ok(graph) -} - -pub fn read_graph_delta(directory: impl AsRef) -> std::io::Result> { - read_jsonl_file(directory.as_ref().join("delta.jsonl")) -} - -fn write_json_file(path: impl AsRef, value: &impl Serialize) -> std::io::Result<()> { - let file = File::create(path)?; - let writer = BufWriter::new(file); - serde_json::to_writer_pretty(writer, value).map_err(invalid_data) -} - -fn read_json_file Deserialize<'de>>(path: impl AsRef) -> std::io::Result { - let file = File::open(path)?; - let reader = BufReader::new(file); - serde_json::from_reader(reader).map_err(invalid_data) -} - -fn write_jsonl_file<'a, T: 'a + Serialize>( - path: impl AsRef, - values: impl IntoIterator, -) -> std::io::Result<()> { - let file = File::create(path)?; - let mut writer = BufWriter::new(file); - - for value in values { - serde_json::to_writer(&mut writer, value).map_err(invalid_data)?; - writer.write_all(b"\n")?; - } - - Ok(()) -} - -fn read_jsonl_file Deserialize<'de>>( - path: impl AsRef, -) -> std::io::Result> { - let file = File::open(path)?; - let reader = BufReader::new(file); - let mut values = Vec::new(); - - for line in reader.lines() { - let line = line?; - if line.trim().is_empty() { - continue; - } - values.push(serde_json::from_str(&line).map_err(invalid_data)?); - } - - Ok(values) -} - -fn invalid_data(error: impl Error + Send + Sync + 'static) -> std::io::Error { - std::io::Error::new(std::io::ErrorKind::InvalidData, error) -} +pub use artifact::{read_graph_artifact, read_graph_delta, write_graph_artifact}; +pub use graph::{Delta, Edge, Graph, GraphError, Metadata, Node, NodeId, SourceRef}; pub fn run() { println!("Ariadne");