1use blake3::Hasher;
8use serde::{Deserialize, Serialize};
9use std::path::{Path, PathBuf};
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct NodeId([u8; 16]);
14
15impl NodeId {
16 pub fn new(repo_id: &str, file_path: &Path, span: &Span, kind: &NodeKind) -> Self {
18 let mut hasher = Hasher::new();
19 hasher.update(repo_id.as_bytes());
20 hasher.update(file_path.to_string_lossy().as_bytes());
21 hasher.update(&span.start_byte.to_le_bytes());
22 hasher.update(&span.end_byte.to_le_bytes());
23 hasher.update(format!("{:?}", kind).as_bytes());
24
25 let hash = hasher.finalize();
26 let mut id = [0u8; 16];
27 id.copy_from_slice(&hash.as_bytes()[..16]);
28 Self(id)
29 }
30
31 pub fn to_hex(&self) -> String {
33 hex::encode(self.0)
34 }
35}
36
37impl std::fmt::Debug for NodeId {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "NodeId({})", &self.to_hex()[..8])
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum NodeKind {
47 Module,
49 Class,
51 Function,
53 Method,
55 Parameter,
57 Variable,
59 Call,
61 Import,
63 Literal,
65 Route,
67 SqlQuery,
69 Event,
71 Unknown,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
78pub enum EdgeKind {
79 Calls,
81 Reads,
83 Writes,
85 Imports,
87 Emits,
89 RoutesTo,
91 Raises,
93 Extends,
95 Implements,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
101pub struct Span {
102 pub start_byte: usize,
104 pub end_byte: usize,
106 pub start_line: usize,
108 pub end_line: usize,
110 pub start_column: usize,
112 pub end_column: usize,
114}
115
116impl Span {
117 pub fn new(
119 start_byte: usize,
120 end_byte: usize,
121 start_line: usize,
122 end_line: usize,
123 start_column: usize,
124 end_column: usize,
125 ) -> Self {
126 Self {
127 start_byte,
128 end_byte,
129 start_line,
130 end_line,
131 start_column,
132 end_column,
133 }
134 }
135
136 pub fn from_node(node: &tree_sitter::Node) -> Self {
138 let start_pos = node.start_position();
139 let end_pos = node.end_position();
140
141 Self {
142 start_byte: node.start_byte(),
143 end_byte: node.end_byte(),
144 start_line: start_pos.row + 1, end_line: end_pos.row + 1,
146 start_column: start_pos.column + 1,
147 end_column: end_pos.column + 1,
148 }
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
154#[serde(rename_all = "lowercase")]
155pub enum Language {
156 Python,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Node {
163 pub id: NodeId,
165 pub kind: NodeKind,
167 pub name: String,
169 pub lang: Language,
171 pub file: PathBuf,
173 pub span: Span,
175 pub signature: Option<String>,
177 pub metadata: serde_json::Value,
179}
180
181impl Node {
182 pub fn new(
184 repo_id: &str,
185 kind: NodeKind,
186 name: String,
187 lang: Language,
188 file: PathBuf,
189 span: Span,
190 ) -> Self {
191 let id = NodeId::new(repo_id, &file, &span, &kind);
192 Self {
193 id,
194 kind,
195 name,
196 lang,
197 file,
198 span,
199 signature: None,
200 metadata: serde_json::Value::Null,
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
207pub struct Edge {
208 pub source: NodeId,
210 pub target: NodeId,
212 pub kind: EdgeKind,
214}
215
216impl Edge {
217 pub fn new(source: NodeId, target: NodeId, kind: EdgeKind) -> Self {
219 Self {
220 source,
221 target,
222 kind,
223 }
224 }
225}