the_code_graph_domain/
ports.rs1use crate::error::Result;
2use crate::model::*;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6pub trait GraphStore: Send + Sync {
8 fn upsert_file(&self, file: &FileNode) -> Result<()>;
9 fn upsert_symbol(&self, symbol: &SymbolNode) -> Result<()>;
10 fn upsert_edge(&self, edge: &Edge) -> Result<()>;
11 fn get_file(&self, path: &Path) -> Result<Option<FileNode>>;
12 fn get_symbol(&self, qualified_name: &str) -> Result<Option<SymbolNode>>;
13 fn get_edges_from(&self, source: &str) -> Result<Vec<Edge>>;
14 fn get_edges_to(&self, target: &str) -> Result<Vec<Edge>>;
15 fn all_files(&self) -> Result<Vec<FileNode>>;
16 fn all_symbols(&self) -> Result<Vec<SymbolNode>>;
17 fn all_edges(&self) -> Result<Vec<Edge>>;
18 fn remove_file(&self, path: &Path) -> Result<()>;
19 fn remove_symbols_in_file(&self, path: &Path) -> Result<()>;
20 fn stats(&self) -> Result<GraphStats>;
21 fn find_by_name(&self, pattern: &str) -> Result<Vec<SymbolNode>>;
22
23 fn symbols_for_files(&self, paths: &[&Path]) -> Result<Vec<SymbolNode>> {
25 let all = self.all_symbols()?;
26 Ok(all
27 .into_iter()
28 .filter(|s| paths.contains(&&*s.location.file))
29 .collect())
30 }
31
32 fn edges_streaming(&self, callback: &mut dyn FnMut(Edge) -> Result<()>) -> Result<()> {
34 for edge in self.all_edges()? {
35 callback(edge)?;
36 }
37 Ok(())
38 }
39
40 fn store_file_data(
42 &self,
43 file: &FileNode,
44 symbols: &[SymbolNode],
45 edges: &[Edge],
46 ) -> Result<()>;
47
48 fn remove_file_data(&self, path: &Path) -> Result<()>;
50}
51
52pub trait SearchIndex: Send + Sync {
54 fn index_symbol(&self, symbol: &SymbolNode) -> Result<()>;
55 fn search(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>>;
56 fn rebuild(&self) -> Result<()>;
57}
58
59pub trait GitProvider: Send + Sync {
61 fn diff_hunks(&self, from: &str, to: Option<&str>) -> Result<Vec<DiffHunk>>;
62 fn changed_files(&self, from: &str, to: &str) -> Result<Vec<PathBuf>>;
63 fn current_head(&self) -> Result<String>;
64 fn modified_files(&self) -> Result<Vec<PathBuf>>;
65}
66
67pub trait FileSystem: Send + Sync {
69 fn read_file(&self, path: &Path) -> Result<String>;
70 fn list_files(&self, root: &Path, extensions: &[&str]) -> Result<Vec<PathBuf>>;
71 fn file_hash(&self, path: &Path) -> Result<String>;
72}
73
74#[derive(Debug, Clone)]
76pub struct FileData {
77 pub file: FileNode,
78 pub symbols: Vec<SymbolNode>,
79 pub edges: Vec<Edge>,
80}
81
82pub trait ParseProvider: Send + Sync {
84 fn parse_and_resolve(
85 &self,
86 files: &[(PathBuf, Vec<u8>)],
87 project_root: &Path,
88 ) -> Result<Vec<FileData>>;
89}
90
91pub trait EmbeddingProvider: Send + Sync {
92 fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>>;
93 fn embed_query(&self, text: &str) -> Result<Vec<f32>>;
94 fn dimension(&self) -> usize;
95}
96
97pub trait VectorStore: Send + Sync {
98 fn store_embeddings(&self, entries: &[EmbeddingEntry]) -> Result<()>;
99 fn search_nearest(&self, query_vec: &[f32], limit: usize) -> Result<Vec<(String, f64)>>;
100 fn has_embeddings(&self) -> bool;
101 fn count(&self) -> Result<usize>;
102 fn remove_embeddings(&self, qualified_names: &[&str]) -> Result<()>;
103 fn get_stored_hashes(&self) -> Result<Vec<(String, String)>> {
106 Ok(vec![])
107 }
108}
109
110impl<V: VectorStore> VectorStore for Arc<V> {
115 fn store_embeddings(&self, entries: &[EmbeddingEntry]) -> Result<()> {
116 (**self).store_embeddings(entries)
117 }
118 fn search_nearest(&self, query_vec: &[f32], limit: usize) -> Result<Vec<(String, f64)>> {
119 (**self).search_nearest(query_vec, limit)
120 }
121 fn has_embeddings(&self) -> bool {
122 (**self).has_embeddings()
123 }
124 fn count(&self) -> Result<usize> {
125 (**self).count()
126 }
127 fn remove_embeddings(&self, qualified_names: &[&str]) -> Result<()> {
128 (**self).remove_embeddings(qualified_names)
129 }
130 fn get_stored_hashes(&self) -> Result<Vec<(String, String)>> {
131 (**self).get_stored_hashes()
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 fn assert_send_sync<T: Send + Sync>() {}
140
141 #[test]
142 fn graph_store_is_send_sync() {
143 assert_send_sync::<Box<dyn GraphStore>>();
144 }
145
146 #[test]
147 fn search_index_is_send_sync() {
148 assert_send_sync::<Box<dyn SearchIndex>>();
149 }
150
151 #[test]
152 fn git_provider_is_send_sync() {
153 assert_send_sync::<Box<dyn GitProvider>>();
154 }
155
156 #[test]
157 fn file_system_is_send_sync() {
158 assert_send_sync::<Box<dyn FileSystem>>();
159 }
160
161 #[test]
162 fn parse_provider_is_send_sync() {
163 assert_send_sync::<Box<dyn ParseProvider>>();
164 }
165
166 #[test]
167 fn file_data_construction() {
168 let fd = FileData {
169 file: FileNode {
170 path: "src/main.rs".into(),
171 language: Language::Rust,
172 hash: "abc123".into(),
173 },
174 symbols: vec![],
175 edges: vec![],
176 };
177 assert_eq!(fd.file.path.to_str().unwrap(), "src/main.rs");
178 assert!(fd.symbols.is_empty());
179 assert!(fd.edges.is_empty());
180 }
181}