git_indexer/
client.rs

1//! Helix DB client for indexing git repositories.
2//!
3//! This module provides a client for pushing git repository information
4//! to a Helix DB graph database instance.
5
6use crate::error::{ConfigError, HelixError, Result};
7use crate::extraction::extract;
8use crate::models::{BranchInfo, CommitInfo, FileChange, GitInfo};
9use std::path::Path;
10
11/// Client for interacting with a Helix DB instance.
12///
13/// Use [`GitIndexerClient::builder()`] to create a new client.
14///
15/// # Example
16///
17/// ```no_run
18/// use git_indexer::GitIndexerClient;
19///
20/// # async fn example() -> git_indexer::Result<()> {
21/// let client = GitIndexerClient::builder()
22///     .endpoint("http://localhost:6969")
23///     .build()?;
24///
25/// let git_info = client.index_repository("/path/to/repo").await?;
26/// # Ok(())
27/// # }
28/// ```
29#[derive(Debug, Clone)]
30pub struct GitIndexerClient {
31    endpoint: String,
32    // Future: Add helix-db client when implementing actual API calls
33    // helix_client: HelixDB,
34}
35
36/// Builder for constructing a [`GitIndexerClient`].
37#[derive(Debug, Default)]
38pub struct GitIndexerClientBuilder {
39    endpoint: Option<String>,
40}
41
42impl GitIndexerClient {
43    /// Create a new builder for configuring a `GitIndexerClient`.
44    pub fn builder() -> GitIndexerClientBuilder {
45        GitIndexerClientBuilder::default()
46    }
47
48    /// Get the configured endpoint URL.
49    pub fn endpoint(&self) -> &str {
50        &self.endpoint
51    }
52
53    /// Index a git repository and push all data to Helix DB.
54    ///
55    /// This is the main entry point for indexing a repository. It will:
56    /// 1. Extract all git information (branches, commits, file changes)
57    /// 2. Create nodes for each branch, commit, and file change
58    /// 3. Create edges for parent-child commit relationships
59    ///
60    /// # Arguments
61    ///
62    /// * `repo_path` - Path to the git repository
63    ///
64    /// # Returns
65    ///
66    /// Returns the extracted `GitInfo` after successfully pushing to Helix DB.
67    pub async fn index_repository<P: AsRef<Path>>(&self, repo_path: P) -> Result<GitInfo> {
68        let git_info = extract(repo_path.as_ref())?;
69
70        // Create branch nodes
71        for branch in &git_info.branches {
72            self.create_branch_node(branch).await?;
73        }
74
75        // Create commit nodes and their relationships
76        for commit in &git_info.commits {
77            self.create_commit_node(commit).await?;
78
79            // Create file change nodes
80            for file_change in &commit.file_changes {
81                self.create_file_node(file_change, &commit.id).await?;
82            }
83
84            // Create parent edges
85            for parent_id in &commit.parent_ids {
86                self.create_parent_edge(&commit.id, parent_id).await?;
87            }
88        }
89
90        Ok(git_info)
91    }
92
93    /// Create a commit node in Helix DB.
94    ///
95    /// # Arguments
96    ///
97    /// * `commit` - The commit information to store
98    pub async fn create_commit_node(&self, commit: &CommitInfo) -> Result<()> {
99        // TODO: Implement actual Helix DB API call
100        // Example future implementation:
101        // ```
102        // use helix_db::HelixDB;
103        // let payload = serde_json::json!({
104        //     "id": commit.id,
105        //     "message": commit.message,
106        //     "author": commit.author,
107        //     "timestamp": commit.timestamp,
108        // });
109        // self.helix_client.query("CreateCommit", &payload).await?;
110        // ```
111
112        let _ = commit; // Suppress unused warning
113        Err(HelixError::NotImplemented("create_commit_node".to_string()).into())
114    }
115
116    /// Create a branch node in Helix DB.
117    ///
118    /// # Arguments
119    ///
120    /// * `branch` - The branch information to store
121    pub async fn create_branch_node(&self, branch: &BranchInfo) -> Result<()> {
122        // TODO: Implement actual Helix DB API call
123        // Example future implementation:
124        // ```
125        // let payload = serde_json::json!({
126        //     "name": branch.name,
127        //     "is_head": branch.is_head,
128        //     "commit_id": branch.commit_id,
129        //     "is_remote": branch.is_remote,
130        // });
131        // self.helix_client.query("CreateBranch", &payload).await?;
132        // ```
133
134        let _ = branch; // Suppress unused warning
135        Err(HelixError::NotImplemented("create_branch_node".to_string()).into())
136    }
137
138    /// Create a file change node in Helix DB.
139    ///
140    /// # Arguments
141    ///
142    /// * `file` - The file change information to store
143    /// * `commit_id` - The SHA of the commit this file change belongs to
144    pub async fn create_file_node(&self, file: &FileChange, commit_id: &str) -> Result<()> {
145        // TODO: Implement actual Helix DB API call
146        // Example future implementation:
147        // ```
148        // let payload = serde_json::json!({
149        //     "path": file.path,
150        //     "old_path": file.old_path,
151        //     "change_type": file.change_type,
152        //     "commit_id": commit_id,
153        //     "hunks": file.hunks,
154        // });
155        // self.helix_client.query("CreateFileChange", &payload).await?;
156        // ```
157
158        let _ = (file, commit_id); // Suppress unused warning
159        Err(HelixError::NotImplemented("create_file_node".to_string()).into())
160    }
161
162    /// Create a parent-child edge between commits in Helix DB.
163    ///
164    /// # Arguments
165    ///
166    /// * `child_id` - SHA of the child commit
167    /// * `parent_id` - SHA of the parent commit
168    pub async fn create_parent_edge(&self, child_id: &str, parent_id: &str) -> Result<()> {
169        // TODO: Implement actual Helix DB API call
170        // Example future implementation:
171        // ```
172        // let payload = serde_json::json!({
173        //     "child_id": child_id,
174        //     "parent_id": parent_id,
175        // });
176        // self.helix_client.query("CreateParentEdge", &payload).await?;
177        // ```
178
179        let _ = (child_id, parent_id); // Suppress unused warning
180        Err(HelixError::NotImplemented("create_parent_edge".to_string()).into())
181    }
182}
183
184impl GitIndexerClientBuilder {
185    /// Set the Helix DB endpoint URL.
186    ///
187    /// # Arguments
188    ///
189    /// * `endpoint` - The URL of the Helix DB instance (e.g., "http://localhost:6969")
190    pub fn endpoint<S: Into<String>>(mut self, endpoint: S) -> Self {
191        self.endpoint = Some(endpoint.into());
192        self
193    }
194
195    /// Build the `GitIndexerClient`.
196    ///
197    /// # Errors
198    ///
199    /// Returns an error if the endpoint is not set.
200    pub fn build(self) -> Result<GitIndexerClient> {
201        let endpoint = self
202            .endpoint
203            .ok_or_else(|| ConfigError::MissingField("endpoint".to_string()))?;
204
205        // Validate endpoint URL format
206        if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") {
207            return Err(ConfigError::InvalidEndpoint(format!(
208                "Endpoint must start with http:// or https://, got: {}",
209                endpoint
210            ))
211            .into());
212        }
213
214        Ok(GitIndexerClient { endpoint })
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_builder_missing_endpoint() {
224        let result = GitIndexerClient::builder().build();
225        assert!(result.is_err());
226    }
227
228    #[test]
229    fn test_builder_invalid_endpoint() {
230        let result = GitIndexerClient::builder().endpoint("not-a-url").build();
231        assert!(result.is_err());
232    }
233
234    #[test]
235    fn test_builder_valid_endpoint() {
236        let client = GitIndexerClient::builder()
237            .endpoint("http://localhost:6969")
238            .build()
239            .unwrap();
240        assert_eq!(client.endpoint(), "http://localhost:6969");
241    }
242
243    #[test]
244    fn test_builder_https_endpoint() {
245        let client = GitIndexerClient::builder()
246            .endpoint("https://helix.example.com:6969")
247            .build()
248            .unwrap();
249        assert_eq!(client.endpoint(), "https://helix.example.com:6969");
250    }
251}