use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
error::GitCortexError,
schema::{CodeSmell, DesignPattern, EdgeKind, NodeKind, SolidHint, Visibility},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NodeId(Uuid);
impl NodeId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn as_str(&self) -> String {
self.0.to_string()
}
}
impl Default for NodeId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl TryFrom<&str> for NodeId {
type Error = GitCortexError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Uuid::parse_str(s)
.map(NodeId)
.map_err(|e| GitCortexError::Store(format!("invalid NodeId '{s}': {e}")))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
pub start_line: u32,
pub end_line: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct LldLabels {
pub solid_hints: Vec<SolidHint>,
pub patterns: Vec<DesignPattern>,
pub smells: Vec<CodeSmell>,
pub complexity: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct NodeMetadata {
pub loc: u32,
pub visibility: Visibility,
pub is_async: bool,
pub is_unsafe: bool,
pub is_static: bool,
pub is_abstract: bool,
pub is_final: bool,
pub is_property: bool,
pub is_generator: bool,
pub is_const: bool,
pub generic_bounds: Vec<String>,
pub lld: LldLabels,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Node {
pub id: NodeId,
pub kind: NodeKind,
pub name: String,
pub qualified_name: String,
pub file: PathBuf,
pub span: Span,
pub metadata: NodeMetadata,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Edge {
pub src: NodeId,
pub dst: NodeId,
pub kind: EdgeKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct GraphDiff {
pub added_nodes: Vec<Node>,
pub removed_node_ids: Vec<NodeId>,
pub removed_files: Vec<PathBuf>,
pub added_edges: Vec<Edge>,
pub removed_edges: Vec<(NodeId, NodeId, EdgeKind)>,
pub deferred_calls: Vec<(NodeId, String)>,
pub deferred_uses: Vec<(NodeId, String)>,
pub deferred_implements: Vec<(NodeId, String)>,
pub deferred_inherits: Vec<(NodeId, String)>,
pub deferred_throws: Vec<(NodeId, String)>,
pub deferred_annotated: Vec<(NodeId, String)>,
}
impl GraphDiff {
pub fn is_empty(&self) -> bool {
self.added_nodes.is_empty()
&& self.removed_node_ids.is_empty()
&& self.removed_files.is_empty()
&& self.added_edges.is_empty()
&& self.removed_edges.is_empty()
&& self.deferred_calls.is_empty()
&& self.deferred_uses.is_empty()
&& self.deferred_implements.is_empty()
&& self.deferred_inherits.is_empty()
&& self.deferred_throws.is_empty()
&& self.deferred_annotated.is_empty()
}
pub fn merge(&mut self, other: GraphDiff) {
self.added_nodes.extend(other.added_nodes);
self.removed_node_ids.extend(other.removed_node_ids);
self.removed_files.extend(other.removed_files);
self.added_edges.extend(other.added_edges);
self.removed_edges.extend(other.removed_edges);
self.deferred_calls.extend(other.deferred_calls);
self.deferred_uses.extend(other.deferred_uses);
self.deferred_implements.extend(other.deferred_implements);
self.deferred_inherits.extend(other.deferred_inherits);
self.deferred_throws.extend(other.deferred_throws);
self.deferred_annotated.extend(other.deferred_annotated);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_id_is_unique() {
let a = NodeId::new();
let b = NodeId::new();
assert_ne!(a, b);
}
#[test]
fn graph_diff_merge() {
let node = Node {
id: NodeId::new(),
kind: NodeKind::Function,
name: "foo".into(),
qualified_name: "crate::foo".into(),
file: PathBuf::from("src/lib.rs"),
span: Span {
start_line: 1,
end_line: 3,
},
metadata: NodeMetadata::default(),
};
let mut base = GraphDiff::default();
let other = GraphDiff {
added_nodes: vec![node],
..Default::default()
};
base.merge(other);
assert_eq!(base.added_nodes.len(), 1);
}
#[test]
fn graph_diff_is_empty_on_default() {
assert!(GraphDiff::default().is_empty());
}
}