Skip to main content

kaish_kernel/backend/
mod.rs

1//! KernelBackend trait for abstracting kaish's I/O layer.
2//!
3//! This module defines the `KernelBackend` trait which provides a unified interface
4//! for file operations and tool dispatch. Two implementations are provided:
5//!
6//! - `LocalBackend`: Default implementation wrapping VfsRouter for local filesystem access
7//! - (Future) `KaijutsuBackend`: CRDT-backed blocks when running under kaijutsu
8//!
9//! # Architecture
10//!
11//! ```text
12//! Builtins (cat, ls, echo, etc.)
13//!     ↓
14//! ctx.backend: Arc<dyn KernelBackend>
15//!     ↓
16//! ┌─────────────────────────────────────────────┐
17//! │  LocalBackend (default)  │  KaijutsuBackend │
18//! │  - wraps VfsRouter       │  - CRDT blocks   │
19//! │  - local ToolRegistry    │  - parent tools  │
20//! └─────────────────────────────────────────────┘
21//! ```
22
23mod local;
24mod overlay;
25
26pub use local::LocalBackend;
27pub use overlay::VirtualOverlayBackend;
28
29#[cfg(test)]
30pub mod testing;
31
32#[cfg(test)]
33pub use testing::MockBackend;
34
35// Data types re-exported from kaish-types.
36pub use kaish_types::backend::{
37    BackendError, BackendResult, ConflictError, MountInfo, PatchOp, ReadRange, ToolInfo,
38    ToolResult, WriteMode,
39};
40
41// The `KernelBackend` trait moved to the leaf `kaish-tool-api` crate (its
42// `call_tool` takes the portable `&mut dyn ToolCtx`, and tools reach it through
43// `ctx.backend()`). Re-exported here so existing `crate::backend::KernelBackend`
44// paths — and the `LocalBackend` / overlay / testing impls below — keep working.
45pub use kaish_tool_api::KernelBackend;
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use super::testing::MockBackend;
51    use crate::tools::{ExecContext, ToolArgs};
52    use crate::vfs::DirEntry;
53    use std::sync::atomic::Ordering;
54    use std::sync::Arc;
55
56    #[test]
57    fn test_backend_error_from_io_error() {
58        let not_found = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
59        let backend_err: BackendError = not_found.into();
60        assert!(matches!(backend_err, BackendError::NotFound(_)));
61
62        let permission = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "no access");
63        let backend_err: BackendError = permission.into();
64        assert!(matches!(backend_err, BackendError::PermissionDenied(_)));
65    }
66
67    #[test]
68    fn test_dir_entry_constructors() {
69        let dir = DirEntry::directory("mydir");
70        assert!(dir.is_dir());
71        assert_eq!(dir.name, "mydir");
72
73        let file = DirEntry::file("myfile.txt", 1024);
74        assert!(file.is_file());
75        assert_eq!(file.size, 1024);
76    }
77
78    #[test]
79    fn test_tool_result() {
80        let success = ToolResult::success("hello");
81        assert!(success.ok());
82        assert_eq!(success.stdout, "hello");
83
84        let failure = ToolResult::failure(1, "error");
85        assert!(!failure.ok());
86        assert_eq!(failure.code, 1);
87    }
88
89    #[test]
90    fn test_read_range() {
91        let lines = ReadRange::lines(10, 20);
92        assert_eq!(lines.start_line, Some(10));
93        assert_eq!(lines.end_line, Some(20));
94
95        let bytes = ReadRange::bytes(100, 50);
96        assert_eq!(bytes.offset, Some(100));
97        assert_eq!(bytes.limit, Some(50));
98    }
99
100    #[tokio::test]
101    async fn test_mock_backend_call_tool_routing() {
102        let (backend, call_count) = MockBackend::new();
103        let backend: Arc<dyn KernelBackend> = Arc::new(backend);
104        let mut ctx = ExecContext::with_backend(backend.clone());
105
106        // Verify initial count is 0
107        assert_eq!(call_count.load(Ordering::SeqCst), 0);
108
109        // Call tool through backend
110        let args = ToolArgs::new();
111        let result = backend.call_tool("test-tool", args, &mut ctx).await.unwrap();
112
113        // Verify call was routed through backend
114        assert_eq!(call_count.load(Ordering::SeqCst), 1);
115        assert!(result.ok());
116        assert!(result.stdout.contains("mock executed: test-tool"));
117
118        // Call again to verify count increments
119        let args = ToolArgs::new();
120        backend.call_tool("another-tool", args, &mut ctx).await.unwrap();
121        assert_eq!(call_count.load(Ordering::SeqCst), 2);
122    }
123}