Skip to main content

the_code_graph_domain/
ports.rs

1use crate::error::Result;
2use crate::model::*;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6/// Primary storage for the code graph — files, symbols, edges.
7pub 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    /// Returns symbols only for the specified file paths.
24    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    /// Processes edges row-by-row via callback.
33    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    /// Store a file and all its symbols and edges atomically.
41    fn store_file_data(
42        &self,
43        file: &FileNode,
44        symbols: &[SymbolNode],
45        edges: &[Edge],
46    ) -> Result<()>;
47
48    /// Remove all data associated with a file: file row, symbols, and related edges.
49    fn remove_file_data(&self, path: &Path) -> Result<()>;
50}
51
52/// Full-text search over symbols.
53pub 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
59/// Git operations (diff, log, etc.).
60pub 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
67/// Filesystem abstraction for reading source files.
68pub 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/// Data ready for storage: one file's worth of graph data.
75#[derive(Debug, Clone)]
76pub struct FileData {
77    pub file: FileNode,
78    pub symbols: Vec<SymbolNode>,
79    pub edges: Vec<Edge>,
80}
81
82/// Outbound port: parse and resolve a batch of source files.
83pub 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    /// Returns (qualified_name, text_hash) pairs for all stored embeddings.
104    /// Default impl returns an empty list (no incremental support).
105    fn get_stored_hashes(&self) -> Result<Vec<(String, String)>> {
106        Ok(vec![])
107    }
108}
109
110// ---------------------------------------------------------------------------
111// Blanket Arc impls — allow Arc<T> to be used wherever T is required
112// ---------------------------------------------------------------------------
113
114impl<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}