1use anyhow::Result;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22
23#[derive(Clone)]
24pub struct GitentClient {
25 base_url: String,
26 agent_id: String,
27 client: reqwest::blocking::Client,
28}
29
30#[derive(Serialize)]
31struct CreateChangeRequest {
32 change_type: String,
33 path: String,
34 content_before: Option<String>,
35 content_after: Option<String>,
36 agent_id: Option<String>,
37}
38
39#[derive(Serialize)]
40struct CreateCommitRequest {
41 message: String,
42 agent_id: String,
43 change_ids: Vec<String>,
44}
45
46#[derive(Deserialize)]
47struct Change {
48 id: String,
49}
50
51impl GitentClient {
52 pub fn new(base_url: impl Into<String>, agent_id: impl Into<String>) -> Self {
59 Self {
60 base_url: base_url.into(),
61 agent_id: agent_id.into(),
62 client: reqwest::blocking::Client::new(),
63 }
64 }
65
66 pub fn file_created(&self, path: &str, content: &str) -> Result<()> {
68 self.create_change("create", path, None, Some(content))
69 }
70
71 pub fn file_modified(
73 &self,
74 path: &str,
75 content_before: &str,
76 content_after: &str,
77 ) -> Result<()> {
78 self.create_change("modify", path, Some(content_before), Some(content_after))
79 }
80
81 pub fn file_written(
83 &self,
84 path: &str,
85 content: &str,
86 previous_content: Option<&str>,
87 ) -> Result<()> {
88 if let Some(prev) = previous_content {
89 self.file_modified(path, prev, content)
90 } else {
91 self.file_created(path, content)
92 }
93 }
94
95 pub fn file_deleted(&self, path: &str, content_before: Option<&str>) -> Result<()> {
97 self.create_change("delete", path, content_before, None)
98 }
99
100 fn create_change(
101 &self,
102 change_type: &str,
103 path: &str,
104 content_before: Option<&str>,
105 content_after: Option<&str>,
106 ) -> Result<()> {
107 let request = CreateChangeRequest {
108 change_type: change_type.to_string(),
109 path: path.to_string(),
110 content_before: content_before.map(|s| s.to_string()),
111 content_after: content_after.map(|s| s.to_string()),
112 agent_id: Some(self.agent_id.clone()),
113 };
114
115 self.client
116 .post(format!("{}/changes", self.base_url))
117 .json(&request)
118 .send()?
119 .error_for_status()?;
120
121 Ok(())
122 }
123
124 pub fn get_uncommitted_changes(&self) -> Result<Vec<HashMap<String, serde_json::Value>>> {
126 let response = self
127 .client
128 .get(format!("{}/changes", self.base_url))
129 .send()?
130 .error_for_status()?;
131
132 Ok(response.json()?)
133 }
134
135 pub fn commit(&self, message: &str) -> Result<String> {
137 let changes: Vec<Change> = self
139 .client
140 .get(format!("{}/changes", self.base_url))
141 .send()?
142 .error_for_status()?
143 .json()?;
144
145 let change_ids: Vec<String> = changes.iter().map(|c| c.id.clone()).collect();
146
147 let request = CreateCommitRequest {
148 message: message.to_string(),
149 agent_id: self.agent_id.clone(),
150 change_ids,
151 };
152
153 let response: serde_json::Value = self
154 .client
155 .post(format!("{}/commits", self.base_url))
156 .json(&request)
157 .send()?
158 .error_for_status()?
159 .json()?;
160
161 Ok(response["id"].as_str().unwrap_or("unknown").to_string())
162 }
163
164 pub fn get_commits(&self) -> Result<Vec<HashMap<String, serde_json::Value>>> {
166 let response = self
167 .client
168 .get(format!("{}/commits", self.base_url))
169 .send()?
170 .error_for_status()?;
171
172 Ok(response.json()?)
173 }
174
175 pub fn health_check(&self) -> Result<bool> {
177 let response = self
178 .client
179 .get(format!("{}/health", self.base_url))
180 .send()?;
181
182 Ok(response.status().is_success())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_client_creation() {
192 let client = GitentClient::new("http://localhost:3030", "test-agent");
193 assert_eq!(client.base_url, "http://localhost:3030");
194 assert_eq!(client.agent_id, "test-agent");
195 }
196}