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