mod local;
mod overlay;
pub use local::LocalBackend;
pub use overlay::VirtualOverlayBackend;
#[cfg(test)]
pub mod testing;
#[cfg(test)]
pub use testing::MockBackend;
use async_trait::async_trait;
use std::path::{Path, PathBuf};
pub use kaish_types::backend::{
BackendError, BackendResult, ConflictError, PatchOp, ReadRange, ToolInfo, ToolResult, WriteMode,
};
use crate::tools::{ExecContext, ToolArgs};
use crate::vfs::{DirEntry, MountInfo};
#[async_trait]
pub trait KernelBackend: Send + Sync {
async fn read(&self, path: &Path, range: Option<ReadRange>) -> BackendResult<Vec<u8>>;
async fn write(&self, path: &Path, content: &[u8], mode: WriteMode) -> BackendResult<()>;
async fn append(&self, path: &Path, content: &[u8]) -> BackendResult<()>;
async fn patch(&self, path: &Path, ops: &[PatchOp]) -> BackendResult<()>;
async fn list(&self, path: &Path) -> BackendResult<Vec<DirEntry>>;
async fn stat(&self, path: &Path) -> BackendResult<DirEntry>;
async fn mkdir(&self, path: &Path) -> BackendResult<()>;
async fn remove(&self, path: &Path, recursive: bool) -> BackendResult<()>;
async fn rename(&self, from: &Path, to: &Path) -> BackendResult<()>;
async fn exists(&self, path: &Path) -> bool;
async fn lstat(&self, path: &Path) -> BackendResult<DirEntry>;
async fn read_link(&self, path: &Path) -> BackendResult<PathBuf>;
async fn symlink(&self, target: &Path, link: &Path) -> BackendResult<()>;
async fn call_tool(
&self,
name: &str,
args: ToolArgs,
ctx: &mut ExecContext,
) -> BackendResult<ToolResult>;
async fn list_tools(&self) -> BackendResult<Vec<ToolInfo>>;
async fn get_tool(&self, name: &str) -> BackendResult<Option<ToolInfo>>;
fn read_only(&self) -> bool;
fn backend_type(&self) -> &str;
fn mounts(&self) -> Vec<MountInfo>;
fn resolve_real_path(&self, path: &Path) -> Option<std::path::PathBuf>;
}
#[cfg(test)]
mod tests {
use super::*;
use super::testing::MockBackend;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[test]
fn test_backend_error_from_io_error() {
let not_found = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let backend_err: BackendError = not_found.into();
assert!(matches!(backend_err, BackendError::NotFound(_)));
let permission = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "no access");
let backend_err: BackendError = permission.into();
assert!(matches!(backend_err, BackendError::PermissionDenied(_)));
}
#[test]
fn test_dir_entry_constructors() {
let dir = DirEntry::directory("mydir");
assert!(dir.is_dir());
assert_eq!(dir.name, "mydir");
let file = DirEntry::file("myfile.txt", 1024);
assert!(file.is_file());
assert_eq!(file.size, 1024);
}
#[test]
fn test_tool_result() {
let success = ToolResult::success("hello");
assert!(success.ok());
assert_eq!(success.stdout, "hello");
let failure = ToolResult::failure(1, "error");
assert!(!failure.ok());
assert_eq!(failure.code, 1);
}
#[test]
fn test_read_range() {
let lines = ReadRange::lines(10, 20);
assert_eq!(lines.start_line, Some(10));
assert_eq!(lines.end_line, Some(20));
let bytes = ReadRange::bytes(100, 50);
assert_eq!(bytes.offset, Some(100));
assert_eq!(bytes.limit, Some(50));
}
#[tokio::test]
async fn test_mock_backend_call_tool_routing() {
let (backend, call_count) = MockBackend::new();
let backend: Arc<dyn KernelBackend> = Arc::new(backend);
let mut ctx = ExecContext::with_backend(backend.clone());
assert_eq!(call_count.load(Ordering::SeqCst), 0);
let args = ToolArgs::new();
let result = backend.call_tool("test-tool", args, &mut ctx).await.unwrap();
assert_eq!(call_count.load(Ordering::SeqCst), 1);
assert!(result.ok());
assert!(result.stdout.contains("mock executed: test-tool"));
let args = ToolArgs::new();
backend.call_tool("another-tool", args, &mut ctx).await.unwrap();
assert_eq!(call_count.load(Ordering::SeqCst), 2);
}
}