1use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::{SystemTime, UNIX_EPOCH};
15
16use async_trait::async_trait;
17
18use kaish_kernel::ast::Value;
19use kaish_kernel::interpreter::ExecResult;
20use kaish_kernel::vfs::Filesystem;
21use kaish_kernel::{ExecuteOptions, Kernel, KernelConfig};
22
23use crate::traits::{ClientError, ClientResult, KernelClient};
24
25fn generate_blob_id() -> String {
27 static COUNTER: AtomicU64 = AtomicU64::new(0);
28
29 let timestamp = SystemTime::now()
30 .duration_since(UNIX_EPOCH)
31 .map(|d| d.as_nanos())
32 .unwrap_or(0);
33 let count = COUNTER.fetch_add(1, Ordering::SeqCst);
34
35 format!("{:x}-{:x}", timestamp, count)
36}
37
38pub struct EmbeddedClient {
56 kernel: Arc<Kernel>,
57}
58
59impl EmbeddedClient {
60 pub fn new(kernel: Kernel) -> Self {
62 Self {
63 kernel: kernel.into_arc(),
64 }
65 }
66
67 pub fn transient() -> ClientResult<Self> {
69 let kernel = Kernel::new(KernelConfig::transient())
70 .map_err(ClientError::Other)?;
71 Ok(Self::new(kernel))
72 }
73
74 pub fn with_defaults() -> ClientResult<Self> {
76 let kernel = Kernel::new(KernelConfig::default())
77 .map_err(ClientError::Other)?;
78 Ok(Self::new(kernel))
79 }
80
81 pub fn kernel(&self) -> &Kernel {
83 &self.kernel
84 }
85
86 pub async fn execute_streaming(
92 &self,
93 input: &str,
94 on_output: &mut (dyn FnMut(&ExecResult) + Send),
95 ) -> ClientResult<ExecResult> {
96 self.kernel
97 .execute_with_options_streaming(input, ExecuteOptions::default(), on_output)
98 .await
99 .map_err(|e| ClientError::Execution(e.to_string()))
100 }
101
102 pub async fn execute_with_options(
106 &self,
107 input: &str,
108 opts: ExecuteOptions,
109 ) -> ClientResult<ExecResult> {
110 self.kernel
111 .execute_with_options(input, opts)
112 .await
113 .map_err(|e| ClientError::Execution(e.to_string()))
114 }
115
116 pub async fn execute_with_options_streaming(
118 &self,
119 input: &str,
120 opts: ExecuteOptions,
121 on_output: &mut (dyn FnMut(&ExecResult) + Send),
122 ) -> ClientResult<ExecResult> {
123 self.kernel
124 .execute_with_options_streaming(input, opts, on_output)
125 .await
126 .map_err(|e| ClientError::Execution(e.to_string()))
127 }
128}
129
130#[async_trait(?Send)]
131impl KernelClient for EmbeddedClient {
132 async fn execute(&self, input: &str) -> ClientResult<ExecResult> {
133 self.kernel
134 .execute(input)
135 .await
136 .map_err(|e| ClientError::Execution(e.to_string()))
137 }
138
139 async fn execute_with_vars(
140 &self,
141 input: &str,
142 vars: HashMap<String, Value>,
143 ) -> ClientResult<ExecResult> {
144 self.kernel
145 .execute_with_options(input, ExecuteOptions::new().with_vars(vars))
146 .await
147 .map_err(|e| ClientError::Execution(e.to_string()))
148 }
149
150 async fn get_var(&self, name: &str) -> ClientResult<Option<Value>> {
151 Ok(self.kernel.get_var(name).await)
152 }
153
154 async fn set_var(&self, name: &str, value: Value) -> ClientResult<()> {
155 self.kernel.set_var(name, value).await;
156 Ok(())
157 }
158
159 async fn list_vars(&self) -> ClientResult<Vec<(String, Value)>> {
160 Ok(self.kernel.list_vars().await)
161 }
162
163 async fn cwd(&self) -> ClientResult<String> {
164 Ok(self.kernel.cwd().await.to_string_lossy().to_string())
165 }
166
167 async fn set_cwd(&self, path: &str) -> ClientResult<()> {
168 self.kernel.set_cwd(PathBuf::from(path)).await;
169 Ok(())
170 }
171
172 async fn last_result(&self) -> ClientResult<ExecResult> {
173 Ok(self.kernel.last_result().await)
174 }
175
176 async fn reset(&self) -> ClientResult<()> {
177 self.kernel
178 .reset()
179 .await
180 .map_err(ClientError::Other)
181 }
182
183 async fn ping(&self) -> ClientResult<String> {
184 Ok("pong".to_string())
185 }
186
187 async fn shutdown(&self) -> ClientResult<()> {
188 Ok(())
191 }
192
193 async fn read_blob(&self, id: &str) -> ClientResult<Vec<u8>> {
194 let vfs = self.kernel.vfs();
195 let path = PathBuf::from(format!("/v/blobs/{}", id));
196
197 vfs.read(&path)
198 .await
199 .map_err(ClientError::Io)
200 }
201
202 async fn write_blob(&self, content_type: &str, data: &[u8]) -> ClientResult<String> {
203 let vfs = self.kernel.vfs();
204 let id = generate_blob_id();
205 let path = PathBuf::from(format!("/v/blobs/{}", id));
206
207 let parent = Path::new("/v/blobs");
209 if let Err(e) = vfs.mkdir(parent).await {
210 if e.kind() != std::io::ErrorKind::AlreadyExists {
212 tracing::warn!("Failed to create blob directory: {}", e);
213 }
214 }
215
216 tracing::debug!("Creating blob {} with content type {}", id, content_type);
218
219 vfs.write(&path, data)
220 .await
221 .map_err(ClientError::Io)?;
222
223 Ok(id)
224 }
225
226 async fn delete_blob(&self, id: &str) -> ClientResult<bool> {
227 let vfs = self.kernel.vfs();
228 let path = PathBuf::from(format!("/v/blobs/{}", id));
229
230 match vfs.remove(&path).await {
231 Ok(()) => Ok(true),
232 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
233 Err(e) => Err(ClientError::Io(e)),
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[tokio::test]
243 async fn test_embedded_transient() {
244 let client = EmbeddedClient::transient().expect("failed to create client");
245 let result = client.ping().await.expect("ping failed");
246 assert_eq!(result, "pong");
247 }
248
249 #[tokio::test]
250 async fn test_embedded_execute() {
251 let client = EmbeddedClient::transient().expect("failed to create client");
252 let result = client.execute("echo hello").await.expect("execute failed");
253 assert!(result.ok());
254 assert_eq!(result.text_out().trim(), "hello");
255 }
256
257 #[tokio::test]
258 async fn test_embedded_variables() {
259 let client = EmbeddedClient::transient().expect("failed to create client");
260
261 client.execute("X=42").await.expect("set failed");
263 let value = client.get_var("X").await.expect("get failed");
264 assert_eq!(value, Some(Value::Int(42)));
265
266 client.set_var("Y", Value::String("hello".into())).await.expect("set_var failed");
268 let value = client.get_var("Y").await.expect("get failed");
269 assert_eq!(value, Some(Value::String("hello".into())));
270
271 let vars = client.list_vars().await.expect("list failed");
273 assert!(vars.iter().any(|(n, _)| n == "X"));
274 assert!(vars.iter().any(|(n, _)| n == "Y"));
275 }
276
277 #[tokio::test]
278 async fn test_embedded_cwd() {
279 let client = EmbeddedClient::transient().expect("failed to create client");
280
281 let cwd = client.cwd().await.expect("cwd failed");
283 let home = std::env::var("HOME").unwrap_or_else(|_| "/".to_string());
284 assert_eq!(cwd, home);
285
286 client.set_cwd("/tmp").await.expect("set_cwd failed");
287 let cwd = client.cwd().await.expect("cwd failed");
288 assert_eq!(cwd, "/tmp");
289 }
290
291 #[tokio::test]
292 async fn test_embedded_reset() {
293 let client = EmbeddedClient::transient().expect("failed to create client");
294
295 client.execute("X=1").await.expect("set failed");
296 assert!(client.get_var("X").await.expect("get failed").is_some());
297
298 client.reset().await.expect("reset failed");
299 assert!(client.get_var("X").await.expect("get failed").is_none());
300 }
301
302 #[tokio::test]
303 async fn test_embedded_last_result() {
304 let client = EmbeddedClient::transient().expect("failed to create client");
305
306 client.execute("echo test").await.expect("execute failed");
307 let last = client.last_result().await.expect("last_result failed");
308 assert!(last.ok());
309 assert_eq!(last.text_out().trim(), "test");
310 }
311
312 #[tokio::test]
313 async fn test_embedded_blob_write_read() {
314 let client = EmbeddedClient::transient().expect("failed to create client");
315
316 let data = b"hello blob world!";
317 let id = client.write_blob("text/plain", data).await.expect("write_blob failed");
318
319 assert!(!id.is_empty(), "blob id should not be empty");
320
321 let read_data = client.read_blob(&id).await.expect("read_blob failed");
322 assert_eq!(read_data, data);
323 }
324
325 #[tokio::test]
326 async fn test_embedded_blob_delete() {
327 let client = EmbeddedClient::transient().expect("failed to create client");
328
329 let data = b"blob to delete";
330 let id = client.write_blob("application/octet-stream", data).await.expect("write_blob failed");
331
332 let read_data = client.read_blob(&id).await.expect("read_blob failed");
334 assert_eq!(read_data, data);
335
336 let deleted = client.delete_blob(&id).await.expect("delete_blob failed");
338 assert!(deleted, "blob should have been deleted");
339
340 let result = client.read_blob(&id).await;
342 assert!(result.is_err(), "blob should not exist after deletion");
343 }
344
345 #[tokio::test]
346 async fn test_embedded_blob_delete_nonexistent() {
347 let client = EmbeddedClient::transient().expect("failed to create client");
348
349 let deleted = client.delete_blob("nonexistent-blob-id").await.expect("delete_blob failed");
350 assert!(!deleted, "deleting nonexistent blob should return false");
351 }
352
353 #[tokio::test]
354 async fn test_embedded_blob_large_data() {
355 let client = EmbeddedClient::transient().expect("failed to create client");
356
357 let data: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
359 let id = client.write_blob("application/octet-stream", &data).await.expect("write_blob failed");
360
361 let read_data = client.read_blob(&id).await.expect("read_blob failed");
362 assert_eq!(read_data.len(), data.len());
363 assert_eq!(read_data, data);
364 }
365}