1use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Tool {
9 pub name: String,
10 pub description: String,
11 pub parameters: serde_json::Value,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ToolResult {
17 pub success: bool,
18 pub output: String,
19 pub error: Option<String>,
20}
21
22pub fn resolve_path(workspace: &Path, path: &str) -> Result<PathBuf, String> {
24 let resolved = workspace.join(path);
25
26 let canonical = resolved
28 .canonicalize()
29 .map_err(|e| format!("Failed to resolve path: {}", e))?;
30
31 if !canonical.starts_with(workspace) {
33 return Err(format!("Path escapes workspace: {}", path));
34 }
35
36 Ok(canonical)
37}
38
39pub fn resolve_path_for_write(workspace: &Path, path: &str) -> Result<PathBuf, String> {
41 let resolved = workspace.join(path);
42
43 if let Some(parent) = resolved.parent() {
45 if parent.exists() {
46 let canonical = parent
47 .canonicalize()
48 .map_err(|e| format!("Failed to resolve parent: {}", e))?;
49
50 if !canonical.starts_with(workspace) {
51 return Err(format!("Path escapes workspace: {}", path));
52 }
53
54 if let Some(filename) = resolved.file_name() {
56 return Ok(canonical.join(filename));
57 }
58 }
59 }
60
61 Err(format!(
63 "Invalid path or parent directory doesn't exist: {}",
64 path
65 ))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::fs;
72
73 #[test]
74 fn test_tool_creation() {
75 let tool = Tool {
76 name: "test".to_string(),
77 description: "Test tool".to_string(),
78 parameters: serde_json::json!({}),
79 };
80 assert_eq!(tool.name, "test");
81 }
82
83 #[test]
84 fn test_resolve_path() {
85 let temp = std::env::temp_dir()
86 .canonicalize()
87 .expect("temp dir should be canonicalizable");
88 let workspace = temp.join("test_workspace_common");
89 fs::create_dir_all(&workspace).unwrap();
90
91 let result = resolve_path(&workspace, ".");
92 assert!(result.is_ok());
93
94 fs::remove_dir_all(&workspace).ok();
95 }
96}