1use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
13pub enum ToolError {
14 #[error("Tool not found: {0}")]
16 NotFound(String),
17
18 #[error("Permission denied: {0}")]
20 PermissionDenied(String),
21
22 #[error("Execution failed: {0}")]
24 ExecutionFailed(String),
25
26 #[error("Timeout after {0:?}")]
28 Timeout(Duration),
29
30 #[error("Safety check failed: {0}")]
32 SafetyCheckFailed(String),
33
34 #[error("Invalid parameters: {0}")]
36 InvalidParams(String),
37
38 #[error("IO error: {0}")]
40 Io(#[from] std::io::Error),
41
42 #[error("Cancelled")]
44 Cancelled,
45}
46
47impl ToolError {
48 pub fn not_found(name: impl Into<String>) -> Self {
50 Self::NotFound(name.into())
51 }
52
53 pub fn permission_denied(reason: impl Into<String>) -> Self {
55 Self::PermissionDenied(reason.into())
56 }
57
58 pub fn execution_failed(reason: impl Into<String>) -> Self {
60 Self::ExecutionFailed(reason.into())
61 }
62
63 pub fn timeout(duration: Duration) -> Self {
65 Self::Timeout(duration)
66 }
67
68 pub fn safety_check_failed(reason: impl Into<String>) -> Self {
70 Self::SafetyCheckFailed(reason.into())
71 }
72
73 pub fn invalid_params(reason: impl Into<String>) -> Self {
75 Self::InvalidParams(reason.into())
76 }
77
78 pub fn is_retryable(&self) -> bool {
80 matches!(self, Self::Timeout(_) | Self::Io(_))
81 }
82
83 pub fn is_permission_error(&self) -> bool {
85 matches!(self, Self::PermissionDenied(_))
86 }
87
88 pub fn is_safety_error(&self) -> bool {
90 matches!(self, Self::SafetyCheckFailed(_))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_not_found_error() {
100 let err = ToolError::not_found("bash");
101 assert!(matches!(err, ToolError::NotFound(_)));
102 assert_eq!(err.to_string(), "Tool not found: bash");
103 }
104
105 #[test]
106 fn test_permission_denied_error() {
107 let err = ToolError::permission_denied("Access denied to file system");
108 assert!(matches!(err, ToolError::PermissionDenied(_)));
109 assert_eq!(
110 err.to_string(),
111 "Permission denied: Access denied to file system"
112 );
113 }
114
115 #[test]
116 fn test_execution_failed_error() {
117 let err = ToolError::execution_failed("Command returned non-zero exit code");
118 assert!(matches!(err, ToolError::ExecutionFailed(_)));
119 assert_eq!(
120 err.to_string(),
121 "Execution failed: Command returned non-zero exit code"
122 );
123 }
124
125 #[test]
126 fn test_timeout_error() {
127 let err = ToolError::timeout(Duration::from_secs(30));
128 assert!(matches!(err, ToolError::Timeout(_)));
129 assert_eq!(err.to_string(), "Timeout after 30s");
130 }
131
132 #[test]
133 fn test_safety_check_failed_error() {
134 let err = ToolError::safety_check_failed("Dangerous command detected: rm -rf /");
135 assert!(matches!(err, ToolError::SafetyCheckFailed(_)));
136 assert_eq!(
137 err.to_string(),
138 "Safety check failed: Dangerous command detected: rm -rf /"
139 );
140 }
141
142 #[test]
143 fn test_invalid_params_error() {
144 let err = ToolError::invalid_params("Missing required parameter: path");
145 assert!(matches!(err, ToolError::InvalidParams(_)));
146 assert_eq!(
147 err.to_string(),
148 "Invalid parameters: Missing required parameter: path"
149 );
150 }
151
152 #[test]
153 fn test_io_error_conversion() {
154 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
155 let err: ToolError = io_err.into();
156 assert!(matches!(err, ToolError::Io(_)));
157 }
158
159 #[test]
160 fn test_cancelled_error() {
161 let err = ToolError::Cancelled;
162 assert_eq!(err.to_string(), "Cancelled");
163 }
164
165 #[test]
166 fn test_is_retryable() {
167 assert!(ToolError::timeout(Duration::from_secs(1)).is_retryable());
168 assert!(ToolError::Io(std::io::Error::other("test")).is_retryable());
169 assert!(!ToolError::not_found("test").is_retryable());
170 assert!(!ToolError::permission_denied("test").is_retryable());
171 assert!(!ToolError::safety_check_failed("test").is_retryable());
172 assert!(!ToolError::Cancelled.is_retryable());
173 }
174
175 #[test]
176 fn test_is_permission_error() {
177 assert!(ToolError::permission_denied("test").is_permission_error());
178 assert!(!ToolError::not_found("test").is_permission_error());
179 assert!(!ToolError::Cancelled.is_permission_error());
180 }
181
182 #[test]
183 fn test_is_safety_error() {
184 assert!(ToolError::safety_check_failed("test").is_safety_error());
185 assert!(!ToolError::not_found("test").is_safety_error());
186 assert!(!ToolError::permission_denied("test").is_safety_error());
187 }
188}